mysql 事务时长 mysql事务超时时间_mysql 事务时长


前言

这篇博文源于公司一个批处理的项目异常而起的。先简单描述下发生背景。一个基于spring batch开发的批处理应用,线上运行了9个多月后,某一天突然跑批任务失败了,检查日志得知,是因为一个mysql异常导致的:Lock wait timeout exceeded。

msyql事务锁等待超时这个异常虽然不常见,但随便一搜就会看到大量的相关的信息。导致这个异常的原因就是mysql数据库事务锁等待超时,默认超时时间是50S。但我们的批处理业务从逻辑上讲不会出现这种事务排他锁等待的情况,不得其解。

故通过以下这些实例来捋一捋mysql事务内隔离级别和锁等知识点,看看是否如我们了解的这样,同时加深下印象。

讲什么?

通读本文你能了解到,mysql InnoDB事务是通过锁+MVCC(多版本并发控制)来解决并发问题的,然后什么情况下会发生Lock wait timeout exceeded,也就是什么情况下会加锁,加什么锁,以及怎么排查数据库死锁等问题。主要是提供一些思路。

说明:此文适用mysql版本5.7及以下

事务的隔离级别

先了解下mysql的事务隔离级别,这个也是老生常谈的一个知识点,也是面试比较常问的一个问题,不过能够以自己的理解描述出来的人不多

读未提交(read-uncommitted):存在【脏读】、【不可重复度】、【幻读】的问题

读已提交(read-committed) :存在【不可重复度】、【幻读】的问题

可重复读(repeatable-read):存在【幻读】的问题

串行化(serializable):没问题。

  • 【脏读】:事务A读取到了事务B未提交的内容,事务B在后面可能事务回滚了,那么事务A读取的记录就是脏数据了。
  • 【不可重复读】:事务A在一个事务内读取同一个记录两次,由于事务B在事务A读取的间隙修改了数据,导致事务A两次读取到的记录不一样。
  • 【幻读】:事务A查询一条记录是否存在而去插入这条记录,查询出来不存在,当执行插入前间隙的时候,事务B插入了这条记录,这个时候事务A插入就会失败,就像幻觉一样,明明刚刚查询的时候这条记录还不存在

关于幻读,网上有大量的针对【幻读】的解读,其实都是有误解的。幻读其实是类似这种根据查询某些符合条件的记录去做相关的业务的,比如事务A查询记录1的值,复制这条记录,然后保存,这个时候事务结束后期望的是复制的这条记录和原始记录是一样的。

然而如果发生幻读现象,事务B在事务A复制这条新纪录的间隙修改了原记录的值,那么新的这个复制记录的值和原记录的值就不相等了。

网上很多说幻读是事务两次读取的记录的行数不一致的问题(也就是幻读针对的是ROW的新增,针对插入动作)其实是不对的,这种情况下RR的隔离级别通过间隙锁(Gap Locks)和MVCC就可以避免了。

简而言之就是A事务内查询的数据可能在A事务未完成之前被事务B修改了,A事务再次读取记录验证时与预期不符。

InnoDB的事务、锁、等表

mysql有一个系统数据库,里面有很多表,其中,如下三张表保存了事务相关的信息,如当前发生的事务,以及当前锁定的记录,和当前事务等待锁的信息等。下面的实例最终需要结合这些表的记录信息来分析验证,所以我们先看看这些表具体记录了哪些信息,能给我们平时排查问题带来哪些帮助。

INNODB_TRX:InnoDB的事务表,每次开启事务这里都会有记录,可以方便的查看当前正在执行以及正在等待执行的事务信息。包括执行的sql、事务的隔离级别、相关联表的数量等信息,详情如下:


CREATE


INNODB_LOCKS:InnoDB的锁信息,可以查询到当前事务使用了锁来控制并发的锁信息


CREATE


INNODB_LOCK_WAITS:Innodb锁等待信息,主要记录了当前执行事务ID和锁ID以及等待执行的事务ID和锁ID的关系。


CREATE


基于这些表的信息,可以帮助我们诊断在并发负载较重时发生的性能问题。

mysql的锁信息

以下是mysql事务中比较常见的锁的信息,

X(排它锁):也叫独占锁,亦称写锁。事务A持有X后,事务B无论请求X锁还是S锁都只能等待A事务的释放。

S(共享锁):也叫读锁。事务A持有S锁后,事务B请求S锁会立即授予,如果请求X锁照样需要等待。例如,串行化事务隔离级别对读取的记录加S读锁了,当其他的事务需要读取这条记录的时候会立即授予,当其他事务需要修改这条记录获取X写锁时就必须阻塞等待了

GAP(间隙锁):锁定一个区间的记录数。例如,SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;阻止其他事务将值15插入列t.c1,无论列 中是否已存在任何此类值,因为该范围中所有现有值之间的间隙都已锁定。

快照读和当前读

思考个问题。在RR级别下的事务在没有使用锁的情况下,是怎么解决一致性读的问题的?是因为mysql使用了MVCC(多版本并发控制)技术,事务内首次读时生成了快照,再次读记录其实就是读取的快照的内容,所以就涉及到了快照读和当前读:

快照读:读取数据的可见版本。不加事务的select操作都是快照读,加事务的情况下要看事务隔离级别。如READ_UNCOMMITTED情况下就可能读到最新未提交的记录

当前读:读取数据的最新版本。如带lock in share mode、 for update的select,以及增删改等操作都是当前读。需要对当前的主键以及唯一索引数据加锁

测试实例

先创建一张表user表,包含如下的字段,如:


CREATE


id为主键,并给card字段设置唯一索引

REPEATABLE-READ隔离级别

这个是mysql默认的隔离级别,故在使用spring tx时,如若不指定事务的隔离级别默认就是repeatable-read。这个隔离级别下有效的防止了脏读和不可重复读,但是不能防止幻读。下面我们来看下这种事务隔离级别下事务锁的情况。

如下测试用例:


@Test


在事务提交前打上断点,分别两次执行测试用例。你会发现,第一次会进断点,第二次执行时直接在execute处就阻塞死了。这个时候事务记录表当前有两个事务,然后有个事务状态是LOCK WAIT的状态。下面是详细记录:

INNODB_TRX:


mysql 事务时长 mysql事务超时时间_mysql当前时间减一天_02


可以看到事务id:4473463正在执行中,事务id:4473464正在请求锁,包括执行的sql语句和事务的线程id等信息

INNODB_LOCKS:


mysql 事务时长 mysql事务超时时间_mysql当前时间减一天_03


锁的信息记录了当前锁定了id为31的记录。事务4473463持有了写锁。锁的索引类型为PRIMARY等信息,所以从这里可以看出mysql首先是根据主键记录来加锁的,当主键不不冲突时,在看唯一索引是否需要加锁。

INNODB_LOCK_WAITS:


mysql 事务时长 mysql事务超时时间_mysql 事务时长_04


上面测试的主要是分别写入相同主键记录的情况,还有其他的一些情况,这里就不贴代码和表记录的图片了,稍微做下总结:

repeatable-read级别:

执行:INSERT INTO USER (id,name,card,age) VALUES (31,'kl','00',66),阻塞事务提交后

针对主键31、和唯一索引列‘00’相关的insert、update、delete都会加锁

读这条记录的操作(where 条件为id或唯一索引记录)不加锁

transaction-serializable级别:

执行:INSERT INTO USER (id,name,card,age) VALUES (31,'kl','00',66),阻塞事务提交后

针对主键31、和唯一索列‘00’相关的insert、update、delete都会加锁

读这条记录的操作(where 条件为id或唯一索引记录)也会加锁

如上,所以加锁互斥的操作,如果由等待锁的时间太长都会抛Lock wait timeout exceeded的异常

结语

mysql数据库是每个做后端开发的不可避免的要去了解的东西,对于mysql事务和数据锁这块博主之前也一直停留在教程和博文上面,这次因为生产的异常,又没有专职的DBA,所以去系统的验证了下事务锁相关的东西,包括去看了mysql的官方文档。收获还是比较大的,对后面分析类似问题有很大的帮助。由于个人能力有限,可能很多地方理解和网上的不一样,主要是带着一种解决自己疑惑的心态去理解的,可能和实际有偏差,欢迎指正。


mysql 事务时长 mysql事务超时时间_mysql 事务时长_05


作者:KL博客