目录
在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引擎锁的总结