Skip to content

MySQL 锁等待分析

锁等待概述

版本差异

特性MySQL 5.6MySQL 5.7MySQL 8.0
锁等待监控视图INFORMATION_SCHEMA.INNODB_LOCKS/INNODB_LOCK_WAITSINFORMATION_SCHEMA.INNODB_LOCKS/INNODB_LOCK_WAITSINFORMATION_SCHEMA.INNODB_LOCKS/INNODB_LOCK_WAITS + performance_schema.data_locks
死锁检测支持支持支持(增强算法)
锁超时机制innodb_lock_wait_timeoutinnodb_lock_wait_timeoutinnodb_lock_wait_timeout
Online DDL支持部分操作支持大部分操作支持几乎所有操作
间隙锁行为严格严格可配置(innodb_strict_mode)
锁内存管理基于哈希表基于哈希表优化的内存管理
事务隔离级别默认值REPEATABLE READREPEATABLE READREPEATABLE READ
锁监控工具基本工具增强工具全面监控

锁的基本概念

概念描述
数据库用于控制并发访问的机制
锁等待一个事务等待另一个事务释放锁的状态
死锁两个或多个事务相互等待对方释放锁
锁超时等待锁超过指定时间后失败

锁的类型

按锁粒度分类

锁粒度描述优点缺点
全局锁锁定整个数据库实现简单并发度极低
表级锁锁定整张表开销小,加锁快并发度低
页级锁锁定数据页(16KB)平衡粒度开销较大
行级锁锁定单行数据并发度高开销大,加锁慢

按锁模式分类

锁模式描述兼容关系
共享锁(S)允许读取,阻止写入与S锁兼容,与X锁互斥
排他锁(X)允许写入,阻止其他所有锁与所有锁互斥
意向共享锁(IS)表级锁,表示事务打算加S锁与S、IS兼容,与X互斥
意向排他锁(IX)表级锁,表示事务打算加X锁与S、X、IS、IX兼容

InnoDB特有锁

锁类型描述场景
记录锁锁定单行记录精准条件查询
间隙锁锁定索引区间防止幻读
临键锁记录锁+间隙锁范围查询
插入意向锁插入操作时的间隙锁并发插入同一区间

锁等待的影响

  • 性能下降:锁等待导致查询延迟增加
  • 吞吐量降低:并发事务数减少
  • 死锁风险:锁等待可能升级为死锁
  • 应用超时:客户端可能因为等待锁而超时
  • 资源浪费:事务持有锁但不释放,占用连接资源

锁等待监控与分析

查看锁等待状态

使用 SHOW PROCESSLIST

sql
-- 查看当前所有进程
SHOW FULL PROCESSLIST;

-- 筛选出处于锁等待的进程
SHOW FULL PROCESSLIST WHERE Command = 'Query' AND State LIKE '%lock%';

输出解读

  • State 列显示锁等待状态,如 Waiting for table metadata lockWaiting for table level lockWaiting for row lock
  • Info 列显示正在执行的SQL语句
  • Time 列显示等待时间(秒)

使用 INFORMATION_SCHEMA 表

sql
-- 查看正在等待锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

-- 查看当前持有锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

-- 查看当前事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

-- 关联查询锁等待详情
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,
    t.lock_mode,
    t.lock_type,
    t.lock_table,
    t.lock_index,
    t.lock_space,
    t.lock_page,
    t.lock_rec
FROM
    INFORMATION_SCHEMA.INNODB_LOCK_WAITS w
JOIN
    INFORMATION_SCHEMA.INNODB_TRX b ON w.blocking_trx_id = b.trx_id
JOIN
    INFORMATION_SCHEMA.INNODB_TRX r ON w.requesting_trx_id = r.trx_id
JOIN
    INFORMATION_SCHEMA.INNODB_LOCKS t ON w.requested_lock_id = t.lock_id;

使用 SHOW ENGINE INNODB STATUS

sql
-- 查看InnoDB引擎状态,包含死锁和锁等待信息
SHOW ENGINE INNODB STATUS\G

关键信息解读

  • TRANSACTIONS 部分:显示当前事务
  • LATEST DETECTED DEADLOCK 部分:显示最近的死锁信息
  • TRX HAS BEEN WAITING X SEC FOR THIS LOCK TO BE GRANTED:显示锁等待详情

使用 Performance Schema

sql
-- 启用Performance Schema锁监控
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE '%lock%';
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%lock%';

-- 查看锁等待事件
SELECT
    EVENT_NAME,
    OBJECT_SCHEMA,
    OBJECT_NAME,
    INDEX_NAME,
    LOCK_TYPE,
    LOCK_MODE,
    LOCK_STATUS,
    SOURCE,
    TIMER_WAIT / 1000000000 AS WAIT_TIME_SEC
FROM
    performance_schema.data_locks
WHERE
    LOCK_STATUS = 'WAITING';

-- 查看锁等待统计
SELECT
    EVENT_NAME,
    COUNT_STAR AS WAIT_COUNT,
    SUM_TIMER_WAIT / 1000000000 AS TOTAL_WAIT_SEC,
    AVG_TIMER_WAIT / 1000000000 AS AVG_WAIT_SEC
FROM
    performance_schema.events_waits_summary_global_by_event_name
WHERE
    EVENT_NAME LIKE '%lock%'
ORDER BY
    TOTAL_WAIT_SEC DESC;

锁等待分析工具

命令行工具

工具用途命令示例
mysqladmin查看进程列表mysqladmin -u root -p processlist
pt-deadlock-loggerPercona工具,记录死锁pt-deadlock-logger h=localhost,u=root,p=password
pt-query-digest分析锁相关的慢查询`pt-query-digest --filter '($event->
innotop实时监控InnoDB事务和锁innotop

可视化工具

工具特点适用场景
MySQL Workbench图形化查看锁等待开发测试环境
Percona Monitoring and Management (PMM)集成锁等待监控生产环境
MySQL Enterprise Monitor官方工具,锁等待告警企业版用户
DataDog云原生监控,锁等待指标云环境

常见锁等待场景分析

表级锁等待

场景描述

-- 会话1:执行ALTER TABLE,持有表级锁
ALTER TABLE users ADD COLUMN age INT;

-- 会话2:执行查询,等待表级锁
SELECT * FROM users WHERE id = 1;

分析方法

sql
-- 查看进程列表
SHOW FULL PROCESSLIST;

-- 结果示例
+----+------+-----------+-------+---------+------+---------------------------------+--------------------------+
| Id | User | Host      | db    | Command | Time | State                           | Info                     |
+----+------+-----------+-------+---------+------+---------------------------------+--------------------------+
| 1  | root | localhost | test  | Query   | 10   | Waiting for table metadata lock | SELECT * FROM users WHERE id = 1 |
| 2  | root | localhost | test  | Query   | 20   | NULL                            | ALTER TABLE users ADD COLUMN age INT |
+----+------+-----------+-------+---------+------+---------------------------------+--------------------------+

解决方案

sql
-- 1. 查找并终止阻塞会话
KILL 2;

-- 2. 或等待DDL操作完成

-- 3. 优化DDL操作(使用Online DDL)
ALTER TABLE users ADD COLUMN age INT, ALGORITHM=INPLACE, LOCK=NONE;

行级锁等待

场景描述

sql
-- 会话1:开启事务,更新行但不提交
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;

-- 会话2:更新同一行,等待行锁
UPDATE users SET balance = balance + 50 WHERE id = 1;

分析方法

sql
-- 查看InnoDB事务状态
SHOW ENGINE INNODB STATUS\G

-- 关键输出
---TRANSACTION 12345, ACTIVE 10 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 2, OS thread handle 140123456789012, query id 123 localhost root updating
UPDATE users SET balance = balance + 50 WHERE id = 1
------- TRX HAS BEEN WAITING 10 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 123 page no 4 n bits 72 index PRIMARY of table `test`.`users` trx id 12345 lock_mode X waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;  # id = 1
 1: len 6; hex 000000003039; asc     09;;
 2: len 7; hex 800000019e0110; asc        ;;  # 事务ID和回滚指针
 3: len 5; hex 726f6f7400; asc root ;;  # username
 4: len 4; hex 800003e8; asc     ;;  # balance = 1000

---TRANSACTION 12344, ACTIVE 20 sec
mysql tables in use 1, locked 1
2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 1, OS thread handle 140123456789011, query id 122 localhost root
START TRANSACTION
mysql tables in use 1, locked 1
13072019 10:00:00 UPDATE users SET balance = balance - 100 WHERE id = 1

解决方案

sql
-- 1. 提交或回滚阻塞事务(会话1)
COMMIT;
-- 或
ROLLBACK;

-- 2. 终止阻塞会话
KILL 1;

-- 3. 优化事务,减少锁持有时间
-- 优化前
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
-- 执行其他耗时操作
SLEEP(10);
COMMIT;

-- 优化后
-- 先执行耗时操作
SLEEP(10);
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;

死锁

场景描述

sql
-- 会话1
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;

-- 会话2
START TRANSACTION;
UPDATE users SET balance = balance - 50 WHERE id = 2;

-- 会话1继续
UPDATE users SET balance = balance + 50 WHERE id = 2;

-- 会话2继续
UPDATE users SET balance = balance + 100 WHERE id = 1;

分析方法

sql
-- 查看InnoDB状态中的死锁信息
SHOW ENGINE INNODB STATUS\G

-- 关键输出
LATEST DETECTED DEADLOCK
------------------------
2023-01-01 10:00:00 0x7f1234567890

*** (1) TRANSACTION:
TRANSACTION 12345, ACTIVE 10 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 1, OS thread handle 140123456789011, query id 123 localhost root updating
UPDATE users SET balance = balance + 50 WHERE id = 2

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 123 page no 5 n bits 72 index PRIMARY of table `test`.`users` trx id 12345 lock_mode X waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;  # id = 2
...

*** (2) TRANSACTION:
TRANSACTION 12346, ACTIVE 5 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 2, OS thread handle 140123456789012, query id 124 localhost root updating
UPDATE users SET balance = balance + 100 WHERE id = 1

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 123 page no 5 n bits 72 index PRIMARY of table `test`.`users` trx id 12346 lock_mode X locks rec but not gap
Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;  # id = 2
...

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 123 page no 4 n bits 72 index PRIMARY of table `test`.`users` trx id 12346 lock_mode X waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;  # id = 1
...

*** WE ROLL BACK TRANSACTION (2)

解决方案

  1. 优化事务顺序

    sql
    -- 两个会话都按相同顺序更新行
    -- 会话1
    START TRANSACTION;
    UPDATE users SET balance = balance - 100 WHERE id = 1;
    UPDATE users SET balance = balance + 50 WHERE id = 2;
    COMMIT;
    
    -- 会话2
    START TRANSACTION;
    UPDATE users SET balance = balance - 50 WHERE id = 1;
    UPDATE users SET balance = balance + 100 WHERE id = 2;
    COMMIT;
  2. 减少事务长度

    • 将大事务拆分为小事务
    • 减少事务中的操作数量
  3. 使用更细粒度的锁

    • 使用行级锁而非表级锁
    • 合理设计索引,减少锁范围
  4. 设置合理的锁超时

    sql
    -- 设置锁等待超时时间(秒)
    SET GLOBAL innodb_lock_wait_timeout = 30;

间隙锁导致的锁等待

场景描述

sql
-- 表结构
CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    order_no VARCHAR(20) NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    INDEX idx_order_no (order_no)
) ENGINE=InnoDB;

-- 插入测试数据
INSERT INTO orders (order_no, amount) VALUES ('202301010001', 100.00);
INSERT INTO orders (order_no, amount) VALUES ('202301010003', 200.00);
INSERT INTO orders (order_no, amount) VALUES ('202301010005', 300.00);

-- 会话1:范围查询,产生间隙锁
START TRANSACTION;
SELECT * FROM orders WHERE order_no BETWEEN '202301010002' AND '202301010004' FOR UPDATE;

-- 会话2:插入间隙中的数据,等待锁
INSERT INTO orders (order_no, amount) VALUES ('202301010002', 150.00);

分析方法

sql
-- 查看锁等待
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS\G

-- 查看锁信息
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS\G

-- 关键输出
--- 会话1持有的锁
LOCK_ID: 12345:123:4:5
LOCK_TRX_ID: 12345
LOCK_MODE: X, GAP
LOCK_TYPE: RECORD
LOCK_TABLE: `test`.`orders`
LOCK_INDEX: idx_order_no
LOCK_SPACE: 123
LOCK_PAGE: 4
LOCK_REC: 5
LOCK_DATA: '202301010003', 2

--- 会话2等待的锁
LOCK_ID: 12346:123:4:5
LOCK_TRX_ID: 12346
LOCK_MODE: X, INSERT INTENTION
LOCK_TYPE: RECORD
LOCK_TABLE: `test`.`orders`
LOCK_INDEX: idx_order_no
LOCK_SPACE: 123
LOCK_PAGE: 4
LOCK_REC: 5
LOCK_DATA: '202301010003', 2

解决方案

  1. 使用更精确的查询条件

    sql
    -- 避免范围查询,使用精确查询
    SELECT * FROM orders WHERE order_no = '202301010003' FOR UPDATE;
  2. 调整事务隔离级别

    sql
    -- 使用READ COMMITTED隔离级别,减少间隙锁
    SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
  3. 优化索引设计

    • 确保查询使用最精确的索引
    • 避免在范围查询中使用非唯一索引
  4. 使用INSERT ... ON DUPLICATE KEY UPDATE

    sql
    -- 避免插入间隙,使用已存在的唯一键
    INSERT INTO orders (order_no, amount) VALUES ('202301010003', 150.00) ON DUPLICATE KEY UPDATE amount = 150.00;

锁等待优化策略

应用层面优化

  1. 减少事务持有锁的时间

    • 事务中先执行查询操作,最后执行更新/删除操作
    • 避免在事务中调用外部服务
    • 避免在事务中进行复杂计算
  2. 优化事务设计

    • 将大事务拆分为小事务
    • 使用更细粒度的事务
    • 合理设置事务隔离级别
  3. 使用乐观锁

    sql
    -- 添加版本号字段
    ALTER TABLE users ADD COLUMN version INT DEFAULT 1;
    
    -- 更新时检查版本号
    UPDATE users SET balance = balance - 100, version = version + 1 WHERE id = 1 AND version = 1;
  4. 合理设计应用架构

    • 实现读写分离
    • 使用缓存减少数据库访问
    • 异步处理非关键操作

数据库层面优化

  1. 优化索引设计

    • 为查询条件添加合适的索引
    • 避免全表扫描
    • 减少锁的范围
  2. 优化SQL语句

    • 避免使用SELECT *
    • 减少JOIN操作
    • 避免在WHERE子句中使用函数
  3. 调整InnoDB配置

    ini
    [mysqld]
    # 减少锁等待超时时间
    innodb_lock_wait_timeout = 30
    
    # 启用死锁检测
    innodb_deadlock_detect = ON
    
    # 优化并发事务处理
    innodb_concurrency_tickets = 5000
    innodb_thread_concurrency = 0
    
    # 优化锁内存管理
    innodb_lock_wait_timeout = 30
  4. 使用合适的存储引擎

    • 对于高并发场景,使用InnoDB存储引擎
    • 避免使用MyISAM存储引擎
  5. 定期维护表

    sql
    -- 优化表,减少碎片
    OPTIMIZE TABLE users;
    
    -- 重建索引
    ALTER TABLE users ENGINE=InnoDB;

运维层面优化

  1. 监控锁等待

    • 设置锁等待告警阈值
    • 定期分析锁等待日志
    • 监控死锁发生频率
  2. 定期分析慢查询

    • 优化导致锁等待的慢查询
    • 识别并修复长期运行的事务
  3. 实施读写分离

    • 减少主库的并发访问
    • 分散读压力
  4. 使用连接池

    • 管理数据库连接
    • 避免连接泄漏
    • 控制并发连接数

锁等待监控与告警

基于脚本的监控

bash
#!/bin/bash

# 配置信息
MYSQL_USER="root"
MYSQL_PASSWORD="password"
ALERT_EMAIL="admin@example.com"
THRESHOLD=10

# 查询锁等待时间超过阈值的事务
LOCK_WAITS=$(mysql -u $MYSQL_USER -p$MYSQL_PASSWORD -e "
SELECT
    r.trx_id waiting_trx_id,
    r.trx_mysql_thread_id waiting_thread,
    r.trx_query waiting_query,
    TIMESTAMPDIFF(SECOND, r.trx_started, NOW()) wait_time,
    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
JOIN
    INFORMATION_SCHEMA.INNODB_TRX b ON w.blocking_trx_id = b.trx_id
JOIN
    INFORMATION_SCHEMA.INNODB_TRX r ON w.requesting_trx_id = r.trx_id
WHERE
    TIMESTAMPDIFF(SECOND, r.trx_started, NOW()) > $THRESHOLD;")

# 检查是否有锁等待
if [ -n "$LOCK_WAITS" ]; then
    # 发送告警邮件
    echo -e "Subject: MySQL Lock Wait Alert\n\nLock waits detected:\n\n$LOCK_WAITS" | sendmail $ALERT_EMAIL
fi

集成到监控平台

Prometheus + Grafana

  1. 配置Prometheus监控锁等待指标

    yaml
    scrape_configs:
      - job_name: 'mysql'
        static_configs:
          - targets: ['localhost:9104']
        metrics_path: /metrics
  2. Grafana面板配置

    • 指标:mysql_global_status_innodb_row_lock_waits
    • 图表类型:Time series
    • 告警规则:当5分钟内锁等待次数超过10次时触发告警
  3. Alertmanager告警规则

    yaml
    groups:
    - name: mysql_lock_alerts
      rules:
      - alert: MySQLHighLockWaitRate
        expr: rate(mysql_global_status_innodb_row_lock_waits[5m]) > 10
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "High MySQL lock wait rate"
          description: "MySQL server {{ $labels.instance }} has a high lock wait rate ({{ $value }} waits per second)"

Zabbix监控

  1. 创建监控项

    • 类型:Zabbix agent (active)
    • 键值:mysql.status[Innodb_row_lock_waits]
    • 数据类型:数字(无正负号)
  2. 创建触发器

    • 名称:MySQL High Lock Waits
    • 表达式:{mysql-server:mysql.status[Innodb_row_lock_waits].diff(5m)} > 50
    • 严重性:警告

案例分析:电商系统锁等待优化

问题描述

某电商系统在促销活动期间出现大量锁等待,导致订单处理延迟,用户体验下降。

分析过程

  1. 查看锁等待状态

    sql
    SHOW ENGINE INNODB STATUS\G
  2. 识别锁等待热点

    sql
    SELECT
        r.trx_query waiting_query,
        COUNT(*) wait_count,
        AVG(TIMESTAMPDIFF(SECOND, r.trx_started, NOW())) avg_wait_time
    FROM
        INFORMATION_SCHEMA.INNODB_LOCK_WAITS w
    JOIN
        INFORMATION_SCHEMA.INNODB_TRX r ON w.requesting_trx_id = r.trx_id
    GROUP BY
        r.trx_query
    ORDER BY
        wait_count DESC;
  3. 发现主要问题

    • 订单表的UPDATE操作导致大量行锁等待
    • 订单状态更新的事务持有锁时间过长
    • 库存扣减和订单创建在同一个长事务中

优化方案

  1. 优化事务设计

    • 将长事务拆分为小事务
    • 先扣减库存,再创建订单
    • 减少事务持有锁的时间
  2. 优化SQL语句

    • 为订单表的状态字段添加索引
    • 优化订单查询条件
    • 减少SELECT *的使用
  3. 调整应用架构

    • 实现异步订单处理
    • 使用缓存减少数据库访问
    • 优化库存扣减逻辑
  4. 调整数据库配置

    ini
    [mysqld]
    innodb_lock_wait_timeout = 15
    innodb_deadlock_detect = ON
    innodb_concurrency_tickets = 10000

优化效果

指标优化前优化后
锁等待次数100+次/分钟<10次/分钟
平均锁等待时间10+秒<1秒
订单处理延迟5-10秒<1秒
系统吞吐量100订单/秒500订单/秒

总结

锁等待是MySQL高并发环境中常见的性能问题,通过合理的分析和优化,可以有效减少锁等待对系统性能的影响。作为DBA,应该:

  1. 定期分析锁等待:使用各种工具和方法监控锁等待状态
  2. 优化应用设计:减少事务持有锁的时间,优化SQL语句
  3. 优化数据库配置:调整InnoDB参数,优化索引设计
  4. 实施监控告警:及时发现和处理锁等待问题
  5. 持续优化:根据业务发展和系统负载变化,持续优化锁等待问题

通过综合运用这些方法,可以提高MySQL数据库的并发处理能力,确保系统在高负载下的稳定运行。