1.一个事务中某条sql语句对某行开启lock in share mode 后,其它事务中的sql语句就无法更新该行数据,但是可以继续对该行数据进行lock in share mode加锁,但是无法对该行数据进行for update加锁。
2.一个事务中某条sql语句对某话开启for update后,其它事务中的sql语句就无法更新该行数据,且无法继续对该行数据进行for update加锁,也无法对该行数据进行lock in share mode 加锁。
3.临键锁:比如一个索引有id=10,11,13,20这四个值,那么该索引可能被Next-key Locking的区间为:
(-∞,10] (10,11] (11,13] (13,20] (20,+∞)
当查询的索引含有唯一属性时,InnDB存储引擎会对Next-key Lock进行优化,将其降级为Record Lock记录锁,即仅锁住索引本身,而不是范围。
锁分三级:全局锁,表级锁,行级锁
全局锁的语法是Flush tables with read lock (FTWRL)。
全局锁的典型使用场景是:做全库逻辑备份。
表锁的语法是 lock tables … read/write。
行锁的语法是select ... in share mode/for update; update ...
锁问题:有三个脏读,不可重复读,丢失更新
解决脏读需要把数据库的隔离级别提升至读已提交。
解决不可重复读需要把数据库的隔离级别提升至可重复读。
丢失更新:简单来说其就是一个事务的更新操作会被另一个事务的更新操作所覆盖,从而导致数据的不一致。例如:
1)事务T1将行记录r更新为v1,但是事务T1并未提交
2)与此同时,事务T2将行记录r更新为v2,事务T2并未提交
3)事务T1提交
4)事务T2提交
但是,在当前数据库的任何隔离级别下,都不会导致理论意义上的丢失更新问题。这是因为,即使是读未提交的隔离级别,对于DML操作时,需要对行或去其它粗粒度级别的对象加锁。因此在上述步骤2)中,事务T2并不能对行记录r进行更新操作,其会被阻塞,直到事务T1提交。
虽然数据库能阻止丢失更新,但是在生产应用中还有一个逻辑意义上的丢失更新问题,而导致该问题的并不是数据库本身的问题。实际上,在所有多用户计算机系统环境下(并发场景)都可能产生这个问题。例如:
1)事务T1查询一行数据,放入本地内存,并显示给一个终端用户User1
2)事务T2也查询该行数据,并将取得的数据显示给终端用户User2
3)User1修改这行数据,更新数据库并提交
4)User2修改这行数据,更新数据库并提交
显然,在这个过程中User1的修改更新操作“丢失”了。 即库存超卖问题。
如何解决这个问题呢?
Time | 会话A | 会话B |
1 | BEGIN; | |
2 | SELECT stock FROM product WHERE id = 1 FOR UPDATE; | BEGIN; |
3 | SELECT stock FROM product WHERE id = 1 FOR UPDATE; # 会被阻塞等待 | |
....(其它操作) | ....(其它操作) | |
m | UPDATE product SET stock = 10 WHERE id = 1; | |
m+1 | COMMIT; | |
m+2 | UPDATE product SET stock = 8 WHERE id = 1; #由于前面语句被阻塞,这条语句也不会被立马执行 | |
m+3 | COMMIT; |
这样操作就避免了丢失更新问题。
在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。
一个疑问,为什么不允许执行UPDATE语句,而首先要进行SELECT ... FOR UPDATE操作?
的确,直接使用UPDATE可以避免丢失更新问题的产生。然而在实际应用中,应用首先需要检测库存信息,查看是否可以进行扣减操作,然后再进行最后的UPDATE操作,因此在SELECT与UPDATE操作之间可能还存在一些其它的SQL操作。