欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【官方文档整理】Mysql中的锁

程序员文章站 2022-03-18 13:50:04
...

目录

共享锁(S)和排它锁(X)

意向锁(Intension Locks)

记录锁(Record Locks)

间隙锁(Gap Locks)

next-key锁

插入意向锁(Insert Intention Locks)


以下内容90%来自mysql 5.7官方文档,有兴趣的可以直接看原文。

共享锁(S)和排它锁(X)

都属于行锁,当一行数据被添加共享锁时,其它事务只能读取不能修改;当一行数据被添加排它锁时,其它事务的读取和修改操作都被禁止。

意向锁(Intension Locks)

分为意向共享锁(IS)和意向排它锁(IX)。意向锁协议:1、一个事务在获取S锁之前必须先或许IS锁;2、一个事务在获取X锁之前 必须先获取IX锁。一个事务获得IS锁表示这个事务准备为一行记录添加S锁,一个事务获得IX锁表示这个事务准备为一行记录添加X锁,意向锁存在的目的就是为了说明有事务正在或将要锁定一行记录,那这到底有什么意义呢?

在MySql中,可以为一张表添加S/X锁,也可以为一张表中的某几行添加S/锁,如下。显然,事务A获取了tab表的X锁,事务B就不能获取其它任意锁;同理,事务A获取了id=1的X锁,事务B就不能获取tab表的X/S锁,其它情况就不一一介绍了。在没有意向锁的情况下,事务A想要获取tab表的X锁,则必须遍历tab中所有的记录,看看有没有加X/S锁的情况,有就不能获取表的X锁,这样就很麻烦。但是,有了意向锁就不一样了,上面说了,意向锁存在的目的就是为了说明有事务正在或将要锁定一行记录,我不管这里的事务有多少个,不管要锁定的行记录有多少条,意向锁只说明“有事务锁定了某行”即可,可以把IS锁和IX锁看成两个字段boolean flagIS和boolean flagIX。这时如果事务A要获取tab表的X锁,只要判断(flagIS || flagIX)是否为true,是就不能获取表锁,否就直接获取;如果事务A想要获取tab表的S锁,就判断(flagIX)是否为true即可,这样就省去了遍历整张表的麻烦。

lock tables tab write                          # 为表tab添加X锁
lock tables tab read                           # 为表tab添加S锁
select * from tab where id = 1                 # 为表tab中id=1的记录添加S锁
update tab set name = 'zkw' where id = 1       # 为表tab中id=1的记录添加X锁

意向锁仅仅会阻塞获取整张表的锁的请求,不会阻塞任何其他请求,IS和IX锁之间是兼容的,事务A获取了IX锁,事务B依然可以获取IS锁和IX锁。

记录锁(Record Locks)

记录锁锁一个具体的索引记录(index record),就是对一行数据上的索引进行加锁。比如下面SQL执行后,Mysql会阻止其它事务插入、修改、删除t.c1=10的记录。当某个表没有定义索引,比如t.c1不是索引,InnoDB会创建一个隐藏的聚簇索引,然后用这个索引来作为记录锁锁定的对象。

SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;

间隙锁(Gap Locks)

  • 锁某个间隙(gap),这个间隙可能是两个索引记录之间的范围(如a~b),也可能是a之前,也可能是b之后。间隙可能跨越(span)单个索引值、多个索引值,甚至可以是空(empty)。
  • 间隙锁的目的是防止因其它事务不合时宜的插入导致的幻读。
  • 当使用唯一索引(唯一索引不允许具有索引值相同的行,如主键)进行等值查询时不会出现间隙锁,如其它情况则会出现。如下面sql,id为主键,则执行select语句后仅仅使用记录锁对id=100的行的索引加锁,不会锁住前面的间隙(the preceding gap),如id<100的间隙。如果id不是索引或者是一个非唯一索引,那么这条语句就会锁住前面的间隙。
SELECT * FROM child WHERE id = 100;
  • 间隙锁出现在RR隔离级别中,RU和RC中没有间隙锁。

next-key锁

是记录锁和间隙锁(锁某个索引记录之前的间隙,before the index record)的组合形式,在RR级别下被运用。当在查询和浏览索引时,会为每一个遇到的索引添加记录锁,假设执行下面的sql,显然InnoDB要扫描id为10,11,13,20的row,因此会为这些row添加记录锁,除此之外,也会为加锁记录前的间隙加锁。整个SQL执行后,最终的锁定范围如下,锁定范围内不能进行增删改操作,避免出现幻读。

  • 会锁定(上一个存在索引,当前添加记录锁的索引]。
  • 对于遍历索引中的最后一个记录,会锁定它后面的间隙。
# tab表中rows的id包括:4,10,11,13,20
select * from tab where id > 5 for update;

# 整个next-key锁加锁的范围
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)

插入意向锁(Insert Intention Locks)

插入意向锁就是一种类型间隙锁,在执行insert操作时加锁到insert索引的上下索引区间上,如数据库中有4,7两条记录,你插入5或6,就会给(4,7)这个区间加插入意向锁。

它的作用和上面的意向锁一样,也仅仅是为了说明在某个区间内有事务准备或正在执行insert操作,这样一来,那些想要给这个区间加间隙锁或next-key锁(就是锁住这个区间,不让其他事务读或写这个索引区间的数据)的事务直接判断这个区间有没有插入意向锁,有就等待,没有就锁住,避免了需要遍历这个区间的每条数据来查看是否有加锁的情况。

多个事务可以同时获取同一个区间的插入意向锁,只要他们插入记录的索引值不相同,那就不会发生阻塞情况,如上面例子,事务A和事务B分别插入5、6,都可以正常插入。

下面因子官方案例,经过自己测试,在创建child表和插入90、102两条数据后,不管是事务A或事务B谁先执行,后执行的事务总是会阻塞。道理很简单,我查这个区间时,你不能往这个区间里面插入数据;我往这个区间插入数据时,你不能查这个区间。

【官方文档整理】Mysql中的锁

【官方文档整理】Mysql中的锁

自增锁(AUTO-INC Locks)

自增锁是一种特殊的表锁,只针对有自增列的表。举个最简单的例子,当事务A往有自增列的表insert一条数据时,事务B必须阻塞自己的insert操作,以致于事务A插入数据的列是正确的。这样可能还不太明白,这就跟多线程情况下执行i++是一个道理,如果不对i++这个动作加锁,那么很可能会出现线程i++后i=1,线程2i++后i也等于1的情况。

InnoDB中通过配置“innodb_autoinc_lock_mode”选项来指定使用自增锁时的算法,通过这些算法能调整【自增值的可预测性】和【执行insert操作的事务并发量】之间的平衡。

算法以后有时间再看。。。

相关标签: mysql