Mysql数据库锁之---表级锁、行级锁、悲观锁、乐观锁、间隙锁GAP、死锁等简单介绍
目录
一、数据库默认加的锁
二、表级锁与行级锁
表级锁
行级锁
三、乐观锁与悲观锁
乐观锁
悲观锁
四、间隙锁GAP
五、死锁
总结
在Mysql中有:排它锁,共享锁,表锁,页锁,间隙锁,意向排它锁,意向共享锁,行锁,读锁,写锁,乐观锁,悲观锁,死锁...等关于锁的名词我们是耳听目染,但这些锁名词是什么?怎么用?
本文,以Mysql数据库为例,在MyISAM与InnoDB引擎下,梳理 表级锁与行级锁、乐观锁与悲观锁、间隙锁GAP、死锁
之后如果有补充也会在该本章内继续添加。
一、数据库默认加的锁
在并发情况下,为了尽可能的保证数据的正确性,故有了锁这个概念。
很多开发者都对锁概念有了解,但在平时的开发中,感觉并没有使用到"锁",但是程序依然可以正确运行。
这是因为Mysql的数据库**隐式**自动对一些语句加锁了。
InnDB:对于UPDATE、DELETE、INSERT语句,自动给涉及数据集加排他锁(X)
**MyISAM:**执行**SELECT**语句前,会**自动**给涉及的所有表加**读锁**。对于UPDATE、DELETE、INSERT等,会**自动**给涉及的表加**写锁**
二、表级锁与行级锁
从锁粒度,我们将锁分为了:表级锁、行级锁。
表级锁
开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低
行级锁
开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高
**InnDB**:
(1) 行锁和表锁都支持,但是!只有通过**索引条件**检索数据**才使用行级锁**,否则,InnoDB将使用**表锁。**
(2) 不支持查询和插入操 作的并发进行。
MyISAM:
(1) 只支持表锁。
(2) 支持查询和插入操作的**并发**进行。可以通过系统变量concurrent\_insert来指定哪种模式,在**MyISAM**中它默认是:如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从**表尾**插入记录。
表级锁 分为两种模式:
- 表读锁(Table Read Lock)
- 表写锁(Table Write Lock)
读读不阻塞:当前用户在读数据,其他的用户也在读数据,不会加锁
读写阻塞:当前用户在读数据,其他的用户**不能修改当前用户读的数据**,会加锁!
写写阻塞:当前用户在修改数据,其他的用户**不能修改当前用户正在修改的数据**,会加锁!
也就是说:读锁和写锁是互斥的,读写操作是串行。
参考资料:
- dev.mysql.com/doc/refman/…—官方手册
- ourmysql.com/archives/56…—-几个参数说明
三、乐观锁与悲观锁
乐观锁
概念:
乐观锁是一种**思想**!其核心思想是:这个模式没有从数据库加锁!
其"锁"概念的实现是:通过一些方式来判断数据库数据是否变动,如果未变动,则修改数据;如果发现数据变动,则不修改数据。
例:
我们为某表添加一个版本字段version。修改一次数据,version则 +1。如下图
(1) 张三和李四同时查出上面的数据。
(2) 张三先做出修改:
update A set Name=lisi,version=version+1 where ID=#{1} and version=#{1}
注:判断之前查询到的version与现在的数据的version进行比较,同时会更新version字段+1
(3) 此时,数据为:
(4) 李四后又修改:
update A set Name=lisi,version=version+1 where ID=#{1} and version=#{1}
但此时失败了!因为当前数据库中的版本跟查询出来的版本不一致!
这样便实现了”锁”机制的实现。这种便是“乐观锁”思想。它是一种思想!
悲观锁
使用:
悲观锁的话其实很简单(手动加行锁就行了)
- select * from xxxx for update
在select 语句后边加了 for update相当于加了排它锁(写锁),加了写锁以后,其他的事务就不能对它修改了!需要等待当前事务修改完之后才可以修改.
也就是说,如果张三使用select … for update,李四就无法对该条记录修改了~
参考资料:
- zhuanlan.zhihu.com/p/31537871—-什么是悲观锁和乐观锁
- www.zhihu.com/question/27…—-乐观锁和 MVCC 的区别?
四、间隙锁GAP
概念:
当我们**用范围条件检索数据,**并请求共享或排他锁时,InnoDB会给**符合范围条件的已有数据记录的索引项加锁**;
对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”。InnoDB也会对这个“间隙”加锁
注意!间隙锁只能在Repeatable read隔离级别下使用~
例:
假如emp表中只有101条记录
- Select * from emp where empid > 100 for update;
nnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。
目的:
- 为了防止幻读:在一个事务未提交前,其他并发事务不能插入满足其锁定条件的任何记录
五、死锁
概念:
是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
发生条件:
**1)互斥条件:**指进程对所分配到的资源具有排它性。即在一段时间内该资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持(持续获得)至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞(只能等其他进程用完再用),但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。(感觉和 互斥条件 差不多呢?)
4)环路等待条件:即{P0,P1,P2,P3}进程中,P0等待P1占用的资源;P1等待P2占用的资源,P3正在等待P0占用的资源。然后就这么等下去了….前三个为具条件,第四个为发生诱因!
解决方案:
1)以固定的顺序访问表和行。将两个事务的sql顺序调整为一致,来避免死锁。
2)大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。
- 3)一次锁定。在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。
- 4)降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。
- 5)为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。
参考资料:
- hedengcheng.com/?p=771#_Toc…
- www.cnblogs.com/LBSer/p/518…
- https://blog.csdn.net/joejames/article/details/37960873
总结
关于表锁
- 在MyISAM存储引擎中,当执行SQL语句的时候是自动加的。
- 在InnoDB存储引擎中,如果没有使用索引,表锁也是自动加的。
所以 其实我们程序员是很少关心它
关于行锁
InnoDB支持行锁:
- 共享锁—读锁—S锁
- 排它锁—写锁—X锁
在默认的情况下,select是不加任何行锁的~事务可以通过以下语句显示给记录集加共享锁或排他锁。
- 共享锁(S):SELECT * FROM table_name WHERE … LOCK IN SHARE MODE
- 排他锁(X):SELECT * FROM table_name WHERE … FOR UPDATE
关于乐观锁与悲观锁:
- 乐观锁其实是一种 思想!它不加锁!一般是添加version字段来验证数据是否异常。如果发现数据异常,则不更新(回滚)
- 悲观锁是 添加行锁!认为数据库会发生并发冲突,直接上来就把数据锁住,其他事务不能修改,直至提交了当前事务
关于间隙锁GAP:
在InnoDB下,Repeatable read隔离级别配合GAP间隙锁来**避免幻读**!
参考资料:
- zhuanlan.zhihu.com/p/29150809—Mysql锁总结
- blog.csdn.net/mysteryhaoh…—MySQL学习之——锁(行锁、表锁、页锁、乐观锁、悲观锁等)
- segmentfault.com/a/119000001…—MySQL InnoDB引擎锁的总结
本文参考:https://www.toutiao.com/i6583941335516971524/
还没有评论,来说两句吧...