MySQL教程 / 第 90 节

第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;

兼容性矩阵:

ISIXSX
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的锁
-- 死锁!

死锁的四个必要条件:

  1. 互斥:资源不能共享
  2. 持有并等待:持有资源的同时等待其他资源
  3. 不可剥夺:资源不能被强制释放
  4. 循环等待:形成环形等待链

9.6.2 死锁检测和处理

InnoDB的死锁处理:

  1. 自动检测死锁
  2. 选择一个事务回滚(回滚代价小的)
  3. 返回错误: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锁)
  • 记录锁、间隙锁、临键锁 ⭐⭐⭐⭐⭐
  • 死锁的产生和避免 ⭐⭐⭐⭐⭐
  • ✅ 查看和分析锁信息
  • ✅ 实战案例(秒杀、转账)

重点掌握:

  1. InnoDB默认使用行锁,锁是加在索引上的
  2. 没有索引会升级为表锁
  3. Next-Key Lock = 记录锁 + 间隙锁
  4. 间隙锁用于解决幻读问题
  5. 死锁的四个必要条件
  6. 避免死锁:统一加锁顺序、使用索引、缩短事务

面试重点:

  • 行锁的实现原理
  • 间隙锁和临键锁的区别
  • 如何避免死锁
  • 悲观锁和乐观锁的使用场景
  • 如何分析死锁日志

下一章预告: MySQL架构详解


练习题

  1. 演示共享锁和排他锁的兼容性
  2. 分析间隙锁的锁定范围
  3. 模拟死锁并查看死锁日志
  4. 实现一个防止超卖的秒杀系统
  5. 对比悲观锁和乐观锁的性能

继续学习: 第10章:MySQL架构