第13章 MySQL监控
1、监控锁情况
1)什么是死锁
在数据库系统中,死锁(Deadlock)指的是两个或多个事务在执行的过程中,因各自持有对方想要获取的资源而相互等待,导致这几个事务都无法继续执行的现象。
通俗一点说,事务 A 拿着事务 B 想要的锁,同时事务 B 也占着事务 A 想要的锁,双方都不肯“让步”,就陷入了无限等待的僵局,这就是死锁。
当数据库检测到死锁时,往往会主动回滚其中一个或多个事务,以打破僵局。
2)死锁产生的原因
1. **并发事务过多**
多个事务在高并发场景下,操作同一批数据或同一张表,频繁地获取和释放锁。如果事务之间锁的申请顺序不一致,很容易导致死锁。
2. **锁的粒度与方式**
InnoDB 中的行级锁、间隙锁(Gap Lock)、Next-Key Lock 等机制,会在复杂的查询和更新语句中造成锁范围的扩大或锁等待冲突,从而引发死锁。
3. **未能及时提交或回滚事务**
长事务没有及时结束(提交/回滚),导致锁被长时间持有,其它事务在获取锁时被阻塞,增加了死锁发生的概率。
4. **SQL 语句执行顺序**
相同的业务逻辑,如果开发者在代码层面未对锁的获取顺序进行规范(如先锁表 A 再锁表 B 或者先锁表 B 再锁表 A),在高并发条件下,各事务获取资源的顺序不一致,就可能导致死锁。
3)死锁有哪些分类
行锁
表锁
4)监控死锁语句涉及到的表及命令
相关的表:
谁在等待锁(通过 innodb_trx / innodb_lock_waits)
谁持有锁(innodb_trx / innodb_lock_waits)
正在执行什么语句(performance_schema.events_statements_current)
执行完成后的历史语句(performance_schema.events_statements_history)
对应会话/线程信息(performance_schema.threads)
相关监控命令:
# 查看 InnoDB 锁相关状态
SHOW GLOBAL STATUS like 'innodb_rows_lock%'
# 当前事务信息
select * from information_schema.innodb_trx;
# 锁等待依赖信息
select * from sys.innodb_lock_waits;
# 线程信息
select * from performance_schema.threads;
# 当前正在执行的 SQL
select * from performance_schema.events_statements_current;
# 历史执行的 SQL
select * from performance_schema.events_statements_history;
5)死锁实验
# 创建测试表与插入数据
CREATE DATABASE test;
USE test;
CREATE TABLE test_lock (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50),
value INT
) ENGINE=InnoDB;
INSERT INTO test_lock (name, value) VALUES ('alice', 100), ('bob', 100);
# Session A(占用行锁)
START TRANSACTION;
UPDATE test_lock SET value = value + 10 WHERE name = 'alice';
-- 不要提交或回滚,让事务保持打开状态
# Session B(触发锁等待)
USE test;
START TRANSACTION;
UPDATE test_lock SET value = value + 20 WHERE name = 'alice';
-- 该语句将被阻塞,等待 Session A 释放锁
# 查看 InnoDB 锁相关状态
mysql> SHOW GLOBAL STATUS LIKE 'Innodb_row_lock%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 1 |
| Innodb_row_lock_time | 0 |
| Innodb_row_lock_time_avg | 0 |
| Innodb_row_lock_time_max | 0 |
| Innodb_row_lock_waits | 1 |
+-------------------------------+-------+
5 rows in set (0.06 sec)
# 当前所有 InnoDB 事务信息
mysql> SELECT trx_id, trx_state, trx_started, trx_wait_started, trx_mysql_thread_id, trx_query
-> FROM information_schema.innodb_trx
-> ORDER BY trx_started;
+--------+-----------+---------------------+---------------------+---------------------+--------------------------------------------------------------+
| trx_id | trx_state | trx_started | trx_wait_started | trx_mysql_thread_id | trx_query |
+--------+-----------+---------------------+---------------------+---------------------+--------------------------------------------------------------+
| 3852 | RUNNING | 2025-01-10 18:34:17 | NULL | 3 | NULL |
| 3853 | LOCK WAIT | 2025-01-10 18:34:32 | 2025-01-10 18:34:32 | 4 | UPDATE test_lock
SET value = value + 20
WHERE name = 'alice' |
+--------+-----------+---------------------+---------------------+---------------------+--------------------------------------------------------------+
2 rows in set (0.00 sec)
6)发现死锁后处理流程
1、通过分析找到锁源的SQL语句
2、将相关SQL语句抄送给技术老大及开发负责人
3、根据开发反馈可能有两种情况:
第一种:不是代码问题
可能是数据库繁忙导致事务语句执行失败
建议临时kill掉有问题的会话
第二种:是代码问题
发现是代码业务逻辑问题导致的死锁
临时解决方案 kill掉有问题的会话
最终解决方案 修复代码中的逻辑
7)监控死锁脚本
ChatGPT Plus编写的检查脚本:
cat > check-v4.sh << 'EOF'
#!/bin/bash
# ===================================================================
# check_v2_optimized.sh
#
# 说明:
# - 基于 v2 版本的锁检查脚本,输出包括:
# [1] InnoDB 行锁统计
# [2] 当前所有 InnoDB 事务信息
# [3] 锁等待关系 (sys.innodb_lock_waits)
# [4] 关联 InnoDB 事务与 PROCESSLIST
# - 仅在第 [4] 段末尾添加一行说明,解释当 innodb_trx 中的SQL
# 和 processlist 中的SQL 相同时是正常现象。
# ===================================================================
HOST="127.0.0.1"
PORT="3306"
USER="root"
PASS="root"
DB="test" # 可视情况指定,或留空
MYSQL_CMD="mysql -h$HOST -P$PORT -u$USER -p$PASS -N" # -N 去除列名,减少干扰
LINE="--------------------------------------------------"
# [1] InnoDB 行锁统计
echo
echo "=================== [1] InnoDB 行锁统计 ==================="
echo
$MYSQL_CMD -e "SHOW GLOBAL STATUS LIKE 'Innodb_row_lock%';" 2>/dev/null | while read var_name var_value; do
case "$var_name" in
"Innodb_row_lock_current_waits") echo "当前锁等待个数: $var_value" ;;
"Innodb_row_lock_time") echo "行锁等待总时间(毫秒): $var_value" ;;
"Innodb_row_lock_time_avg") echo "平均每次锁等待时间(毫秒): $var_value" ;;
"Innodb_row_lock_time_max") echo "最长一次锁等待时间(毫秒): $var_value" ;;
"Innodb_row_lock_waits") echo "自启动以来锁等待的总次数: $var_value" ;;
esac
done
# [2] 当前所有 InnoDB 事务信息
echo
echo "=================== [2] 当前所有 InnoDB 事务信息 ==================="
echo
# 使用 CONCAT_WS('|', ...) 避免空格错位
innodb_trx_output=$($MYSQL_CMD -e "
SELECT CONCAT_WS('|',
trx_id,
trx_state,
trx_started,
IFNULL(trx_wait_started,'NULL'),
IFNULL(trx_mysql_thread_id,'NULL'),
IFNULL(REPLACE(REPLACE(trx_query, '\n',' '), '\t',' '),'NULL')
)
FROM information_schema.innodb_trx
ORDER BY trx_started;
" 2>/dev/null)
if [ -z "$innodb_trx_output" ]; then
echo "当前无 InnoDB 事务或无法读取 innodb_trx 表。"
else
while IFS='|' read -r trx_id trx_state trx_started trx_wait_started trx_thread_id trx_query; do
echo "事务ID: $trx_id"
echo " - 状态: $trx_state"
echo " - 开始时间: $trx_started"
if [ "$trx_wait_started" != "NULL" ]; then
echo " - 锁等待开始时间: $trx_wait_started"
fi
echo " - 所属线程ID: $trx_thread_id"
echo " - 当前SQL: $trx_query"
echo "$LINE"
done <<< "$innodb_trx_output"
fi
# [3] 锁等待关系 (sys.innodb_lock_waits)
echo
echo "=================== [3] 锁等待关系 ==================="
echo
echo "使用 sys.innodb_lock_waits 查询:"
lock_waits_res=$($MYSQL_CMD -e "
SELECT
waiting_trx_id,
blocking_trx_id,
waiting_thread_id,
blocking_thread_id
FROM sys.innodb_lock_waits;
" 2>/dev/null)
if [ -z "$lock_waits_res" ]; then
echo "当前无锁等待记录。"
else
echo "$lock_waits_res"
fi
# [4] 关联 InnoDB 事务与 PROCESSLIST
echo
echo "=================== [4] 关联 InnoDB 事务与 PROCESSLIST ==================="
echo
trx_process_res=$($MYSQL_CMD -e "
SELECT CONCAT_WS('|',
t.trx_id,
IFNULL(t.trx_mysql_thread_id,'NULL'),
IFNULL(REPLACE(REPLACE(t.trx_query, '\n',' '), '\t',' '),'NULL'),
IFNULL(REPLACE(REPLACE(p.INFO, '\n',' '), '\t',' '),'NULL')
)
FROM information_schema.innodb_trx t
LEFT JOIN information_schema.processlist p
ON t.trx_mysql_thread_id = p.ID
ORDER BY t.trx_started;
" 2>/dev/null)
if [ -z "$trx_process_res" ]; then
echo "无事务或无法关联 processlist。"
else
while IFS='|' read -r trx_id thr_id t_query p_info; do
echo "事务 $trx_id <-> 线程 $thr_id"
echo " - innodb_trx 中记录的SQL: $t_query"
echo " - processlist 中实际在执行的SQL: $p_info"
echo " (说明:如果这两处SQL相同,通常表示该事务正在执行或等待同一条SQL,属正常情况。)"
echo "$LINE"
done <<< "$trx_process_res"
fi
echo
echo "【脚本执行完毕】以上为当前锁等待及事务信息的简要解析。"
echo "如果发现某事务长时间占用锁,可考虑 COMMIT 或 ROLLBACK 或执行: KILL <线程ID>"
echo
EOF
执行效果:
2、监控指标
以下是针对MySQL 5.7的重要监控指标,包括单位、含义、监控命令以及官方建议值
MySQL监控的指标众多,我们可以参考阿里云上的RDS的监控作为参考:
指标名称 | 单位 | 含义 | 监控命令 | 正常情况(示例) | 异常情况(示例) |
---|---|---|---|---|---|
查询吞吐量 (QPS) | 次/秒 | 每秒执行的查询次数,反映数据库的负载情况 | SHOW GLOBAL STATUS LIKE 'Questions'; | 稳定在预期范围内,如1000 QPS | 突然 spike 到 2000 QPS 或 drop 到 500 QPS |
连接数 | 个 | 当前连接到 MySQL 的客户端数量 | SHOW GLOBAL STATUS LIKE 'Threads_connected'; | 低于最大连接数,如 100/150 | 接近或达到最大连接数,如 149/150 |
缓存命中率 | % | InnoDB 缓冲池的命中率,反映缓冲池的使用效率 | SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%'; | 大于90% | 小于80% |
锁等待时间 | 秒 | 事务等待锁的时间,反映锁争用情况 | SHOW GLOBAL STATUS LIKE 'Innodb_row_lock_time'; | 接近零或很低,如 0.01 秒 | 持续高于 0.1 秒 |
慢查询数量 | 个 | 执行时间超过 long_query_time 的查询数量 | SHOW GLOBAL STATUS LIKE 'Slow_queries'; | 很低,如每天少于 10 个 | 突然增加到每秒多个 |
临时表创建数量 | 个 | 创建的磁盘临时表数量,反映查询的优化情况 | SHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables'; | 很低,如每天少于 100 个 | 持续高于 1000 个 |
网络流量 | 字节 | MySQL 接收和发送的网络数据量 | SHOW GLOBAL STATUS LIKE 'Bytes_received'; 和 SHOW GLOBAL STATUS LIKE 'Bytes_sent'; | 稳定趋势,如每天 10GB | 突然 spike 到 100GB |
打开的表数量 | 个 | 当前打开的表数量,反映表缓存的使用情况 | SHOW GLOBAL STATUS LIKE 'Open_tables'; | 低于表缓存设置,如 500/1000 | 接近或超过表缓存设置,如 999/1000 |
复制延迟 | 秒 | 从库相对于主库的复制延迟时间 | SHOW SLAVE STATUS\G 中的 Seconds_Behind_Master | 0 或接近零 | 持续高于 60 秒 |
InnoDB 日志写等待 | 次 | InnoDB 日志写操作的等待次数,反映日志写入的性能 | SHOW GLOBAL STATUS LIKE 'Innodb_log_waits'; | 接近零 | 持续高于 10 次 |
线程缓存命中率 | % | 线程缓存的命中率,反映线程缓存的使用效率 | SHOW GLOBAL STATUS LIKE 'Threads_cached'; | 大于80% | 小于50% |
临时文件创建数量 | 个 | 创建的临时文件数量,反映查询的优化情况 | SHOW GLOBAL STATUS LIKE 'Created_tmp_files'; | 很低,如每天少于 50 个 | 持续高于 500 个 |
InnoDB 缓冲池使用率 | % | InnoDB 缓冲池的使用率,反映缓冲池的内存使用情况 | SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages%'; | 大于80% | 小于70% 或过高导致 swapping |
InnoDB 行锁等待时间 | 秒 | InnoDB 行锁的等待时间,反映行锁的争用情况 | SHOW GLOBAL STATUS LIKE 'Innodb_row_lock_time'; | 接近零或很低 | 持续高于 0.05 秒 |
这个表格提供了各个监控指标的正常和异常情况示例,帮助数据库管理员更好地理解和监控MySQL 5.7的性能和健康状况。
3、监控图形及解释
MySQL CPU/内存 利用率
MySQL存储空间使用量(MB)
磁盘使用率(%)
MySQL物理存储空间使用量(MB)
MySQL IOPS
IOPS使用率
MySQL每秒读写吞吐量(B)
流量吞吐(KB)
TPS/QPS
会话连接
执行次数
线程
MySQL_SelectScan
刷盘次数
InnoDB Data 读写吞吐量(KB)
InnoDB Buffer Pool 请求次数
InnoDB Buffer Pool 命中率
行锁
节点复制线程状态
节点复制延迟(second)
更新: 2025-01-11 11:18:38