[db:标题]
摘要:共享锁、排他锁、意向锁、记录锁、间隙锁、临键锁(Next Key Lock)、插入意向锁、AUTO-INC、悲观锁、乐观锁
MySQL的InnoDB的锁机制
MySQL的InnoDB引擎下,在锁的级别上一般分为两种:共享锁(S锁)、排他锁(X锁)
共享锁
共享锁又称为读锁,是读取操作时创建的锁。其他用户可以并发读取数据,但是一旦某行被加上共享锁,其他事务仍可继续对该行加共享锁;但任何事务若想对该行加排他锁(即执行 UPDATE/DELETE 等),则必须等待所有共享锁释放。
例如:事务T1对数据A的加上共享锁,其他事务只能对数据A加共享锁,不能加排他锁。而共享锁的事务只能读取数据,不能修改数据。
共享锁的加锁方式如下:
-- MySQL8.0之前的推荐写法
SELECT ID,NAME FROM TABLE1 WHERE ID >1 AND ID <10 LOCK IN SHARE MODE;
-- MySQL8.0以及之后的推荐写法
SELECT ID,NAME FROM TABLE1 WHERE ID >1 AND ID <10 FOR SHARE;
在查询语句后面增加LOCK IN SHARE MODE,会对查询范围中的每行都加共享锁,这样的数据行还可以被其他事务成功申请共享锁,但是不能被申请排他锁。
排他锁
排他锁又称写锁,若事务T1对数据A加上排他锁之后,其他事务则不能再对数据A加任何类型的锁。而且排他锁的事务既可以读数据又可以写数据。
排他锁的加锁方式:
SELECT ID,NAME FROM TABLE1 WHERE ID >1 AND ID <10 FOR UPDATE;
在查询语句后面增加FOR UPDATE,会对查询命中的每条记录都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。
共享锁和排他锁的总结
当一行数据获取了排他锁,那么其他事务就不能再对这一行数据添加共享锁或者排他锁。
当一行数据获取了共享锁,那么其他事务依然可以对这一行数据添加共享锁,但不能添加排他锁。
使用场景
共享锁
读-读并发高且不允许“脏读”:例如报表统计时,希望别的事务可以并发读,但禁止任何事务修改这些行。
父子表一致性校验:先对父表主键 FOR SHARE,再读子表,防止父记录在此期间被删。
排他锁
读-改-写(Read-Modify-Write):
SELECT balance FROM account WHERE id = 1 FOR UPDATE;
UPDATE account SET balance = balance - 100 WHERE id = 1;
防止并发扣款出现负余额。
悲观锁实现“秒杀库存”:先 FOR UPDATE 检查库存 > 0,再 UPDATE 减库存。
-- 加排他锁并读取当前库存
SELECT stock FROM merchandise WHERE id = 123 FOR UPDATE;
-- 在应用层判断返回的 stock 值,若 stock > 0,则继续;否则回滚并返回“已售罄”
-- 真正扣库存(MySQL 8 支持原子写法,也可拆两步)
UPDATE merchandise SET stock = stock - 1 WHERE id = 123 AND stock > 0;
意向锁
除了S锁(共享锁)和X锁(排他锁)之外,InnoDB还有两种锁,就是IS锁和IX锁,S和X前面的I是Intention的意思,即意向锁,IS就是意向共享锁,IX就是意向排他锁。
在MySQL的InnoDB引擎中,根据锁的不同范围也是有区分的,例如:表级锁、间隙锁、行级锁等。当多个事务同时访问同一个数据时,多个事务同时申请获取锁,那么就有可能导致互相阻塞甚至产生死锁。
例如:
事务T1对表Table1中的一行加上了行级锁,此时这行记录就不能被其他事务写了。
事务T2申请对Table1增加了表级锁,若申请成功了,那么就可以修改表中的任意一行记录。
这就跟事务T1发生了冲突。
那么,想要解决这个问题,就需要让事务T2在对Table1增加表级锁的时候,先判断一下是不是有事务增加过行级锁。但是,事务T2总不能逐条判断是否有加锁吧?
因此,为了解决这个问题,MySQL引入了意向锁机制,意向锁是数据库管理系统中用于实现锁协议的一种锁机制,主要是用来处理不同粒度锁(如行锁和表锁)之间的并发性问题(而同粒度的锁之间一般是通过互斥来解决并发的)。
意向锁不做锁定资源的操作,主要是通知的作用,防止在已经加锁的数据上设置不兼容的锁。
意向锁不是直接由用户请求的,而是MySQL管理的。
当一个事务想获取一个行级锁或表级锁时,MySQL会自动获取相应的表的意向锁。
