autocommit=0引发的业务hang住

背景mysql

有用户报告一个普通的select 语句被hang住了,执行超时。查明以后发现是autocommit使用不当致使。
这里将case简化,说明复现步骤及缘由。

复现sql

session1 建表并插入数据:

create table if not exists t(id int primary key, c int);
set autocommit=0;
insert into t values(1,1);
insert into t values(2,2);
insert into t values(3,3);
commit;
select count(*) from t;

这个执行流程的目的很直观,建表、插入数据、查询结果。貌似没有问题。

维持session1不断,新建一个链接session2,执行 create table if not exists t(id int primary key, c int);
此时该语句处于等待状态.

再新建一个链接session3, 执行select count(*) from t; 该语句处于等待状态.

因而从业务上看就是一个select 语句被hang住。

缘由分析session

MySQL Tips:  若是服务中某些语句没法执行完成,追查问题时第一步要先保留现场,pstack <pid of mysqld> > tmplog之一个经常使用的方法。
这两个等待线程的栈如:
#0  0x000000310ce0b7bb in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1  0x000000000063ba46 in MDL_wait::timed_wait(THD*, timespec*, bool, char const*) ()
#2  0x000000000063e095 in MDL_context::acquire_lock(MDL_request*, unsigned long) ()

能够看到,堵在MDL_wait.

简单说明下什么是MDL。试想,若是一个语句在执行一个表上的查询过程当中,表结构被改了,或者表被drop,这样会获得一个错误的结果。所以在一个事务持续期间,就须要对访问的表结构做保护。这个就是meta data lock (MDL).
很容易理解的,对表数据做增删改查,须要对MDL加读锁,修改表结构、删除表等操做则加写锁。
MySQL Tips: MDL是5.5才加入的机制,5.1版本下本文的case不会复现。

MySQL Tips: 事务中MDL申请时机是在首次使用时,释放时机是在事务结束后。
也就是说文章开头的这个case,缘由是session2等待在加写锁过程。而session3虽然只是加读锁,但与session2冲突,也须要等待。

session1的事务ui

也就是说session1还持有表t的MDL读锁。但咱们的事务明明已经提交(commit)了。这里就涉及到一个常见的误解。之前有看过文章说,能够用set autocommit=0开启一个事务。其实这个描述不许确.
MySQL Tips: set autocommit=0是将本线程设置为非自动提交模式。在每一个事务结束后,下个语句开始时自动新建一个事务。
这就意味着,session1最后的那个select count(*)操做,实际上以前隐含了一个begin操做。因为该事务没有提交,所以session1持有表t的MDL读锁。

所以对于业务方的建议就是,及时提交这些读事务,或断开链接。
MySQL Tips: 链接断开时,MySQL会自动回滚当前未提交的事务。
因为本case里面session1的最后一个事务只是一个select语句,所以回滚不影响业务。

小结spa

1) 显式的启动事务的方法是begin或start transaction; 提交事务的方法是commit;
2) set autocommit=0的好处是在频繁开启事务的场景下,减小一次begin的交互。
3) 注意set autocommit=0修改了线程变量,会影响本线程存活期间的事务行为。
4) set autocommit=1能够提交事务并改变值。