MySQL进阶08 — 锁机制2

概述

本章,您将学习到 MySQL 8 中有关锁机制的知识,内容包括:

  • 锁机制的分类
  • 演示 InnoDB 存储引擎的行锁
  • 演示 InnoDB 存储引擎的表锁
  • 锁的查看
  • 有关乐观锁、全局锁和死锁的内容
  • MVCC

由于内容较多,本文档来说明并演示 InnoDB 存储引擎的表锁。

InnoDB 的表锁

主要包括以下锁:

  • 意向排他锁(IS)
  • 意向共享锁(IX)
  • 自增锁
  • MDL

意向共享锁(IS)和意向排他锁(IX)

前文提到:

意向锁:表锁的一种,其本身并不直接锁定任何数据行,而是作为一种 "标记" 存在,用于表明事务后续准备对表中的某些行施加共享锁(S 锁)或排他锁(X 锁)。意向锁由存储引擎自行维护,使用者无法手动操作。意向锁又划分为意向共享锁(IS 锁) 和意向排他锁(IX 锁)。

例如,当某一个事务想对表添加表锁,它需要先确认表中没有任何行被其他事务加了行锁,对于一张存储了十几万行数据的表而言,逐行检查的效率非常地低,于是便引入了意向锁。有了意向锁之后,事务在给某行数据加锁前,会先自动获取该表的意向锁(IS 锁或 IX 锁),这样其他事务想获取表锁时,只需检查意向锁状态即可快速判断,避免了逐行检查。

共享锁(S) 意向共享锁(IS) 排他锁(X) 意向排他锁(IX)
共享锁(S) 兼容 兼容 冲突 冲突
意向共享锁(IS) 兼容 兼容 冲突 兼容
排他锁(X) 冲突 冲突 冲突 冲突
意向排他锁(IX) 冲突 兼容 冲突 兼容

从这里可以看到,意向锁与意向锁之间是相互兼容的;排他锁与其他任何锁都是冲突的。注意!这里的共享锁或排他锁都是表级别的。

作者还是使用前文的 locks 库来演示,示例如下:

use locks;

create table if not exists tlock1(
  id int primary key auto_increment,
  name varchar(10)
);

insert into tlock1 values(1,'kone'),(7,'john'),(15,'lee');
Session 1(T1) Session 2(T2)
use locks;
set autocommit=0;
start transaction;
select * from tlock1 where id=1 for update;
use locks;
set autocommit=0;
start transaction;
select * from tlock1 where id=1 for update;
lock table tlock1 read;
select * from tlock1 where id=7 lock in share mode;
rollback;
rollback;
unlock tables;
  • Session 1 中,id 列应用了主键约束,自动生成同名主键索引,for update 表示主键索引持有一个行锁(记录锁,排他锁),其他事务对这条记录无法更新、删除,但是可以读取。这里 Innodb 存储引擎其实已经自动加了 IX 锁
  • Session 2 中,select * from tlock1 where id=1 for update; 会阻塞,因为行级 X 锁与行级 X 锁之间是相互冲突的
  • Session 2 中,lock table tlock1 read; 表示为 tlock1 表添加表级读锁(表级 S 锁),这条 SQL 语句会阻塞,因为 Session 1 的 IX 锁与表级 S 锁是冲突的
  • Session 2 中,select * from tlock1 where id=7 lock in share mode; 会正常执行,Session 1 只是锁住了 id=1 这条记录,id=7 这条记录不受影响

表锁的语法与演示

添加表锁的语法

基本语法为 —— lock table 表名 read | write;,比如:

# 为某张表添加表级读锁(表级 S 锁)
lock table 表名 read;

# 为某张表添加表级写锁(表级 X 锁)
lock table 表名 write;

# 为多张表添加表锁
lock table 表名1 read, 表名2 write;

表级 S 锁的特点:

  • 一旦表持有表级读锁,当前会话以及其他会话能读取表中的数据(select),但不能进行修改操作(delete、update、insert)

表级 X 锁的特点:

  • 一旦表持有表级写锁,当前会话可以允许读取表中数据以及进行修改操作,但其他会话不能读取表中的数据(select),也不能进行修改操作(delete、update、insert)

删除表锁的语法

语法为 —— unlock tables;

表级读锁的演示

Session 3 Session 4
use locks;
set autocommit=0;
start transaction;
lock table tlock1 read;
insert into tlock1 values(21,'Qa');
delete from tlock1 where id=7;
use locks;
set autocommit=0;
start transaction;
select * from tlock1;
delete from tlock1 where id=1;
update tlock1 set name='Liu' where id=15;
insert into tlock1 values(20,'M');
select * from tlock1 where id=15 lock in share mode;
rollback;
rollback;
unlock tables;
  • Session 3 中,lock table tlock1 read; 表示为 tlock1 表添加表级读锁
  • Session 3 中,insert into tlock1 values(21,'Qa'); 会输出 "ERROR 1099 (HY000): Table 'tlock1' was locked with a READ lock and can't be updated" 错误
  • Session 3 中,delete from tlock1 where id=7; 会输出 "ERROR 1099 (HY000): Table 'tlock1' was locked with a READ lock and can't be updated" 错误
  • Session 4 中,select * from tlock1; 正常运行
  • Session 4 中,delete from tlock1 where id=1;update tlock1 set name='Liu' where id=15;insert into tlock1 values(20,'M'); 会阻塞
  • Session 4 中,select * from tlock1 where id=15 lock in share mode; 正常运行,因为 Session 3 是表级读锁,当前会话以及其他会话可以读取表中的数据,Session 4 中,这是一个行级共享锁,两种锁并不冲突(都允许读)

表级写锁的演示

Session 5 Session 6
use locks;
set autocommit=0;
start transaction;
lock table tlock1 write;
select * from tlock1;
use locks;
set autocommit=0;
start transaction;
select * from tlock1;
delete from tlock1 where id=7;
update tlock1 set name='liu' where id=15;
insert into tlock1 values(20,'m');
rollback;
rollback;
unlock tables;
  • Session 5 中, select * from tb1; 正常执行
  • Session 6 中,select * from tlock1;delete from tlock1 where id=7;update tlock1 set name='liu' where id=15;insert into tlock1 values(20,'m'); 都会阻塞
表锁的使用提示
通常情况下,在 Innodb 存储引擎中,最常用的是行锁(记录锁、间隙锁、临键锁、插入意向锁、共享锁),表级读锁和表级写锁在大部分场景下应尽量避免使用,仅作为基本了解即可。

自增锁

自增锁:特殊的表锁,顾名思义,针对自增长列。当 A 事务中插入包含了自增长列(auto_increment)的新增数据时,会持有自增锁,若 B 事务执行 insert 操作包含了自增长列的数据,需要等待 A 事务的释放。自增锁有几种工作模式,由系统变量 innodb_autoinc_lock_mode 的值控制,MySQL 8 中,其值默认为 2,即交叉模式。

说明 优缺点
0 传统模式(Traditional) 所有类型的 insert 语法都会获得一个特殊的自增锁,当这条 insert 语句执行完毕后,锁释放,能保证生成的自增长列数据是自然连续的,且在 binlog 重放时,保证 master(Source) 和 slave(Replica) 数据的自增长是相同的。由于是表锁,当多个事务执行 insert 的时候,对于自增锁的争夺会限制并发能力。
1 连续模式(Consecutive) MySQL 8 之前的默认值。如果插入的时候能提前知道插入的数据量,则可以不用获取自增锁(转而使用的是 mutex 轻量锁),比如常规的 insert 语句。但如果不能提前知道插入的数据量,比如 insert into 表名1 select 字段1,字段2,字段3,字段4,字段5 from 表名2; 这样的,它还是会获取自增锁。本质上,连续模式和传统模式差不了太多。
2 交叉模式(Interleaved) 所有类型的 insert 语句都不会使用自增锁,可以同时执行多个语句,并发性以及扩展性是最好的。在这种锁模式下,自动递增的值保证是唯一的,并且在所有并发执行的 insert 语句中单调递增。但是,由于多个语句可以同时生成数字(也就是说,数字的分配在语句之间交叉进行),因此为任何给定语句插入行所生成的值可能不是连续的。
MySQL 8 开始,binglog 的格式从默认的 STATEMENT 更改为了 ROW 。STATEMENT 是基于 SQL 语句,每一条修改数据的 SQL 语句都会记录到 master 的 binlog 中,如果使用 STATEMENT 格式搭配交叉模式,会导致插入的自增长列数据在主从之间是不同的(因为 insert 语句的自增长列数据是交叉分配进行),这是不可接受的。 ROW 格式基于每一行数据的修改,主从同步的都是真实的行数据,不关心具体的 SQL 语句,因此 MySQL 8 选择交叉模式是合理的。

binlog 的三种格式:

  • ROW,默认格式,不记录具体的 SQL 语句,仅保存 SQL 语句执行成功后的每一行数据修改。该格式的特点是记录非常详细,占用的存储空间大,但是它能解决主从同步数据不一致的问题
  • STATEMENT,以 SQL 语句的形式保存修改数据,SQL语句可以是 DDL、DML 等
  • MIXED,前两者的混合

MDL(元数据锁)

MDL 的作用是为了保证读写的正确性,其加锁过程由 MySQL 自动控制,通常在访问一张表时会自动加上该锁。

  • 当对一张表的数据执行增删改查(insertupdatedeleteselect)操作时,加 MDL 读锁
  • 当对表的结构进行变更时(比如加字段、删字段、修改字段名、修改字段的数据类型、修改约束、修改表名等操作),加 MDL 写锁

MDL 读锁与 MDL 读锁之间不冲突,但 MDL 读锁与 MDL 写锁是冲突的,见下面的操作:

Session 7 Session 8 Session9
use locks;
set autocommit=0;
start transaction;
select * from tlock1;
use locks;
set autocommit=0;
start transaction;
alter table tlock1 add comments varchar(10);
use locks;
set autocommit=0;
start transaction;
select * from tlock1;
insert into tlock1 values(20,'ki');
show processlist\G;
rollback;
rollback;
rollback;
  • Session 7 中,select * from tlock1; 自动加上 MDL 读锁
  • Session 8 中,alter table tlock1 add comments varchar(10); 会阻塞,因为 MDL 读锁与 MDL 写锁之间是冲突的
  • Session 9 中,select * from tlock1; 正常运行
  • Session 9 中,insert into tlock1 values(20,'ki'); 正常运行,因为 MDL 写锁之间不冲突
  • Session 9 中,show processlist\G; 的输出如下:

    show processlist\G;
    
    *************************** 1. row ***************************
       Id: 5
     User: event_scheduler
     Host: localhost
       db: NULL
    Command: Daemon
     Time: 3090
    State: Waiting on empty queue
     Info: NULL
    *************************** 2. row ***************************
       Id: 8
     User: root
     Host: localhost
       db: locks
    Command: Sleep
     Time: 106
    State:
     Info: NULL
    *************************** 3. row ***************************
       Id: 9
     User: root
     Host: localhost
       db: locks
    Command: Query
     Time: 4
    State: Waiting for table metadata lock
     Info: alter table tlock1 add comments varchar(10)  ←← 这是 Session 8 中的阻塞
    *************************** 4. row ***************************
       Id: 20
     User: root
     Host: localhost
       db: locks
    Command: Query
     Time: 0
    State: init
     Info: show processlist
    4 rows in set, 1 warning (0.00 sec)
Avatar photo

关于 陸風睿

GNU/Linux 从业者、开源爱好者、技术钻研者,撰写文档既是兴趣也是工作内容之一。Q - "281957576";WeChat - "jiulongxiaotianci",Github - https://github.com/jimcat8
用一杯咖啡支持我们,我们的每一篇[文档]都经过实际操作和精心打磨,而不是简单地从网上复制粘贴。期间投入了大量心血,只为能够真正帮助到您。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇