MySQL 各种锁理解总结

该文章举例都是基于 InnoDB 可重复读(RR)隔离级别的,mysql 版本 8.0

根据加锁的范围,MySQL 里面的锁大致可以分成全局锁、表级锁和行锁三类

全局锁

全局锁就是对整个数据库实例加锁。MySQL 提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)。整个库处于只读状态的时候,其他线程的 DML 和 DDL 语句都会阻塞。

适用场景:全库逻辑备份,就是把整库每个表都 select 出来存成文本。主要是针对 myisam 这种不支持事务一致性读的引擎,InnoDB 引擎可通过 mysqldump 使用参数–single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。

表级锁

MySQL 里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)

表锁

表锁的语法是 lock tables … read/write,通过 unlock tables 主动释放锁,也可以通过客户端断开连接进行释放。

举个例子, 如果在某个线程 A 中执行 lock tables t1 read, t2 write; 这个语句,则其他线程写 t1、读写 t2 的语句都会被阻塞。同时,线程 A 在执行 unlock tables 之前,也只能执行读 t1、读写 t2 的操作。连写 t1 都不允许,自然也不能访问其他表

元数据锁(meta data lock,MDL)

MDL在访问一个表的时候会被自动加上的,增删查改时加 MDL 读锁,DDL 加写锁。其中读读不互斥,读写、写写互斥。
目的:保证读写的正确性。

假如线程 A 查询 C1,C2 两列,线程 B 执行删除列 C1,如果不加锁进行互斥,那么线程 A 查询的结果跟表结构不一致,这是不允许的。

注意事项:给表加个列时,注意程序的长事务(事务没提交时一直占用 MDL 读锁),会长时间阻塞DDL(MDL写锁),进而阻塞后面的增删查改的事务。例子如下:(只有当 sessionA commit 时,其他阻塞线程还会继续执行)

sessionA sessionB sessionC sessionD sessionE
begin;
select * from tb limit 1;//事务没提交,一直持有 MDL 锁
select * from tb limit; //不阻塞。注意没有 begin,事务自动提交。
alter table tb add c int;//阻塞,因为想加 MDL 写锁,以 sessionA 持有的 MDL 读锁互斥
sselect * from tb limit 1;//阻塞,因为前面sessionC MDL 写锁 update tb set b = 1 where a = 0;//阻塞,因为前面sessionC MDL 写锁

自增锁

auto-inc锁是一种特殊的表级锁,由插入带有自动递增列的表中的事务获取。自增 id 锁并不是一个事务锁,而是每次申请完就马上释放,以便允许别的事务再申请。通过参数 innodb_autoinc_lock_mode 不同值控制不同行为,默认值是 1。

  • 0: 即语句执行结束后才释放锁;

  • 1:

    • 普通 insert 语句,自增锁在申请之后就马上释放;
    • 类似 insert … select 这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放;
  • 2: 所有的申请自增主键的动作都是申请后就释放锁。

特点:自增 id 不是连续递增,开发时不要以自增 id 连续做判断逻辑,原因如下:

  • 唯一键冲突。申请到的自增主键弃用
  • 事务回滚。申请到的自增主键弃用

行锁

InnoDB支持行锁,MyISAM 不支持。行锁可以减少并发冲突,提高并发读,这个是 InnoDB 代替 MyISAM 原因之一。

InnoDB 行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁。

加锁特点:根据隔离级别需要的时候加上,在事务提交的时候才释放,并不是不需要就立刻释放。所以,在开发时,一个事务需要锁多行(可能多表不同行),把最可能造成锁冲突的语句放在后面,减少事务之间等待时间,提高并发度。锁的是索引本身,而非记录本身。

行锁的两种形式

  1. 共享锁/读锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁
  2. 排它锁(X): 允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。

SS 可并发、SX 或 XX 不能并发

显示锁定

select ... lock in share mode //共享锁(S)
select ... for update //排他锁(X)
select ... for update

加锁:将查询访问到的索引条目加上排他锁(X),注意如果没有命中索引,走全部扫描时,相当于锁全表。

特点:保证只能一个人去处理数据,其他人不能读也不能写

场景:为了让自己查到的数据确保是最新数据(当前读),并且查到后的数据只允许自己来修改。

select ... lock in share mode

加锁:将查询访问到的索引条目加上共享锁(S),其他事务针对加锁的数据不能进行 DML 操作

特点:保证大家可以一起读,但只能一个人写

场景:为了让自己查到的数据确保是最新数据(当前读),不允许其他人进行修改。(自己不一定能修改,因为其他事务可能持有 S锁)

隐式锁定

不用上面显示锁定方式,隔离级别在需要的时候自动加锁。例如:

update tb set b = b+1 where id = 1;//因为 ID 是主键索引,因此针对 id =1 这行加了行锁

对于insert、update、delete,InnoDB会自动给涉及的数据加排他锁(X);普通 select 不加共享锁和排他锁,会加 MDL锁

意向锁

意向锁的目的就是为了允许行锁和表锁共存,提升判断性能的(高效的互斥判断,加表锁时的判断)

假如没有意向锁,事务 A 给 id=1 加了排他锁,事务 B 想给该表加表排他锁时,需要判断表里面是否有行锁,就需要遍历每条记录,看看是否有行锁,如果有,就意味整个表加了表排他锁。

意向锁有 2 种,都是表锁,具体如下:

  • 意向共享锁(IS): 事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁
  • 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。

所以的 IX锁、IS 锁之间都不互斥,IX 锁、IS 锁只是为了与表共享锁和表排他锁互斥(不是行锁哦)。

举例说明
  • 例子 1: 事务 A 准备给 id= 1 行加行排他锁,获取到该表的 IX 锁,这时事务 B 准备加表锁(例如 DDL),判断 IX 锁已经被其他事务获取,因此说明有其他事务针对某些行数据加行排他锁,因此事务B 阻塞(并不需要遍历表每行数据判断是否有行锁,所以高效)
  • 例子 2:事务 A 准备给 id =1 行加行排他锁,给表加了 IX 锁,这时事务 B 准备给 id= 2行加行排他锁,给表加了 IX 锁。这说明了:IX 锁与 IX锁之间不互斥,仅仅为了表锁高效的判断,作为行锁和表锁之间的桥梁,通过意向锁来实现不同粒度(表、行)的锁之间如何做互斥判断

间隙锁(Grap Lock)

间隙锁指锁索引上的一个区间(左右开区间),为了防止读已提交隔离级别出现的幻读

临键锁(Next-Key Lock)

  1. next-key lock = 间隙锁(grapLock)+ 行锁(record lock),左开右闭区间,例如 (0,10]
  2. Innodb 使用 next-key lock 来锁定记录,例如 select .... for udpate

关于间隙锁和临键锁,请参考专门文章:幻读解决方式--mysql 间隙锁(grap lock)原理

插入意向锁(Insert Intention Locks)

对已有数据行的修改与删除,必须加互斥锁X锁,那对于数据的插入,怎么实现互斥呢?插入意向锁,孕育而生

插入意向锁是一种 grap lock,专门针对 insert 使用的。当插入时,插入的间隙已经有了 grap 锁,那么就申请插入意向锁。

特点:

  • 如果插入到同一索引间隙中的多个事务没有插入到间隙中的同一位置,那么它们就不需要等待对方
  • 插入意向锁不会阻止任何锁,对于插入的记录会持有一个记录锁。

例子:tb 表,其中 a 是二级索引

+----+------+------+------+

| id | a | b | c |

+----+------+------+------+

| 40 | 40 | 40 | NULL |

| 50 | 50 | 50 | NULL |

+----+------+------+------+

事务 A 事务 B
begin;
select * from tb where a > 40 for update;//基于索引a加了next-key lock,(40,50]
begin;
insert into tb(id,a,b) values(45,45,45);//阻塞,因为命中 next-key lock
commit; //不阻塞,执行但未提交
begin;
insert into tb(id,a,b) values(42,42,42);//不阻塞,执行成功
insert into tb(id,a,b) values(45,45,45);//阻塞,因为与事务 B的id=45记录锁冲突
commit;
//报重复主键的错,因为 事务 B 的 id=45已经插入,这时候不阻塞,但是 id=45主键重复了

死锁

死锁指的是不同线程都是等待对方释放资源,形成环状的资源依赖,导致几个线程进行无限等待状态。例如下面:

事务 A 事务 B
T1 begin;
update tb set b = b+1 where id = 1;
T2 begin;
update tb set b=b+1 where id =2;
T3 update tb set b= b+1 where id = 2;
T4 update tb set b = b+1 where id = 1;

上面的死锁说明:T3时,事务 A 等待事务 B释放 id=2的锁,事务 B 在 T4 时又等待 事务 A 释放 id=1的锁。事务 A 和事务 B 在相互等待对方的资源释放,因此进入死锁状态。

死锁策略

  1. 等待超时:在 InnoDB 中等待 50s后,出现死锁的第一个线程超时退出,其他线程可以继续执行。由于超时时间太长,或者设置太短(假如 1s),可能会出现误伤(例如不是死锁,普通锁等待)
  2. 死锁检测:通过检测,主动回滚死锁链中某个事务,InnoDB 默认开启死锁检测(innodb_deadlock_detect=on)

死锁检测性能问题

每条阻塞的线程,都要判断自己的加入是否会导致死锁,时间复杂度 O(n).例如:假如有 2000 条并发线程同时更新同一行,那么死锁检测就是需要判断 2000 * 2000 = 400万次,需要消耗大量 CPU 资源

一种思路:控制 MySQL 并发度,比如在 MySQL 端或中间件 proxy 层控制同行的更新并发度

减少死锁的主要方向

控制访问相同资源的并发事务量。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 锁概述 MySQL的锁机制,就是数据库为了保证数据的一致性而设计的面对并发场景的一种规则。 最显著的特点是不同的存...
    胡一巴阅读 236评论 0 0
  • 锁概述 MySQL的锁机制,就是数据库为了保证数据的一致性而设计的面对并发场景的一种规则。 最显著的特点是不同的存...
    胡一巴阅读 432评论 0 0
  • 摘要:一、故障描述 今天一个朋友遇到数据库遇到一个严重的故障,故障环境如下: MYSQL 5.6.16 RR隔离级...
    暖夏未眠丶阅读 680评论 0 1
  • 一、概述 数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种...
    不变甄心阅读 2,732评论 0 3
  • 夜空中, 高楼林立,灯火阑珊 风凌乱了你的发, 你默然停驻, 目光迷离。 车水马龙,人影匆忙, 灯光泛黄了回忆。 ...
    书呆女子阅读 357评论 4 5