innoDB的事务,是基于锁来实现的,用到事务不自然就会用到锁,而如果对锁理解的不通透,很容易造成线上问题。
数据库加锁的分析,和事务的引擎,隔离级别,索引,主键索引都有关系,
如果去考虑引擎和各种隔离级别的话,就会很复杂了,所以下面都是基于innoDB和RR的隔离级别进行分析:
表结构:
内容:
1 , 根据主键更新
如果根据主键来行数
结论,如果根据非主键来更新,会把整个表进行锁定,无法 进行更新操作。
注:只要是根据主键索引来更新,哪怕事务A没命中主键,也不会锁定整个表
2,根据非索引非主键更新
3, 如果在userId 列上加入普通唯一索引
修改成
再更新
4, 如果在userId 列上加入普通非唯一索引 (重点探讨)
把userId改成非唯一索引:
记录内容如下:
+----+--------+------+
| id | userId | name |
+----+--------+------+
| 1 | 10001 | ce1 |
| 2 | 10002 | ce2 |
| 3 | 10001 | ce3 |
| 4 | 10004 | ce4 |
+----+--------+------+
再相同操作
其实最终还是按主键锁住的记录 id=1和id=3的记录
非唯一索引与普通索引,更一步的区别是GAP锁
gap锁是用于解决幻读的存在,演示
把记录修改成,如:
id为pk. userId为Normal key
1, 首先GAP锁针对的是insert操作
2, 当更新userId='100020'时,会锁住两边的记录区间,防止幻读的存在。
3, 锁是作用在普通索引上,但由于索引是由B+树存储,那么锁住的是两边的区间,防止insert
GAP锁为什么不是锁住一条记录,而是锁住一个区间呢?
附上疑问: https://www.oschina.net/question/867417_2289606
其实:
GAP锁是解决幻读存在的,如当 delete时就必须锁住区间了
可见,这个GAP锁,锁住的是100040~无穷大 的记录
死锁的产生分析:
1, 两条语句产生的死锁
id = pk, userId= key
最简单的。两条语句互相更新等待
2, 由于gap锁,删除一台不存在的记录
如,先删除一条记录,然后插入一条记录, 如果记录GAP锁冲突,两个事务容易互为死锁。如:
结果直接抛出:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
分析:
A事务 delete 加入gap锁【100010,100020】, 第二段【100020,100030】
B事务 delete加入gap 锁【100040,无穷大】
然后A事务插入,获取插入意向锁时B事务的GAP锁被阻塞
B事务插入,获取插入意向锁时时被A事务的GAP锁阻塞
结果死锁