MySQL锁

1.锁的概述

锁:是计算机协调多个进程或者线程并发访问某一资源的机制。
保证数据并发访问的一致性/有效性,锁冲突也是影响数据库并发访问的性能的一个重要因数。
mysql不同存储引擎支持不同的锁机制

存储引擎与锁机制

image.png

| 表级锁:table-level,开销小,加锁快;锁定粒度大,发生锁冲突的概率高,并发度最低。
| 行级锁:row-level,开销大,加锁慢;会出现死锁;锁定力度最小,发生锁冲突的概率最低,并发度也最高。
| 页面锁:page-level,开销与加锁事件介于表锁和行锁之间;会出现死锁;锁定粒度也是介于表锁/行锁之间;并发度一般。

2.锁的分类

| 按照锁的粒度划分:全局锁、表锁、行锁、页锁
| 按照锁的加锁方式划分:共享锁(S)、排它锁(X)
| 意向锁(表锁):意向共享锁(IS),意向排他锁(IX)
| 还有两种思想上的锁:悲观锁、乐观锁。
| InnoDB中有几种行级锁实现方式:Record Lock、Gap Lock、Next-key Lock

| 共享锁(S读锁):在锁定期间,多个用户可以读取同一个资源,读取过程中数据不会发生变化。
| 排他锁(X写锁):在锁定期间,只允许一个用户写入数据,其它用户的读取,写入等操作都会被拒绝。
| 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
| 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
| 乐观锁:利用数据版本号(version)机制是乐观锁最常用的一种实现方式。提交时候对比读取之前的版本号。
| 悲观锁:悲观锁(Pessimistic Locking)是指在数据处理过程,使数据处于锁定状态,一般使用数据库的锁机制实现。
| Record Lock:对索引项加锁。
| Gap Lock:间隙锁,对索引项之间的“间隙”,第一条记录前的“间隙”或者最后一条记录后的“间隙”加锁。
| Next-key Lock:Record Lock+Gap Lock;前2种的组合,对记录及其前面的间隙加锁。

3.全局锁

Server层实现的
|定义:全局锁就是对整个数据库实例加锁。
|命令: Flush tables with read lock (FTWRL)
|作用:使得整个数据库处于只读状态的时候,以下语句会被阻塞:数据的增删改、建表、修改表结构和更新类事务的提交语句。
|使用场景:做全库逻辑备份。也就是把整库每个表都 select 出来存成文本。通过 FTWRL 确保不会有其他线程对数据库做更新,然后对整个库做备份。
|mysqldump:支持事务的存储引擎可以使用逻辑备份工具是 mysqldump。当 mysqldump 使用参数–single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。
为什么不使用set global readonly=true 的方式呢?
1.readonly 的值会被用来做其他逻辑如从库只读
2.在异常处理机制上有差异;FTWRL 命令发生异常断开,MySQL 会自动释放这个全局锁。而设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。

INSERT into `crawler_list_gift` (`user_id`) VALUES ('douyin3');
UPDATE `crawler_list_douyin` set `status` = 1 WHERE `id` = 1;
//加锁
Flush tables with read lock;
//解锁
unlock tables;
//加锁后进行增删改操作提示错误信息
Error Can't execute the query because you have a conflicting read lock

4.表锁

Server层实现的
MySQL 里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)。
4.1表锁
表锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Read Write)

MySQL中表锁兼容性

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

SELECT * FROM `test1`;
SELECT * FROM `test2`;
SELECT * FROM `test3`;
//加锁
lock tables test1 read, test2 write;

UPDATE `test2` set `status`=1 WHERE `id` =1;
Error : Table 'test1' was locked with a READ lock and can't be updated

//解锁
 unlock tables 

4.2 MDL表锁
MDL 在 MySQL 5.5 版本中引入了 MDL,不需要显式使用,在访问一个表的时候会被自动加上。
作用:保证读写的正确性。防止执行查询期间表结构的变更,保证变更表结构操作的安全性。防止DDL和DML并发的冲突。
MDL读锁:对表做增删改查的操作加MDL读锁。
MDL写锁:对表结构做变更操作加MDL写锁。
读锁之间不互斥;读写锁之间、写锁之间是互斥的;如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。

MDL锁机制

//图示语句
BEGIN;
SELECT * FROM `test1`;
rollback;
commit;

问题:sessionA查询会被阻塞吗?

5.页锁

6.行锁

行锁是在引擎层实现的
在 InnoDB 事务中,行锁是在需要的时候才加上的,要等到事务结束时才释放。这个就是两阶段锁协议

两阶段锁协议

事务B会被事务A阻塞

InnoDB的行锁模式和加锁方式
InnoDB实现了两种类型的行锁
共享锁(S)/ 排他锁(X)
为了允许行锁和表锁共存,实现多粒度机制,InnoDB还有两种内部使用的意向锁,这两种意向锁都是表锁。
意向共享锁(IS)/ 意向排他锁(IX)

InnoDB行锁模式兼容性列表

//共享锁获得
select * from `test1` where *** lock in share mode;
//排他锁获得:
select * from `test1` where *** for update;
InnoDB引擎共享锁例子
//示例代码语句
set autocommit=0;
SELECT * FROM `test1`;
SELECT * FROM `test1` WHERE `id` = 2 lock in share mode;
UPDATE `test1` SET `name` = '陈' WHERE `id` = 2;
InnoDB引擎的排他锁例子
//示例代码语句
//session A
set autocommit = 0;
SELECT * FROM `test1` WHERE `id` = 3;
SELECT * FROM `test1` WHERE `id` = 3 FOR UPDATE;
SELECT * FROM `test1` WHERE `id` = 3 lock in share mode;
UPDATE  `test1` set `name` = 'test1' where `id` = 3;
COMMIT;
//session B
set autocommit = 0;
SELECT * FROM `test1` WHERE `id` = 3;
SELECT * FROM `test1` WHERE `id` = 3 lock in share mode;
SELECT * FROM `test1` WHERE `id` = 3 for UPDATE;

InnoDB行锁的实现方式

行锁之间的冲突关系

InnoDB行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB将通过隐藏的聚簇索引来对记录加锁。
InnoDB的行锁分为3种情形:Record lock / Gap lock / Next-key lock

如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,实际效果跟表锁一样。相同的索引值会出现锁冲突。


索引加锁示例
//session A
set autocommit=0;
SELECT * FROM `test_record1` WHERE `user_id` = '1' for update;
COMMIT;
//session B
set autocommit=0;
SELECT * FROM `test_record1` WHERE `user_id` = '2' for update;
SELECT * FROM `test_record1` WHERE `name` = 'name33' for update;
COMMIT;

当表有多个索引,不同的事务可以使用不同的索引锁定不同的的行。
即便使用索引字段,但是否使用索引检索数据由MySQL决定,在MySQL认为使用全表扫描效率高的情况下,就不会使用索引,这种情况下也会对所有记录加锁。
Gap Lock(间隙锁)
间隙锁是在可重复读隔离级别下才会生效
为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 (Gap Lock)。
顾名思义,间隙锁,锁的就是两个值之间的空隙。
跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。间隙锁之间都不存在冲突关系
目的:保护这个间隙,不允许插入值

间隙锁之间不互锁

//隔离级别需要时可重复读RR
select @@transaction_isolation;
//session A
set autocommit=0;
SELECT * FROM `test_gap` where `age` = 7 lock in share mode;
//session B
set autocommit=0;
SELECT * FROM `test_gap` where `age` = 7 for update;
//session C 出现锁等待
set autocommit=0;
INSERT INTO `test_gap` (`age`) VALUES (8);

Next-key锁
当我们用范围条件检索数据,并请求锁时,InnoDB会给符合的已有记录数据记得索引项加锁;对于健值在范围内但不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的Next-Key锁。
目的:防止幻读 / 满足恢复和复制的需要

//mysql 8 以前 是tx_isolation
//现在 是transaction_isolation
select @@transaction_isolation;
Next-key锁阻塞例子
//session A
select @@transaction_isolation;
set autocommit=0;
SELECT * FROM `test_next` WHERE `id` > 5 for UPDATE;
ROLLBACK;
//session B
select @@transaction_isolation;
set autocommit=0;
UPDATE `test_next` SET title='chen' WHERE `id` = 5;
INSERT INTO `test_next` (`user_id`) VALUES ('123');
UPDATE `test_next` SET title='chen' WHERE `id` = 6;
ROLLBACK;

InnoDB中不同的sql在不同的隔离级别下的锁比较


InnoDB中不同的sql在不同的隔离级别下的锁比较

如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放
加锁规则
原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。
原则 2:查找过程中访问到的对象才会加锁。
优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

7.死锁

概念:当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。

死锁例子

Deadlock found when trying to get lock; try restarting transaction

//session A
set autocommit=0;
SELECT * FROM `test2` WHERE `id` = 1 for UPDATE;
SELECT * FROM `test3` WHERE `id` = 1 for UPDATE;
ROLLBACK;
//session B
set autocommit=0;
SELECT * FROM `test3` WHERE `id` = 1 for UPDATE;
SELECT * FROM `test2` WHERE `id` = 1 for UPDATE;
ROLLBACK;

两个事务都需要对方持有的排他锁才能继续完成事务,这种循环锁等待就是典型的死锁。
进入死锁后的系统策略:
1.直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置。
2.发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。
3.拆分热点更新数据行

1.策略1的弊端:时间太长不符合并发系统的设计,时间太短会误伤原本只是锁等待不是死锁的线程。
2.策略2的弊端:每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是 O(n) 的操作。消耗大量的 CPU 资源,却执行不了几个事务。

//超时事件默认50s
select @@innodb_lock_wait_timeout 
//默认为on/1
select @@innodb_deadlock_detect 

业务上的策略:
1.控制并发度,比如同一行同时最多只有 10 个线程在更新,那么死锁检测的成本很低。(需结合系统策略2,需更改MySQL源码或者中间件,控制并发度)
2.将一行改成逻辑上的多行来减少锁冲突。(使得业务逻辑更加复杂)
如何减少锁冲突和死锁
1.尽量使用较低的隔离级别;
2.精心设计索引,并尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会;
3.选择合理的事务大小,小事务发生锁冲突的几率也更小;
4.给记录集显式加锁时,最好一次性请求足够级别的锁。比如要修改数据,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁;
5.不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会;
6.尽量用相等条件访问数据,这样可以避免Next-Key锁对并发插入的影响;
7.不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁;
对于一些特定的事务,可以使用表锁来提高处理速度或减少发生死锁的几率。

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

推荐阅读更多精彩内容

  • 一、锁概述 锁是计算机协调多个进程或线程并发访问某一资源的机制(避免争抢)。 在数据库中,除传统的计算资源(如 C...
    小波同学阅读 533评论 0 10
  • 当一个系统访问量上来的时候,不只是数据库性能瓶颈问题了,数据库数据安全也会浮现,这时候合理使用数据库锁机制就显得异...
    初来的雨天阅读 3,570评论 0 22
  • 对于DB来说,经常会面对并发问题,但是开发的时候DB总是能很好的解决并发的问题。那么面对并发DB是怎么进行控制的呢...
    一只小哈阅读 2,418评论 2 34
  • 1. MySQL锁概述 MyISAM 和 MEMORY 存储引擎采用的是表级锁(table-level locki...
    安静点就睡吧阅读 229评论 0 1
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,046评论 0 4