锁
避免脏写
脏读、不可重复读与幻读,都发生在更新数据时,因此读的不对,就会发生这些问题——不过这些都通过MVCC
机制解决了。但是多个事务并发更新同一行数据时,会有脏写问题,而要解决脏写问题,就需要比MVCC
更加底层的机制——锁——来解决了。
MySQL中的任何数据需要更新的时候,都会创建一把锁,里面包含了自身的trx_id
和等待状态,然后将锁和当前数据关联起来。
根据MySQL的更新机制,更新数据时必须把数据页从磁盘文件里读取到缓存页里来才能更新,因此数据和关联的锁数据结构,都是在内存里面的。

此时,事务B
也想更新数据,并检查数据上有没有锁。如果发现数据已经被锁,那么事务B
也会生成一个自己的锁,等待前一个事务执行完成后解锁(释放资源)。

如果事务A
执行完成,会把自己的锁给释放,然后会寻找数据上是否有其他锁。
当发现事务B
也加了锁时,事务A
会修改事务B的锁等待状态,并唤醒事务
继续执行。

独占锁
当事务更新数据,如果有其他事务的写请求时,默认加的就是独占锁,或排他锁Exlucde
——一次只让一个事务得以执行,后续事务都需要排队等待前一个事务的锁释放。
当事务更新数据,如果有其他事务的读请求时,默认开启MVCC
机制,所以不需要加锁——读和写操作是不需要互斥的。
共享锁
如果在读取时需要加锁,MySQL也支持一种S锁:Share
共享锁。
查询的共享锁。
> SELECT * FROM TABLE LOCK IN SHARE MODE;
查询的互斥锁。
> SELECT * FROM TABLE FOR UPDATE;
如果此时有事务正在更新数据,也就是数据已经被加了独占锁,那么是不能再加共享锁的——独占锁和共享锁互斥。
反过来也一样——如果已经加了共享锁,当有事务需要更新时,也是不能再加独占锁的。
但是如果数据已经加了共享锁,其他事务的读请求还是可以继续加共享锁的,也就是共享锁之间是不会互斥的。
锁的互斥规律
更新数据时必然加独占锁,和其他独占锁、共享锁互斥。
如果查询不带共享锁,由于
MVCC
机制,是不会受到独占锁影响的。如果查询带共享锁,其他查询仍然可以加共享锁,但是无法更新,因为和独占锁互斥。
查询操作也可以加互斥锁:
SELECT * FROM TABLE FOR UPDATE;
。
锁类型 | 独占锁 | 共享锁 |
---|---|---|
独占锁 | 互斥 | 互斥 |
共享锁 | 互斥 | 不互斥 |
行级锁的触发
独占锁和共享锁,其实都是属于行级锁。多个事务并发更新数据时,都要在行级别加独占锁,也就是行锁
。
行锁
不会发生脏写
问题,但在读取时,会有两种可能。
基于
MVCC
机制进行事务隔离,读取undo log
快照。查询的同时加共享锁或互斥锁。
不要轻易加行锁,而是通过Redis/Zookeeper
实现分布式锁。
表锁
在数据库中,除了给数据行加锁,还可以给表加锁。
MySQL通用的DDL
元数据锁Metadata Locks
并不是表级锁
,但表的DDL
和增删改(CDU)
操作确实是互斥的。
表级锁
与存储引擎相关,每种存储引擎都提供了自己的表级锁,比如InnoDB
表级锁。
MySQL表级锁
实际开发中使用不多,分为两种。
手动表锁。
手动共享锁:
LOCK TABLES xxx READ;
。手动独占锁:
LOCK TABLES xxx WRITE;
。
表级意向锁。
如果有事务在执行增删改操作,那么会自动在表级加一个意向独占锁。
如果有事务在执行查询操作,那么会自动在表级加一个意向共享锁。
这两种自动意向锁不会互斥。
但手动加的表级共享锁、独占锁,和自动在表上加的意向共享锁、独占锁之间反而是有一定的互斥关系。
一般读操作默认都是通过MVCC
机制进行的,极少手动加锁。
锁类型 | 手动独占锁 | 意向独占锁 | 手动共享锁 | 意向共享锁 |
---|---|---|---|---|
手动独占锁 | 互斥 | 互斥 | 互斥 | 互斥 |
意向独占锁 | 互斥 | 不互斥 | 互斥 | 不互斥 |
手动共享锁 | 互斥 | 互斥 | 不互斥 | 不互斥 |
意向共享锁 | 互斥 | 不互斥 | 不互斥 | 不互斥 |
感谢支持
更多内容,请移步《超级个体》。