第09章:锁机制 ⭐⭐⭐⭐⭐
第09章:锁机制 ⭐⭐⭐⭐⭐
锁是数据库并发控制的核心机制,理解锁机制是成为MySQL专家的必备技能
9.1 锁概述
9.1.1 为什么需要锁
问题: 多个事务同时修改同一数据,会导致数据不一致。
示例:
-- 初始:stock = 10
-- 事务A 事务B
SELECT stock FROM products WHERE id=1; -- 10
SELECT stock FROM products WHERE id=1; -- 10
UPDATE products SET stock=9 WHERE id=1;
UPDATE products SET stock=9 WHERE id=1;
-- 结果:stock=9(错误!应该是8)
解决方案: 使用锁机制,保证同一时间只有一个事务能修改数据。
9.1.2 锁的分类
按锁的粒度分类:
- 表锁(Table Lock):锁定整张表
- 行锁(Row Lock):锁定某一行
- 页锁(Page Lock):锁定一页数据(BDB引擎)
按锁的类型分类:
- 共享锁(S锁,Shared Lock):读锁,多个事务可以同时持有
- 排他锁(X锁,Exclusive Lock):写锁,只有一个事务可以持有
按锁的模式分类:
- 乐观锁:不真正加锁,通过版本号控制
- 悲观锁:真正加锁,阻塞其他事务
InnoDB特有的锁:
- 意向锁(Intention Lock):表级锁,辅助行锁
- 记录锁(Record Lock):锁定单个行记录
- 间隙锁(Gap Lock):锁定索引记录之间的间隙
- 临键锁(Next-Key Lock):记录锁 + 间隙锁
9.2 表锁
9.2.1 表锁概述
特点:
- 锁定整张表
- 开销小,加锁快
- 不会出现死锁
- 锁粒度大,并发度低
- MyISAM默认使用表锁
9.2.2 表锁的使用
加锁:
-- 加读锁(共享锁)
LOCK TABLES users READ;
-- 加写锁(排他锁)
LOCK TABLES users WRITE;
-- 同时锁定多个表
LOCK TABLES users READ, orders WRITE;
释放锁:
-- 释放所有表锁
UNLOCK TABLES;
演示:
-- 终端1
LOCK TABLES users READ;
SELECT * FROM users; -- 可以读
UPDATE users SET age = 30 WHERE id = 1; -- 报错:表被锁定
UNLOCK TABLES;
-- 终端1
LOCK TABLES users WRITE;
SELECT * FROM users; -- 可以读
UPDATE users SET age = 30 WHERE id = 1; -- 可以写
-- 终端2
SELECT * FROM users; -- 阻塞,等待终端1释放锁
9.2.3 表锁兼容性
| 当前锁 | 请求读锁 | 请求写锁 |
|---|---|---|
| 无锁 | ✅ 允许 | ✅ 允许 |
| 读锁 | ✅ 允许 | ❌ 阻塞 |
| 写锁 | ❌ 阻塞 | ❌ 阻塞 |
9.2.4 查看表锁
-- 查看表锁状态
SHOW OPEN TABLES WHERE In_use > 0;
-- 查看表锁等待情况
SHOW STATUS LIKE 'Table_locks%';
-- Table_locks_immediate:立即获得锁的次数
-- Table_locks_waited:需要等待的次数
9.3 行锁
9.3.1 行锁概述
特点:
- 锁定单行或多行
- 开销大,加锁慢
- 可能出现死锁
- 锁粒度小,并发度高
- InnoDB默认使用行锁
重要: 行锁是针对索引加的锁,如果没有索引,会升级为表锁!
9.3.2 共享锁(S锁)
加锁方式:
-- 方法1:SELECT ... LOCK IN SHARE MODE(MySQL 5.7)
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- 方法2:SELECT ... FOR SHARE(MySQL 8.0)
SELECT * FROM users WHERE id = 1 FOR SHARE;
特点:
- 允许其他事务读取(加S锁)
- 阻塞其他事务写入(加X锁)
演示:
-- 终端1(事务A)
START TRANSACTION;
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE; -- 加S锁
-- 终端2(事务B)
START TRANSACTION;
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE; -- 成功(S锁兼容)
UPDATE users SET age = 30 WHERE id = 1; -- 阻塞(等待S锁释放)
-- 终端1(事务A)
COMMIT; -- 释放S锁
-- 终端2(事务B)
-- UPDATE执行成功
COMMIT;
9.3.3 排他锁(X锁)
加锁方式:
-- 方法1:SELECT ... FOR UPDATE
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 方法2:UPDATE、DELETE、INSERT自动加X锁
UPDATE users SET age = 30 WHERE id = 1;
DELETE FROM users WHERE id = 1;
INSERT INTO users (name) VALUES ('test');
特点:
- 阻塞其他事务读取(加S锁)
- 阻塞其他事务写入(加X锁)
演示:
-- 终端1(事务A)
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 加X锁
-- 终端2(事务B)
START TRANSACTION;
SELECT * FROM users WHERE id = 1; -- 成功(快照读,不加锁)
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE; -- 阻塞(等待X锁释放)
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 阻塞(等待X锁释放)
UPDATE users SET age = 30 WHERE id = 1; -- 阻塞(等待X锁释放)
-- 终端1(事务A)
COMMIT; -- 释放X锁
-- 终端2(事务B)
-- 所有阻塞的操作执行成功
COMMIT;
9.3.4 锁兼容性矩阵
| S锁 | X锁 | |
|---|---|---|
| S锁 | ✅ 兼容 | ❌ 冲突 |
| X锁 | ❌ 冲突 | ❌ 冲突 |
9.4 意向锁
9.4.1 意向锁概述
问题: 如果要加表锁,需要检查是否有行锁,效率低。
解决方案: 意向锁(表级锁),表示事务想要在表的某些行上加锁。
类型:
- 意向共享锁(IS锁):表示事务想要在某些行上加S锁
- 意向排他锁(IX锁):表示事务想要在某些行上加X锁
9.4.2 意向锁的作用
自动加锁:
-- 加行级S锁时,自动加表级IS锁
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- 加行级X锁时,自动加表级IX锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;
兼容性矩阵:
| IS | IX | S | X | |
|---|---|---|---|---|
| IS | ✅ | ✅ | ✅ | ❌ |
| IX | ✅ | ✅ | ❌ | ❌ |
| S | ✅ | ❌ | ✅ | ❌ |
| X | ❌ | ❌ | ❌ | ❌ |
作用:
- 快速判断表是否有行锁
- 提高加表锁的效率
9.5 记录锁、间隙锁、临键锁 ⭐⭐⭐⭐⭐
9.5.1 记录锁(Record Lock)
定义: 锁定单个索引记录。
示例:
-- 锁定id=1的记录
SELECT * FROM users WHERE id = 1 FOR UPDATE;
特点:
- 只锁定匹配的索引记录
- 不锁定间隙
9.5.2 间隙锁(Gap Lock)
定义: 锁定索引记录之间的间隙,防止其他事务插入数据。
作用: 解决幻读问题。
示例:
-- 假设表中有id: 1, 5, 10
-- 锁定(5, 10)之间的间隙
SELECT * FROM users WHERE id > 5 AND id < 10 FOR UPDATE;
-- 此时其他事务无法插入id=6, 7, 8, 9的记录
INSERT INTO users (id, name) VALUES (7, 'test'); -- 阻塞
间隙锁的范围:
-- 假设表中有id: 1, 5, 10, 15
-- 查询id=7(不存在)
SELECT * FROM users WHERE id = 7 FOR UPDATE;
-- 锁定间隙:(5, 10)
-- 查询id>5
SELECT * FROM users WHERE id > 5 FOR UPDATE;
-- 锁定间隙:(5, 10), (10, 15), (15, +∞)
-- 查询id<10
SELECT * FROM users WHERE id < 10 FOR UPDATE;
-- 锁定间隙:(-∞, 1), (1, 5), (5, 10)
9.5.3 临键锁(Next-Key Lock)
定义: 记录锁 + 间隙锁,锁定索引记录及其前面的间隙。
格式: (左开右闭区间]
示例:
-- 假设表中有id: 1, 5, 10, 15
-- 查询id=5
SELECT * FROM users WHERE id = 5 FOR UPDATE;
-- 临键锁:(1, 5]
-- 包含:间隙锁(1, 5) + 记录锁[5]
-- 查询id>=5 AND id<15
SELECT * FROM users WHERE id >= 5 AND id < 15 FOR UPDATE;
-- 临键锁:(1, 5], (5, 10], (10, 15)
InnoDB默认使用Next-Key Lock(REPEATABLE READ)
9.5.4 锁定范围详解
准备测试数据:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
age INT,
KEY idx_age (age)
) ENGINE=InnoDB;
INSERT INTO users VALUES
(1, 'user1', 10),
(5, 'user5', 20),
(10, 'user10', 30),
(15, 'user15', 40);
场景1:等值查询(记录存在)
-- 查询id=5
SELECT * FROM users WHERE id = 5 FOR UPDATE;
-- 锁定:记录锁[5](主键等值查询,退化为记录锁)
场景2:等值查询(记录不存在)
-- 查询id=7(不存在)
SELECT * FROM users WHERE id = 7 FOR UPDATE;
-- 锁定:间隙锁(5, 10)
场景3:范围查询
-- 查询id>5 AND id<=10
SELECT * FROM users WHERE id > 5 AND id <= 10 FOR UPDATE;
-- 锁定:
-- 间隙锁(5, 10)
-- 记录锁[10]
-- 间隙锁(10, 15)
-- 合并:临键锁(5, 15)
场景4:非唯一索引
-- 查询age=20
SELECT * FROM users WHERE age = 20 FOR UPDATE;
-- 锁定:
-- 临键锁(10, 20](age索引)
-- 间隙锁(20, 30)(age索引)
-- 记录锁[5](主键索引)
场景5:无索引(全表扫描)
-- name没有索引
SELECT * FROM users WHERE name = 'user5' FOR UPDATE;
-- 锁定:所有记录 + 所有间隙(相当于表锁)
9.6 死锁 ⭐⭐⭐⭐⭐
9.6.1 什么是死锁
死锁(Deadlock):两个或多个事务互相等待对方释放锁,导致永久阻塞。
经典示例:
-- 事务A 事务B
START TRANSACTION; START TRANSACTION;
UPDATE users SET age=30 WHERE id=1; -- 锁定id=1
UPDATE users SET age=40 WHERE id=2; -- 锁定id=2
UPDATE users SET age=30 WHERE id=2; -- 等待id=2的锁
UPDATE users SET age=40 WHERE id=1; -- 等待id=1的锁
-- 死锁!
死锁的四个必要条件:
- 互斥:资源不能共享
- 持有并等待:持有资源的同时等待其他资源
- 不可剥夺:资源不能被强制释放
- 循环等待:形成环形等待链
9.6.2 死锁检测和处理
InnoDB的死锁处理:
- 自动检测死锁
- 选择一个事务回滚(回滚代价小的)
- 返回错误:
ERROR 1213: Deadlock found when trying to get lock
查看死锁日志:
-- 查看最近一次死锁信息
SHOW ENGINE INNODB STATUS\G
-- 在输出中查找 LATEST DETECTED DEADLOCK 部分
死锁日志示例:
------------------------
LATEST DETECTED DEADLOCK
------------------------
2024-11-11 10:30:00
*** (1) TRANSACTION:
TRANSACTION 12345, ACTIVE 5 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 10, OS thread handle 140123456789, query id 100 localhost root updating
UPDATE users SET age = 30 WHERE id = 2
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 100 page no 3 n bits 72 index PRIMARY of table `test`.`users`
trx id 12345 lock_mode X locks rec but not gap waiting
*** (2) TRANSACTION:
TRANSACTION 12346, ACTIVE 3 sec starting index read
mysql tables in use 1, locked 1
2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 11, OS thread handle 140123456790, query id 101 localhost root updating
UPDATE users SET age = 40 WHERE id = 1
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 100 page no 3 n bits 72 index PRIMARY of table `test`.`users`
trx id 12346 lock_mode X locks rec but not gap
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 100 page no 3 n bits 72 index PRIMARY of table `test`.`users`
trx id 12346 lock_mode X locks rec but not gap waiting
*** WE ROLL BACK TRANSACTION (1)
9.6.3 死锁案例分析
案例1:更新顺序不一致
-- 事务A
START TRANSACTION;
UPDATE users SET age = 30 WHERE id = 1;
UPDATE users SET age = 30 WHERE id = 2;
COMMIT;
-- 事务B
START TRANSACTION;
UPDATE users SET age = 40 WHERE id = 2; -- 与事务A顺序相反
UPDATE users SET age = 40 WHERE id = 1;
COMMIT;
-- 解决方案:统一更新顺序
-- 都按照id从小到大的顺序更新
案例2:间隙锁导致的死锁
-- 假设表中有id: 1, 5, 10
-- 事务A
START TRANSACTION;
SELECT * FROM users WHERE id = 3 FOR UPDATE; -- 锁定间隙(1, 5)
-- 事务B
START TRANSACTION;
SELECT * FROM users WHERE id = 4 FOR UPDATE; -- 锁定间隙(1, 5),等待
-- 事务A
INSERT INTO users (id, name) VALUES (4, 'test'); -- 等待事务B
-- 死锁!
案例3:索引失效导致的死锁
-- 事务A
START TRANSACTION;
UPDATE users SET age = 30 WHERE name = 'user1'; -- name没有索引,锁全表
-- 事务B
START TRANSACTION;
UPDATE users SET age = 40 WHERE name = 'user2'; -- 等待全表锁
-- 解决方案:在name上创建索引
CREATE INDEX idx_name ON users(name);
9.6.4 避免死锁的方法
1. 统一加锁顺序
-- ❌ 错误:顺序不一致
-- 事务A:先锁1,再锁2
-- 事务B:先锁2,再锁1
-- ✅ 正确:统一顺序
-- 所有事务都按照id从小到大的顺序加锁
START TRANSACTION;
SELECT * FROM users WHERE id IN (1, 2, 5) ORDER BY id FOR UPDATE;
UPDATE ...
COMMIT;
2. 尽量使用索引
-- ❌ 错误:没有索引,锁全表
UPDATE users SET age = 30 WHERE name = 'user1';
-- ✅ 正确:有索引,只锁一行
CREATE INDEX idx_name ON users(name);
UPDATE users SET age = 30 WHERE name = 'user1';
3. 缩短事务时间
-- ❌ 错误:事务时间过长
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 执行复杂的业务逻辑(耗时)
UPDATE users SET age = 30 WHERE id = 1;
COMMIT;
-- ✅ 正确:缩短事务时间
-- 先执行业务逻辑
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET age = 30 WHERE id = 1;
COMMIT;
4. 降低隔离级别
-- REPEATABLE READ可能产生间隙锁死锁
SET SESSION transaction_isolation = 'READ-COMMITTED';
-- READ COMMITTED不使用间隙锁
5. 使用乐观锁
-- 悲观锁:可能死锁
START TRANSACTION;
SELECT * FROM products WHERE id = 1 FOR UPDATE;
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;
-- 乐观锁:不会死锁
START TRANSACTION;
SELECT stock, version FROM products WHERE id = 1;
UPDATE products SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = #{old_version};
COMMIT;
6. 设置锁等待超时
-- 查看锁等待超时时间(默认50秒)
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
-- 设置超时时间(秒)
SET innodb_lock_wait_timeout = 10;
9.7 查看锁信息
9.7.1 查看当前锁
MySQL 5.7:
-- 查看InnoDB锁等待
SELECT * FROM information_schema.INNODB_LOCKS;
-- 查看锁等待关系
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
-- 查看事务信息
SELECT * FROM information_schema.INNODB_TRX;
MySQL 8.0:
-- 查看数据锁
SELECT * FROM performance_schema.data_locks;
-- 查看锁等待
SELECT * FROM performance_schema.data_lock_waits;
-- 查看事务信息
SELECT * FROM information_schema.INNODB_TRX;
9.7.2 分析锁等待
-- 查看正在等待锁的事务
SELECT
r.trx_id AS waiting_trx_id,
r.trx_mysql_thread_id AS waiting_thread,
r.trx_query AS waiting_query,
b.trx_id AS blocking_trx_id,
b.trx_mysql_thread_id AS blocking_thread,
b.trx_query AS blocking_query
FROM information_schema.INNODB_LOCK_WAITS w
INNER JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;
9.7.3 杀死阻塞的事务
-- 查看所有连接
SHOW PROCESSLIST;
-- 杀死指定连接
KILL 123; -- 123是thread_id
9.8 实战案例
案例1:秒杀系统(悲观锁)
-- 创建商品表
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
stock INT NOT NULL DEFAULT 0
) ENGINE=InnoDB;
-- 插入测试数据
INSERT INTO products (name, stock) VALUES ('iPhone 15', 100);
-- 秒杀逻辑(悲观锁)
START TRANSACTION;
-- 查询库存并加锁
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
-- 检查库存
IF stock > 0 THEN
-- 减库存
UPDATE products SET stock = stock - 1 WHERE id = 1;
-- 创建订单
INSERT INTO orders (product_id, user_id, create_time)
VALUES (1, 100, NOW());
COMMIT;
SELECT '秒杀成功' AS message;
ELSE
ROLLBACK;
SELECT '库存不足' AS message;
END IF;
案例2:秒杀系统(乐观锁)
-- 创建商品表(带版本号)
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
stock INT NOT NULL DEFAULT 0,
version INT NOT NULL DEFAULT 0
) ENGINE=InnoDB;
-- 秒杀逻辑(乐观锁)
START TRANSACTION;
-- 查询库存和版本号
SELECT stock, version FROM products WHERE id = 1;
-- 假设:stock=10, version=5
-- 减库存(带版本号检查)
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 5 AND stock > 0;
-- 检查是否更新成功
IF ROW_COUNT() = 0 THEN
ROLLBACK;
SELECT '秒杀失败,请重试' AS message;
ELSE
-- 创建订单
INSERT INTO orders (product_id, user_id, create_time)
VALUES (1, 100, NOW());
COMMIT;
SELECT '秒杀成功' AS message;
END IF;
案例3:转账(防止死锁)
-- 创建账户表
CREATE TABLE accounts (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL UNIQUE,
balance DECIMAL(10, 2) NOT NULL DEFAULT 0
) ENGINE=InnoDB;
-- 转账逻辑(统一加锁顺序)
START TRANSACTION;
-- 按照user_id从小到大的顺序加锁
SET @from_user = 1;
SET @to_user = 2;
SET @amount = 100;
-- 确保加锁顺序一致
IF @from_user < @to_user THEN
SELECT balance FROM accounts WHERE user_id = @from_user FOR UPDATE;
SELECT balance FROM accounts WHERE user_id = @to_user FOR UPDATE;
ELSE
SELECT balance FROM accounts WHERE user_id = @to_user FOR UPDATE;
SELECT balance FROM accounts WHERE user_id = @from_user FOR UPDATE;
END IF;
-- 扣款
UPDATE accounts SET balance = balance - @amount
WHERE user_id = @from_user AND balance >= @amount;
IF ROW_COUNT() = 0 THEN
ROLLBACK;
SELECT '余额不足' AS message;
ELSE
-- 加款
UPDATE accounts SET balance = balance + @amount
WHERE user_id = @to_user;
COMMIT;
SELECT '转账成功' AS message;
END IF;
案例4:防止重复插入
-- 方法1:使用唯一索引
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE
) ENGINE=InnoDB;
INSERT INTO users (username) VALUES ('zhangsan');
-- 重复插入会报错
-- 方法2:使用INSERT IGNORE
INSERT IGNORE INTO users (username) VALUES ('zhangsan');
-- 不会报错,也不会插入
-- 方法3:使用ON DUPLICATE KEY UPDATE
INSERT INTO users (username) VALUES ('zhangsan')
ON DUPLICATE KEY UPDATE username = 'zhangsan';
-- 方法4:使用SELECT ... FOR UPDATE
START TRANSACTION;
SELECT id FROM users WHERE username = 'zhangsan' FOR UPDATE;
IF NOT FOUND THEN
INSERT INTO users (username) VALUES ('zhangsan');
END IF;
COMMIT;
9.9 性能优化建议
9.9.1 减少锁冲突
1. 使用合适的索引
-- ❌ 错误:没有索引,锁全表
UPDATE users SET age = 30 WHERE name = 'user1';
-- ✅ 正确:有索引,只锁一行
CREATE INDEX idx_name ON users(name);
UPDATE users SET age = 30 WHERE name = 'user1';
2. 缩小锁的范围
-- ❌ 错误:锁定范围过大
SELECT * FROM users WHERE age > 20 FOR UPDATE;
-- ✅ 正确:精确锁定
SELECT * FROM users WHERE id = 1 FOR UPDATE;
3. 缩短锁的时间
-- ❌ 错误:持锁时间过长
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 执行复杂的业务逻辑(耗时)
UPDATE users SET age = 30 WHERE id = 1;
COMMIT;
-- ✅ 正确:缩短持锁时间
-- 先执行业务逻辑
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET age = 30 WHERE id = 1;
COMMIT;
9.9.2 选择合适的锁策略
悲观锁 vs 乐观锁:
| 场景 | 推荐策略 |
|---|---|
| 冲突频繁 | 悲观锁 |
| 冲突较少 | 乐观锁 |
| 读多写少 | 乐观锁 |
| 写多读少 | 悲观锁 |
9.9.3 监控锁等待
-- 查看锁等待统计
SHOW STATUS LIKE 'Innodb_row_lock%';
-- Innodb_row_lock_current_waits:当前等待锁的数量
-- Innodb_row_lock_time:总等待时间(毫秒)
-- Innodb_row_lock_time_avg:平均等待时间(毫秒)
-- Innodb_row_lock_time_max:最大等待时间(毫秒)
-- Innodb_row_lock_waits:总等待次数
9.10 小结
本章学习了MySQL的锁机制:
- ✅ 锁的分类(表锁、行锁、意向锁)
- ✅ 共享锁(S锁)和排他锁(X锁)
- ✅ 记录锁、间隙锁、临键锁 ⭐⭐⭐⭐⭐
- ✅ 死锁的产生和避免 ⭐⭐⭐⭐⭐
- ✅ 查看和分析锁信息
- ✅ 实战案例(秒杀、转账)
重点掌握:
- InnoDB默认使用行锁,锁是加在索引上的
- 没有索引会升级为表锁
- Next-Key Lock = 记录锁 + 间隙锁
- 间隙锁用于解决幻读问题
- 死锁的四个必要条件
- 避免死锁:统一加锁顺序、使用索引、缩短事务
面试重点:
- 行锁的实现原理
- 间隙锁和临键锁的区别
- 如何避免死锁
- 悲观锁和乐观锁的使用场景
- 如何分析死锁日志
下一章预告: MySQL架构详解
练习题
- 演示共享锁和排他锁的兼容性
- 分析间隙锁的锁定范围
- 模拟死锁并查看死锁日志
- 实现一个防止超卖的秒杀系统
- 对比悲观锁和乐观锁的性能
继续学习: 第10章:MySQL架构