在数据库并发环境中,多个事务可能同时访问同一份数据。如果没有合理的并发控制机制,就会出现数据不一致、脏读、幻读等问题。
为了解决这些问题,mysql 在存储引擎层提供了一套完整的 锁机制(lock mechanism),用于控制多个事务对数据的访问顺序。
从整体结构上看,mysql 的锁可以按照 锁的粒度 分为两大类:
mysql 锁
│
├── 表级锁
│ ├── 表锁(lock tables)
│ ├── 元数据锁(mdl lock)
│ └── 意向锁(intention lock)
│
└── 行级锁(innodb)
├── 记录锁(record lock)
├── 间隙锁(gap lock)
└── 临键锁(next-key lock)下面分别进行介绍。
一、表级锁(table lock)
表级锁是 作用在整张表上的锁,也就是说,一旦某个事务对表加锁,其他事务访问该表就会受到限制。
表级锁的特点:
- 加锁速度快
- 实现简单
- 锁粒度大
- 并发能力较低
mysql 中表级锁主要包括三种:
- 显式表锁
- 元数据锁(mdl)
- 意向锁
1 显式表锁(lock tables)
显式表锁是通过 sql 语句手动对表加锁:
lock tables table_name read; lock tables table_name write;
释放锁:
unlock tables;
1.1 read 锁(读锁)
lock tables user read;
含义:
- 当前事务可以读取表数据
- 不允许修改数据
- 其他事务可以继续获取 read 锁
- 其他事务不能获取 write 锁
也就是说:
读读可以并发,读写互斥。
1.2 write 锁(写锁)
lock tables user write;
含义:
- 当前事务可以读写表
- 其他事务既不能读也不能写
- 直到执行
unlock tables
因此 write 锁是 排他锁。
2 元数据锁(mdl lock)
mdl(metadata lock)是 mysql 自动维护的一种锁,不需要手动加锁。
它的作用是:
防止在执行 dml 操作时表结构被修改。
例如:
执行查询:
select * from user;
mysql 会自动添加:mdl 读锁
如果执行:
alter table user add column age int;
mysql 会添加:mdl 写锁
mdl 写锁会阻塞其他事务的读写操作,从而避免 ddl 与 dml 冲突。
在生产环境中,如果一个事务长时间未提交,可能会导致:
alter table 一直等待
这往往就是 mdl 锁导致的阻塞问题。
3 意向锁(intention lock)
意向锁是 innodb 在 表级别维护的一种锁标记,用于配合行锁使用。
它的核心作用是:
在事务对某一行数据加锁之前,先在表级别声明加锁意图。
例如:
select * from user where id = 1 for update;
innodb 会做两件事:
1️⃣ 在表上加 意向排他锁(ix)
2️⃣ 在对应记录上加 行排他锁(x锁)
3.1 意向锁分类
意向锁(intention lock)不是只有 ix 一种,而是一个锁的类别,其中包括两种类型:
- is(intention shared lock)意向共享锁
- ix(intention exclusive lock)意向排他锁
也就是说:
| 锁类型 | 含义 |
|---|---|
| is | 表示事务准备在某些行上加 共享锁(s锁) |
| ix | 表示事务准备在某些行上加 排他锁(x锁) |
举个例子
1️⃣ 查询加共享锁
select * from user where id = 1 lock in share mode;
innodb 会:
表:is 行:s
2️⃣ 更新数据
update user set age = 20 where id = 1;
innodb 会:
表:ix 行:x
3.2 为什么需要意向锁?
假设没有意向锁:
当一个事务想给整张表加锁时,就必须扫描整张表,查看是否存在行锁,这样效率非常低。
而有了意向锁之后:
只需要检查表级锁即可判断是否存在行锁。
因此:
意向锁的本质是 用于快速判断表中是否存在行锁的标记。
一句话总结:意向锁(intention lock)是 innodb 在表级维护的一种锁标记,用于表示事务即将在某些行上加锁,其主要类型包括意向共享锁(is)和意向排他锁(ix)。
二、行级锁(row lock)
行级锁是 innodb 存储引擎支持的一种锁机制。
相比表锁,它的特点是:
- 锁粒度更小
- 并发能力更强
- 更适合高并发系统
行锁锁定的是 一条具体记录,而不是整张表。
1 记录锁(record lock)
记录锁是最基本的行锁,它只锁定某一条记录。
例如:
select * from user where id = 1 for update;
只会锁住:
id = 1 这一条记录
其他记录仍然可以被访问。
记录锁通常分为:
- s 锁(共享锁)
- x 锁(排他锁)
它们满足:
读读可以并发 读写互斥 写写互斥
2 间隙锁(gap lock)
间隙锁锁住的不是具体记录,而是 索引之间的空隙。
例如:
select * from user where score > 100 for update;
如果当前数据为:
100 150 200
间隙锁可能锁住:
(100 , +∞)
这样其他事务就 不能插入 score=120 的数据。
间隙锁的主要作用是:
防止幻读。
需要注意:
间隙锁 不会锁住已有记录,只锁住区间。
3 临键锁(next-key lock)
next-key lock 是:
record lock + gap lock
也就是说:
既锁住记录,又锁住间隙。
例如:
select * from user where age between 20 and 30 for update;
假设表中存在:
20 25 30
innodb 可能锁住:
(-∞,20] (20,25] (25,30] (30,+∞)
这样不仅锁住已有记录,还锁住记录之间的间隙。
因此:
next-key lock 可以彻底防止幻读。
到此这篇关于mysql中锁的分类与加锁方式小结的文章就介绍到这了,更多相关mysql锁的分类与加锁内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论