mysql空事务_MySQL数据库:如何杀掉空闲事务?

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

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

需求:

① 一个事务启动,如果事务内最后一个语句执行完超过一个时间(innodb_idle_trx_timeout),就应该关闭链接。

②如果事务是纯读事务,因为不加锁,所以无害,不需要关闭,保持即可。

虽然这个思路被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,

"Ifzerothenthisfunctionno effect,ifno-zerothenwait idle_trx_timeout seconds thistransactionwill be closed",

"SecondsofIdle-Transactiontimeout",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; # 更新事务的最后语句起始时间

}elseif(difftime(now, trx->idle_start)                # 如果事务不是新起的,已经执行了一部分则判断空闲时间有多长了>srv_idle_trx_timeout) { # 如果空闲时间超过阈值则杀掉链接/*kill the session*/mutex_exit(&kernel_mutex);

thd_kill(thd_id); # 杀链接gotorescan_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;

...

}

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

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)/*!command == COM_SLEEP);

}

extern "C"

ib_int64_t

innobase_thd_get_start_time(

const void* 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 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(unsignedlongid);

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

class THD :publicStatement,publicOpen_tables_state

{

....

uint16    is_have_lock_thd;

....

}

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

switch (lex->sql_command) {caseSQLCOM_REPLACE:caseSQLCOM_REPLACE_SELECT:caseSQLCOM_UPDATE:caseSQLCOM_UPDATE_MULTI:caseSQLCOM_DELETE:caseSQLCOM_DELETE_MULTI:caseSQLCOM_INSERT:caseSQLCOM_INSERT_SELECT:

thd->is_have_lock_thd=1;break;caseSQLCOM_COMMIT:caseSQLCOM_ROLLBACK:caseSQLCOM_XA_START:caseSQLCOM_XA_END:caseSQLCOM_XA_PREPARE:caseSQLCOM_XA_COMMIT:caseSQLCOM_XA_ROLLBACK:caseSQLCOM_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结构体了,那个感觉更危险。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值