事务是Mysql InnoDB 引擎的一个重要特点,具有ACID 四个特性。
- 原子性(Atomicity):事务的所有操作,要么全部完成,要么全部不完成,不会结束在某个中间环节。
- 一致性(Consistency):事务开始之前和事务结束之后,数据库的完整性限制未被破坏。
- 隔离性(Isolation):两个或者多个事务的执行是互不干扰的,一个事务不可能看到其他事务运行时,中间某一时刻的数据。
- 持久性(Durability):事务完成之后,事务所做的修改进行持久化保存,不会丢失。
平时项目和工作中,会很频繁使用到事务,但是,一些细节如果不稍加注意,是很容易出现问题的。
观察事务
示例一
这是一段很常见的代码,逻辑也很简单,首先开启事务,如果try
代码块没有异常,提交事务; 如果try
代码块遇到异常,事务进行回滚。
1 | Route::get("test", function () { |
在这个过程中,我们只知道,手动选择了开启、提交或者回滚事务,但是对于事务执行的整个过程,比如:什么时候开启了事务、什么时候提交的事务、我们都是毫无感知的。
那有没有什么办法,可以看到整个过程呢?
答案是有的。
示例二
在新的代码示例中,只加了一行代码,它的作用是延缓事务提交(这里 sleep 15秒,便于观察)。
1 | Route::get("sleep15", function () { |
同时需要配合Mysql 客户端,执行一个SQL 语句,查看正在进行中的事务:
1 | SELECT * FROM information_schema.INNODB_TRX; |
可以在发送请求之前先执行一次:
发送请求之后执行一次:
请求结束之后再执行一次:
可以很清晰地看到,事务从无到有再到无的整个过程:
- 发送请求之前,此时事务还没有开启。
- 发送请求之后,此时事务已开启,但因为sleep 的原因,没法直接提交或者回滚,只能一直开启事务等待。
- sleep 结束,此时事务进行提交或回滚,请求结束。
往往因为事务使用不当,而造成锁表等问题,原因大多出在了第二步上。
观察锁
Mysql 的锁(这篇笔记就不对锁的分类具体展开说明了),往往都是伴随事务出现。
为了演示『锁』是如何产生的,这次需要同时用到上面的两个示例。
首先请求127.0.0.1:8000/sleep15
,在请求结束之前,请求127.0.0.1:8000/test
。
此时观察请求状态,可以发现两个接口都没有马上响应。
再次打开Mysql 客户端,查看当前正在进行中的事务:
不出意外,可以发现此时等待的事务变成了两个。
通过trx_id
大小,可以判断出,先请求的127.0.0.1:8000/sleep15
事务当前正在运行中,而后面请求的127.0.0.1:8000/test
事务,则是被锁住等待,等待前面的事务释放(提交或者回滚)。
同理,如果此时请求不是两个,而是多个,相应的,被锁住的事务就是多个。
结语
这个问题看着挺简单的,但实际开发时,往往容易被忽略。
过早开启事务,提交或者回滚事务之前,穿插许多其他业务逻辑,如果其他某个逻辑超时,则会导致事务不能及时释放,从而出现连锁反应。