DBBrain最佳实践:未提交事务的处理与应对

2022-03-03 14:19:09 浏览数 (2)

背景

DBBrain 上经常会有用户来咨询“未提交事务”的事件会有什么问题,该如何处理等。其实这个问题的影响属于可大可小,所以正好来专门分析一下,避免因为轻视了这个问题导致严重的业务故障。

问题描述

未提交事务指的是有连接在数据库中开启了事务,但是却一直没有提交事务的现象。如果事务一直不提交,那么对应数据行的锁始终无法释放,表的元数据锁也会一直持有,导致这个表的 DDL 会被一直阻塞。

DBBrain 针对这个问题有专门的监控,当发现这个现象之后就会推送“未提交事务”的异常事件。

分析

点开DBBrain可以看到有异常事件“未提交事务”。

示例图示例图

这一类未提交事务的信息可以在DBBrain的事件详情,或者在命令行检查:

代码语言:txt复制
mysql> select * from information_schema.INNODB_TRXG
*************************** 1. row ***************************
                    trx_id: 149573207
                 trx_state: RUNNING
               trx_started: 2022-03-03 13:17:36
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 3
       trx_mysql_thread_id: 194632
                 trx_query: NULL
       trx_operation_state: NULL
         trx_tables_in_use: 0
         trx_tables_locked: 1
          trx_lock_structs: 2
     trx_lock_memory_bytes: 1136
           trx_rows_locked: 1
         trx_rows_modified: 1
   trx_concurrency_tickets: 0
       trx_isolation_level: READ COMMITTED
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0

这段信息中,比较重要的信息是事务开始时间和 thread_id,如上例中,可以根据事务开始时间估算是什么业务模块/脚本发起的。trx_mysql_thread_id 显示的是这个事务是由哪个连接发起的,通过 processlist 中的 id 找到这个线程。

代码语言:txt复制
mysql> show processlist;
 -------- ------ ---------------- -------------------- --------- ------ ---------- ------------------ 
| Id     | User | Host           | db                 | Command | Time | State    | Info             |
 -------- ------ ---------------- -------------------- --------- ------ ---------- ------------------ 
| 194631 | root | 10.0.0.6:37912 | information_schema | Query   |    0 | starting | show processlist |
| 194632 | root | 10.0.0.6:37914 | test               | Sleep   | 1611 |          | NULL             |
 -------- ------ ---------------- -------------------- --------- ------ ---------- ------------------ 
2 rows in set (0.00 sec)

可以看到 194632 这个 id 对应的是 test 用户,info 显示的 NULL,不显示具体 SQL 是因为 SQL 语句已经执行完了,然后没有再执行任何操作,处于空闲状态。

如果事务未提交,那么其他连接在操作同样的数据库行时,就会遇到锁等待报错,DDL 也会有 MDL 锁,通过简单的示例模拟一下:

代码语言:txt复制
Seesion 1:
mysql> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> update t1 set name = 'stu-108-m' where id = 8;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql>
mysql> alter table t1 engine=innodb;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql>

Session 2:
mysql> show processlist;
 -------- ------ ---------------- ------ --------- ------ --------------------------------- ------------------------------ 
| Id     | User | Host           | db   | Command | Time | State                           | Info                         |
 -------- ------ ---------------- ------ --------- ------ --------------------------------- ------------------------------ 
| 194632 | root | 10.0.0.6:37914 | test | Query   |    0 | starting                        | show processlist             |
| 195373 | root | 10.0.0.6:57056 | test | Query   |   11 | Waiting for table metadata lock | alter table t1 engine=innodb |
 -------- ------ ---------------- ------ --------- ------ --------------------------------- ------------------------------ 
2 rows in set (0.00 sec)

如果 lock_wait 默认设置得很高,那么未提交事务会长时间阻塞其他的连接,严重一点的会导致大量的连接堆积在数据库中导致数据库hang死,DDL 操作同理,不管是 MDL 锁还是线程堆积都会影响到线上的业务语句。

解决办法其实比较简单,在 DBBrain 的会话中杀死这个 id 为 195373 的连接,或者是在命令行,使用 test 这个用户登录进去,使用 kill 命令杀死 195373 的连接。

PS:使用 test 的原因是腾讯云数据库 MySQL 默认是没有 super 权限的,所以必须要使用同一个账号来操作。

总结

未提交事务产生的影响整体来说还是有比较大的影响的,一般来说临时的解决方案是尽快 kill 掉这个事务对应的连接,之后再根据事务开始的时间去排查未提交事务引起的原因,是脚本、临时操作、还是业务代码上的漏洞。

0 人点赞