0

0

如何杀掉空闲事务

php中文网

php中文网

发布时间:2016-06-07 16:33:14

|

1355人浏览过

|

来源于php中文网

原创

本文内容遵从CC版权协议, 可以随意转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明网址: http://www.penglixun.com/tech/database/how_to_kill_idle_trx.html 我们经常遇到一个情况,就是网络断开或程序Bug导致COMMIT/ROLLBACK语句没有传到数

本文内容遵从cc版权协议, 可以随意转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明网址: http://www.penglixun.com/tech/database/how_to_kill_idle_trx.html

我们经常遇到一个情况,就是网络断开或程序Bug导致COMMIT/ROLLBACK语句没有传到数据库,也没有释放线程,但是线上事务锁定等待严重,连接数暴涨,尤其在测试库这种情况很多,线上也偶有发生,于是想为MySQL增加一个杀掉空闲事务的功能。

那么如何实现呢,通过MySQL Server层有很多不确定因素,最保险还是在存储引擎层实现,我们用的几乎都是InnoDB/XtraDB,所以就基于Percona来修改了,Oracle版的MySQL也可以照着修改。

需求:
1. 一个事务启动,如果事务内最后一个语句执行完超过一个时间(innodb_idle_trx_timeout),就应该关闭链接。
2. 如果事务是纯读事务,因为不加锁,所以无害,不需要关闭,保持即可。
虽然这个思路被Percona的指出Alexey Kopytov可能存在“Even though SELECT queries do not place row locks by default (there are exceptions), they can still block undo log records from being purged.”的问题,但是我们确实有场景SELECT是绝对不能kill的,除非之后的INSERT/UPDATE/DELETE发生了,所以我根据我们的业务特点来修改。
跟Percona的Yasufumi Kinoshita和Alexey Kopytov提出过纯SELECT事务不应被kill,但通过一个参数控制的方案还没有被Alexey Kopytov接受,作为通用处理我提出了用两个变量分别控制纯读事务的空闲超时时间和有锁事务的空闲超时时间,还在等待Percona的回复,因为这个方案还在测试,就先不开放修改了,当然如果你很熟悉MYSQL源码,我提出这个思路你肯定知道怎么分成这两个参数控制了。

根据这两个需求我们来设计方法,首先想到这个功能肯定是放在InnoDB Master Thread最方便,Master Thread每秒调度一次,可以顺便检查空闲事务,然后关闭,因为在事务中操作trx->mysql_thd并不安全,所以一般来说最好在InnoDB层换成Thread ID操作,并且InnoDB中除了ha_innodb.cc,其他地方不能饮用THD,所以Master Thread中需要的线程数值,都需要在ha_innodb中计算好传递整型或布尔型返回值给master thread调用。

首先,我们要增加一个参数:idle_trx_timeout,它表示事务多久没有下一条语句发生就超时关闭。
在storage/innodb_plugin/srv/srv0srv.c的“/* plugin options */”注释下增加如下代码注册idle_trx_timeout变量。

static MYSQL_SYSVAR_LONG(idle_trx_timeout, srv_idle_trx_timeout,
  PLUGIN_VAR_RQCMDARG,
  "If zero then this function no effect, if no-zero then wait idle_trx_timeout seconds this transaction will be closed",
  "Seconds of Idle-Transaction timeout",
  NULL, NULL, 0, 0, LONG_MAX, 0);

代码往下找在innobase_system_variables结构体内加上:

MYSQL_SYSVAR(idle_trx_timeout),

有了这个变量,我们需要在Master Thread(storage/innodb_plugin/srv/srv0srv.c )中执行检测函数查找空闲事务。在loop循环的if (sync_array_print_long_waits(&waiter, &sema)判断后加上这段判断

    if (srv_idle_trx_timeout && trx_sys) {
        trx_t*  trx;
        time_t  now;
rescan_idle:
        now = time(NULL);
        mutex_enter(&kernel_mutex);
        trx = UT_LIST_GET_FIRST(trx_sys->mysql_trx_list); # 从当前事务列表里获取第一个事务
        while (trx) { # 依次循环每个事务进行检查
            if (trx->conc_state == TRX_ACTIVE
                && trx->mysql_thd
                && innobase_thd_is_idle(trx->mysql_thd)) { # 如果事务还活着并且它的状态时空闲的
 
                ib_int64_t  start_time = innobase_thd_get_start_time(trx->mysql_thd); # 获取线程最后一个语句的开始时间
                ulong       thd_id = innobase_thd_get_thread_id(trx->mysql_thd); #获取线程ID,因为存储引擎内直接操作THD不安全
 
                if (trx->last_stmt_start != start_time) { # 如果事务最后语句起始时间不等于线程最后语句起始时间说明事务是新起的
                    trx->idle_start = now; # 更新事务的空闲起始时间
                    trx->last_stmt_start = start_time; # 更新事务的最后语句起始时间
                } else if (difftime(now, trx->idle_start) # 如果事务不是新起的,已经执行了一部分则判断空闲时间有多长了
                       > srv_idle_trx_timeout) { # 如果空闲时间超过阈值则杀掉链接
                    /* kill the session */
                    mutex_exit(&kernel_mutex);
                    thd_kill(thd_id); # 杀链接
                    goto rescan_idle;
                }
            }
            trx = UT_LIST_GET_NEXT(mysql_trx_list, trx); # 检查下一个事务
        }
        mutex_exit(&kernel_mutex);
    }

其中trx中的变量是新加的,在storage/innodb_plugin/include/trx0trx.h的trx_truct加上需要的变量:

struct trx_struct{
...
    time_t      idle_start;
    ib_int64_t  last_stmt_start;
...
}

这里有几个函数是自定义的:

Cursor
Cursor

一个新的IDE,使用AI来帮助您重构、理解、调试和编写代码。

下载
ibool      innobase_thd_is_idle(const void* thd);
ib_int64_t innobase_thd_get_start_time(const void* thd);
ulong      innobase_thd_get_thread_id(const void* thd);

这些函数在ha_innodb.cc中实现,需要在storage/innodb_plugin/srv/srv0srv.c头文件定义下加上这些函数的引用形势。

然后在storage/innodb_plugin/handler/ha_innodb.cc 中定义这些函数的实现:

extern "C"
ibool
innobase_thd_is_idle(
    const void* thd)    /*!< in: thread handle (THD*) */
{
    return(((const THD*)thd)->command == COM_SLEEP);
}
extern "C"
ib_int64_t
innobase_thd_get_start_time(
    const void* thd)    /*!< in: thread handle (THD*) */
{
    return((ib_int64_t)((const THD*)thd)->start_time);
}
extern "C"
ulong
innobase_thd_get_thread_id(
        const void* thd)
{
    return(thd_get_thread_id((const THD*) thd));
}

还有最重要的thd_kill函数负责杀线程的,在sql/sql_class.cc中,找个地方定义这个函数:

void thd_kill(ulong id)
{
    THD *tmp;
    VOID(pthread_mutex_lock(&LOCK_thread_count));
    I_List_iterator<THD> it(threads);
    while ((tmp=it++))
    {
        if (tmp->command == COM_DAEMON || tmp->is_have_lock_thd == 0 ) # 如果是DAEMON线程和不含锁的线程就不要kill了
            continue;
        if (tmp->thread_id == id)
        {
            pthread_mutex_lock(&tmp->LOCK_thd_data);
            break;
        }
    }
    VOID(pthread_mutex_unlock(&LOCK_thread_count));
    if (tmp)
    {
        tmp->awake(THD::KILL_CONNECTION);
        pthread_mutex_unlock(&tmp->LOCK_thd_data);
    }
}

为了存储引擎能引用到这个函数,我们要把它定义到plugin中:
include/mysql/plugin.h和include/mysql/plugin.h中加上

void thd_kill(unsigned long id);

如何判定线程的is_have_lock_thd值?首先在THD中加上这个变量(sql/sql_class.cc):

class THD :public Statement,
           public Open_tables_state
{
....
  uint16    is_have_lock_thd;
....
}

然后在SQL的必经之路mysql_execute_command拦上一刀,判断是有锁操作发生了还是事务提交或新起事务。

  switch (lex->sql_command) {
  case SQLCOM_REPLACE:
  case SQLCOM_REPLACE_SELECT:
  case SQLCOM_UPDATE:
  case SQLCOM_UPDATE_MULTI:
  case SQLCOM_DELETE:
  case SQLCOM_DELETE_MULTI:
  case SQLCOM_INSERT:
  case SQLCOM_INSERT_SELECT:
      thd->is_have_lock_thd = 1;
      break;
  case SQLCOM_COMMIT:
  case SQLCOM_ROLLBACK:
  case SQLCOM_XA_START:
  case SQLCOM_XA_END:
  case SQLCOM_XA_PREPARE:
  case SQLCOM_XA_COMMIT:
  case SQLCOM_XA_ROLLBACK:
  case SQLCOM_XA_RECOVER:
      thd->is_have_lock_thd = 0;
      break;
  }

为了尽可能兼容Percona的补丁,能引用的都引用了Percona的操作,有些函数调用是在层次太多看不下去了就简化了。
另外还有一个版本是我自己弄的,在THD中增加了一个last_sql_end_time,在do_command结束后更新last_sql_end_time,然后在事务中拿到THD查看last_sql_end_time就可以得出idle时间,Oracle版我还是建议这么做,不要去改trx_struct结构体了,那个感觉更危险。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

14

2026.01.30

c++ 字符串格式化
c++ 字符串格式化

本专题整合了c++字符串格式化用法、输出技巧、实践等等内容,阅读专题下面的文章了解更多详细内容。

9

2026.01.30

java 字符串格式化
java 字符串格式化

本专题整合了java如何进行字符串格式化相关教程、使用解析、方法详解等等内容。阅读专题下面的文章了解更多详细教程。

12

2026.01.30

python 字符串格式化
python 字符串格式化

本专题整合了python字符串格式化教程、实践、方法、进阶等等相关内容,阅读专题下面的文章了解更多详细操作。

4

2026.01.30

java入门学习合集
java入门学习合集

本专题整合了java入门学习指南、初学者项目实战、入门到精通等等内容,阅读专题下面的文章了解更多详细学习方法。

20

2026.01.29

java配置环境变量教程合集
java配置环境变量教程合集

本专题整合了java配置环境变量设置、步骤、安装jdk、避免冲突等等相关内容,阅读专题下面的文章了解更多详细操作。

18

2026.01.29

java成品学习网站推荐大全
java成品学习网站推荐大全

本专题整合了java成品网站、在线成品网站源码、源码入口等等相关内容,阅读专题下面的文章了解更多详细推荐内容。

19

2026.01.29

Java字符串处理使用教程合集
Java字符串处理使用教程合集

本专题整合了Java字符串截取、处理、使用、实战等等教程内容,阅读专题下面的文章了解详细操作教程。

3

2026.01.29

Java空对象相关教程合集
Java空对象相关教程合集

本专题整合了Java空对象相关教程,阅读专题下面的文章了解更多详细内容。

6

2026.01.29

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 8万人学习

ASP 教程
ASP 教程

共34课时 | 4.2万人学习

SQL 教程
SQL 教程

共61课时 | 3.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号