Carry の Blog Carry の Blog
首页
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • Python
  • Shell
  • MySQL8-SOP手册
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Carry の Blog

好记性不如烂键盘
首页
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • Python
  • Shell
  • MySQL8-SOP手册
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • MySQL8-SOP

  • MySQL实战45讲学习笔记

    • MySQL45讲学习笔记
    • MySQL基础架构
    • MySQL日志系统 (Redo Log 与 Binlog)
    • MySQL字符串字段索引优化
    • MySQL索引原理与优化
    • MySQL锁机制详解
    • MySQL事务与MVCC机制
    • MySQL普通索引与唯一索引的选择
    • MySQL优化器如何选择索引
    • MySQL抖动刷脏页
    • 表空间管理与回收
    • count函数详解
    • 日志索引
    • orderby工作原理
    • 随机排序实现
    • SQL性能差异函数与转换
    • 慢查询分析锁与版本
    • 幻读与间隙锁
    • 加锁规则分析
      • 1. 加锁基础概念
        • 1.1 锁的类型
        • 1.2 锁的粒度
      • 2. 加锁规则详解
        • 2.1 基本加锁原则
        • 2.2 等值查询加锁规则
        • 2.3 范围查询加锁规则
      • 3. 加锁优化策略
        • 3.1 索引优化对加锁的影响
        • 3.2 加锁顺序优化
        • 3.3 减少锁持有时间
      • 4. 加锁场景分析
        • 4.1 插入操作加锁
        • 4.2 更新操作加锁
        • 4.3 删除操作加锁
      • 5. 加锁监控与诊断
        • 5.1 锁等待监控
        • 5.2 锁状态监控
        • 5.3 死锁检测
      • 6. 加锁优化技巧
        • 6.1 使用合适的索引
        • 6.2 优化查询语句
        • 6.3 事务设计优化
      • 7. 常见加锁问题及解决方案
        • 7.1 死锁问题
        • 7.2 锁等待时间过长
        • 7.3 大事务锁竞争
      • 8. 加锁最佳实践
        • 8.1 设计阶段考虑
        • 8.2 查询优化建议
        • 8.3 监控和维护
      • 9. 总结
    • 应急性能优化方法
    • 数据持久化保证
    • 主备一致性原理
    • 高可用架构与切换
    • 备库延迟分析与优化
    • 主备切换GTID
    • 读写分离实践与问题
    • 数据库健康检查
    • 锁与死锁
    • 数据误删恢复
    • Kill命令详解
    • 查询与内存使用分析
    • Join原理与选择
    • 临时表原理与应用
    • 内部临时表详解
    • Memory引擎详解
    • 自增ID详解
    • Insert加锁分析
    • 表复制方法比较
    • Grant与权限管理
    • 分区表详解
    • SQL语句中的Join问题
    • 自增ID用尽问题
  • 专题系列
  • MySQL实战45讲学习笔记
Carry の Blog
2024-07-27
目录

加锁规则分析

# 加锁规则分析

在MySQL的InnoDB存储引擎中,加锁机制是实现并发控制和事务隔离的核心技术。深入理解加锁规则对于数据库性能优化和故障排查至关重要。本文将详细分析MySQL中的加锁规则和机制。

# 1. 加锁基础概念

# 1.1 锁的类型

-- InnoDB中的主要锁类型:
-- 1. 行级锁(Row Lock)
--    - 共享锁(S Lock)
--    - 排他锁(X Lock)

-- 2. 间隙锁(Gap Lock)
-- 3. Next-Key Lock(记录锁 + 间隙锁)
-- 4. 插入意向锁(Insert Intention Lock)

-- 查看锁相关状态
SHOW STATUS LIKE 'Innodb_row_lock%';
1
2
3
4
5
6
7
8
9
10
11

# 1.2 锁的粒度

-- 锁的粒度层次:
-- 1. 表级锁(Table Lock)- MyISAM引擎
-- 2. 行级锁(Row Lock)- InnoDB引擎
-- 3. 页面锁(Page Lock)- InnoDB内部使用

-- InnoDB行锁示例
SELECT * FROM users WHERE id = 10 FOR UPDATE;
-- 会对id=10的记录加排他锁

SELECT * FROM users WHERE id = 10 LOCK IN SHARE MODE;
-- 会对id=10的记录加共享锁
1
2
3
4
5
6
7
8
9
10
11

# 2. 加锁规则详解

# 2.1 基本加锁原则

-- 加锁的基本原则:
-- 1. 加锁的基本单位是Next-Key Lock(记录锁 + 间隙锁)
-- 2. 查找过程中访问到的对象才会加锁
-- 3. 事务提交或回滚时释放锁

-- 示例:基本加锁过程
-- 假设表结构:
CREATE TABLE test_table (
    id INT PRIMARY KEY,
    value INT,
    INDEX idx_value (value)
);

-- 查询时加锁
SELECT * FROM test_table WHERE id = 10 FOR UPDATE;
-- 实际加锁:记录锁 + 间隙锁

-- 多列查询加锁
SELECT * FROM test_table WHERE value = 100 FOR UPDATE;
-- 根据索引情况加锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 2.2 等值查询加锁规则

-- 等值查询加锁规则:
-- 1. 唯一索引等值查询:退化为行锁
-- 2. 普通索引等值查询:可能加间隙锁

-- 唯一索引等值查询(行锁)
CREATE TABLE unique_table (
    id INT PRIMARY KEY,
    name VARCHAR(50)
);

SELECT * FROM unique_table WHERE id = 10 FOR UPDATE;
-- 只加行锁,不加间隙锁

-- 普通索引等值查询(可能加间隙锁)
CREATE TABLE normal_table (
    id INT,
    name VARCHAR(50),
    INDEX idx_name (name)
);

SELECT * FROM normal_table WHERE name = 'John' FOR UPDATE;
-- 可能加记录锁 + 间隙锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 2.3 范围查询加锁规则

-- 范围查询加锁规则:
-- 1. 使用范围查询时,会加间隙锁
-- 2. 范围锁的边界需要考虑索引结构

-- 范围查询示例
SELECT * FROM test_table WHERE value BETWEEN 100 AND 200 FOR UPDATE;
-- 加锁范围:(100, 200] 间隙锁 + 记录锁

-- 不同范围查询的加锁情况
-- 等值查询
SELECT * FROM test_table WHERE value = 100 FOR UPDATE;
-- 加锁:记录锁 + 间隙锁(如果值存在)

-- 范围查询
SELECT * FROM test_table WHERE value > 100 FOR UPDATE;
-- 加锁:记录锁 + 间隙锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 3. 加锁优化策略

# 3.1 索引优化对加锁的影响

-- 索引优化对加锁的影响:
-- 1. 合理的索引可以减少锁的范围
-- 2. 覆盖索引可以减少回表,减少锁竞争

-- 创建覆盖索引示例
CREATE TABLE cover_table (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    age INT,
    INDEX idx_cover (age, name)
);

-- 使用覆盖索引的查询
SELECT age, name FROM cover_table WHERE age = 25;
-- 不需要回表,减少锁竞争

-- 优化前:需要回表的查询
SELECT * FROM cover_table WHERE age = 25;
-- 可能需要加更多的锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3.2 加锁顺序优化

-- 加锁顺序优化:
-- 1. 保持一致的加锁顺序
-- 2. 避免死锁的发生

-- 死锁示例演示
-- 事务A
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 暂停...

-- 事务B
BEGIN;
SELECT * FROM users WHERE id = 2 FOR UPDATE;
SELECT * FROM users WHERE id = 1 FOR UPDATE;  -- 可能死锁

-- 正确的加锁顺序
-- 事务A和B都应该按相同顺序加锁
BEGIN;
SELECT * FROM users WHERE id IN (1, 2) FOR UPDATE;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3.3 减少锁持有时间

-- 减少锁持有时间的策略:
-- 1. 尽量缩短事务时间
-- 2. 及时提交事务
-- 3. 合理使用LIMIT

-- 优化示例
-- 不好的做法:长事务
BEGIN;
SELECT * FROM large_table WHERE condition FOR UPDATE;
-- 处理大量数据...
COMMIT;

-- 好的做法:短事务 + 分批处理
BEGIN;
SELECT * FROM large_table WHERE condition LIMIT 100 FOR UPDATE;
-- 处理100条数据...
COMMIT;

-- 下一批
BEGIN;
SELECT * FROM large_table WHERE condition LIMIT 100 OFFSET 100 FOR UPDATE;
-- 处理下100条数据...
COMMIT;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 4. 加锁场景分析

# 4.1 插入操作加锁

-- 插入操作加锁:
-- 1. 插入意向锁(Insert Intention Lock)
-- 2. 用于避免插入冲突

-- 插入意向锁示例
-- 事务A插入数据
INSERT INTO test_table VALUES (100, 'value100');

-- 事务B同时插入数据
INSERT INTO test_table VALUES (101, 'value101');

-- 两个事务会使用插入意向锁,不会互相阻塞
1
2
3
4
5
6
7
8
9
10
11
12

# 4.2 更新操作加锁

-- 更新操作加锁:
-- 1. 对旧记录加锁(记录锁)
-- 2. 对新记录加锁(记录锁)
-- 3. 可能需要加间隙锁

-- 更新操作示例
UPDATE test_table SET value = 'new_value' WHERE id = 10;
-- 对id=10的记录加排他锁
-- 如果涉及范围更新,可能加间隙锁

-- 使用索引的更新
UPDATE test_table SET value = 'new_value' WHERE value = 'old_value';
-- 根据索引情况加锁
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4.3 删除操作加锁

-- 删除操作加锁:
-- 1. 对要删除的记录加锁
-- 2. 可能需要加间隙锁

-- 删除操作示例
DELETE FROM test_table WHERE id = 10;
-- 对id=10的记录加排他锁

-- 范围删除
DELETE FROM test_table WHERE value > 100;
-- 对符合条件的记录加锁,可能加间隙锁
1
2
3
4
5
6
7
8
9
10
11

# 5. 加锁监控与诊断

# 5.1 锁等待监控

-- 监控锁等待情况
SELECT 
    r.trx_id waiting_trx_id,
    r.trx_mysql_thread_id waiting_thread,
    r.trx_query waiting_query,
    b.trx_id blocking_trx_id,
    b.trx_mysql_thread_id blocking_thread,
    b.trx_query 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;

-- 查看当前锁信息
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 5.2 锁状态监控

-- 锁状态监控
SHOW STATUS LIKE 'Innodb_row_lock%';

-- 关键锁状态指标:
-- Innodb_row_lock_current_waits: 当前等待的行锁数
-- Innodb_row_lock_time: 锁等待总时间(毫秒)
-- Innodb_row_lock_time_avg: 平均锁等待时间
-- Innodb_row_lock_waits: 锁等待次数
1
2
3
4
5
6
7
8

# 5.3 死锁检测

-- 死锁检测和分析
SHOW ENGINE INNODB STATUS\G

-- 查看死锁相关信息
-- 在InnoDB状态输出中查找:
-- LATEST DETECTED DEADLOCK
-- LATEST FOREIGN KEY ERROR
-- LATEST TRX WITH LOCK WAIT

-- 死锁预防策略
-- 1. 保持一致的加锁顺序
-- 2. 尽量减少事务范围
-- 3. 及时提交事务
1
2
3
4
5
6
7
8
9
10
11
12
13

# 6. 加锁优化技巧

# 6.1 使用合适的索引

-- 索引对加锁的影响
-- 1. 充分利用索引减少锁范围
-- 2. 避免全表扫描导致的锁竞争

-- 创建合适的索引
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    order_date DATE,
    amount DECIMAL(10,2),
    INDEX idx_user_date (user_id, order_date),
    INDEX idx_date_amount (order_date, amount)
);

-- 优化查询加锁
SELECT * FROM orders WHERE user_id = 123 AND order_date = '2024-01-01' FOR UPDATE;
-- 使用复合索引,减少锁范围

-- 避免全表扫描
SELECT * FROM orders WHERE amount > 1000 FOR UPDATE;
-- 如果amount没有索引,会锁住大量记录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 6.2 优化查询语句

-- 优化查询语句减少锁竞争
-- 1. 使用精确的WHERE条件
-- 2. 避免范围过大
-- 3. 使用LIMIT限制结果集

-- 优化前:可能锁住大量记录
SELECT * FROM orders WHERE user_id > 1000 FOR UPDATE;

-- 优化后:精确查询
SELECT * FROM orders WHERE user_id = 1001 FOR UPDATE;

-- 进一步优化:分批处理
SELECT * FROM orders WHERE user_id > 1000 LIMIT 100 FOR UPDATE;
1
2
3
4
5
6
7
8
9
10
11
12
13

# 6.3 事务设计优化

-- 事务设计优化
-- 1. 尽量缩短事务时间
-- 2. 合理划分事务范围
-- 3. 避免长事务

-- 长事务优化示例
-- 不好的做法
BEGIN;
SELECT * FROM large_table WHERE condition1 FOR UPDATE;
-- 处理大量数据...
SELECT * FROM another_table WHERE condition2 FOR UPDATE;
-- 处理大量数据...
COMMIT;

-- 好的做法
BEGIN;
SELECT * FROM large_table WHERE condition1 FOR UPDATE LIMIT 100;
-- 处理100条数据...
COMMIT;

BEGIN;
SELECT * FROM another_table WHERE condition2 FOR UPDATE LIMIT 100;
-- 处理100条数据...
COMMIT;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 7. 常见加锁问题及解决方案

# 7.1 死锁问题

-- 死锁问题分析和解决
-- 1. 死锁检测和分析
-- 2. 优化加锁顺序
-- 3. 减少事务范围

-- 死锁示例
-- 事务A
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 暂停...

-- 事务B
BEGIN;
SELECT * FROM users WHERE id = 2 FOR UPDATE;
SELECT * FROM users WHERE id = 1 FOR UPDATE;  -- 可能死锁

-- 解决方案:保持一致的加锁顺序
-- 事务A和B都按id从小到大加锁
BEGIN;
SELECT * FROM users WHERE id IN (1, 2) FOR UPDATE;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 7.2 锁等待时间过长

-- 锁等待时间过长问题
-- 1. 调整锁等待超时时间
-- 2. 优化查询和索引
-- 3. 减少事务持续时间

-- 调整锁等待超时
SET GLOBAL innodb_lock_wait_timeout = 50;  -- 50秒

-- 查看当前设置
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';

-- 监控锁等待
SELECT 
    VARIABLE_NAME,
    VARIABLE_VALUE
FROM information_schema.GLOBAL_STATUS 
WHERE VARIABLE_NAME LIKE 'Innodb_row_lock%';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 7.3 大事务锁竞争

-- 大事务锁竞争问题
-- 1. 分解大事务
-- 2. 使用批量处理
-- 3. 优化数据访问模式

-- 大事务优化
-- 不好的做法
BEGIN;
UPDATE large_table SET status = 'processed' WHERE batch_id = 123;
COMMIT;

-- 好的做法
BEGIN;
UPDATE large_table SET status = 'processed' WHERE batch_id = 123 LIMIT 1000;
COMMIT;

-- 循环处理
WHILE rows_affected > 0 DO
    BEGIN;
    UPDATE large_table SET status = 'processed' WHERE batch_id = 123 LIMIT 1000;
    SET rows_affected = ROW_COUNT();
    COMMIT;
END WHILE;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 8. 加锁最佳实践

# 8.1 设计阶段考虑

-- 设计阶段的加锁考虑:
-- 1. 合理设计表结构和索引
-- 2. 考虑并发访问模式
-- 3. 预估数据访问模式

-- 优化的表结构设计
CREATE TABLE optimized_table (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    status VARCHAR(20),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_user_status (user_id, status),
    INDEX idx_created_at (created_at)
);

-- 优化的查询模式
SELECT * FROM optimized_table WHERE user_id = 123 AND status = 'active' FOR UPDATE;
-- 使用复合索引,减少锁范围
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 8.2 查询优化建议

-- 查询优化建议:
-- 1. 使用EXPLAIN分析查询计划
-- 2. 确保使用合适的索引
-- 3. 避免不必要的锁

-- 使用EXPLAIN分析
EXPLAIN SELECT * FROM test_table WHERE id = 10 FOR UPDATE;

-- 查看是否使用了索引
-- 如果使用了索引,加锁范围会更精确
1
2
3
4
5
6
7
8
9
10

# 8.3 监控和维护

-- 建立加锁监控体系
CREATE TABLE lock_monitor (
    id INT AUTO_INCREMENT PRIMARY KEY,
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    waiting_threads INT,
    blocking_threads INT,
    lock_waits INT,
    lock_wait_time BIGINT
);

-- 定期记录锁状态
INSERT INTO lock_monitor (
    waiting_threads,
    blocking_threads,
    lock_waits,
    lock_wait_time
) VALUES (
    (SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME = 'Innodb_row_lock_current_waits'),
    (SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME = 'Innodb_row_lock_waits'),
    (SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME = 'Innodb_row_lock_waits'),
    (SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME = 'Innodb_row_lock_time')
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 9. 总结

加锁机制是MySQL数据库并发控制的核心,深入理解加锁规则对于数据库性能优化和故障排查具有重要意义。通过合理设计索引、优化查询语句、规范事务处理等方式,可以有效提升系统的并发性能和响应速度。

关键要点包括:

  1. 理解锁类型:掌握行锁、间隙锁、Next-Key锁等不同类型
  2. 掌握加锁规则:熟悉等值查询、范围查询的加锁机制
  3. 优化索引设计:合理索引可以减少锁竞争和锁范围
  4. 监控锁状态:建立完善的锁监控体系
  5. 预防死锁:保持一致的加锁顺序,减少事务持续时间

通过系统性的加锁分析和优化,可以显著提升MySQL数据库的并发处理能力和整体性能表现。

#MySQL#加锁机制#锁分析#事务处理
上次更新: 3/4/2026

← 幻读与间隙锁 应急性能优化方法→

最近更新
01
表空间管理与回收
03-04
02
MySQL抖动刷脏页
03-04
03
count函数详解
03-04
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式