极客--事务隔离为什么你改了我还看不见?
查询余额、做加减法、更新余额等,这些操作必须保证是一体的,不然等程序查完之后,还没做减法之前,你这100块,完全可以借着这个时间再查一次,然后再给另外一个朋友转账,如果银行这么整,不就乱了么?
- 事务就是保证一组数据库操作,要么全部成功,要么全部失败。在Mysql中,事务支持是在引擎层实现的。Mysql是一个支持多引擎的系统,但并不是所有的引擎都支持事务,比如Mysql原生MyISAM引擎不支持事务,
隔离性与隔离级别
-
提到事务,ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性),今天我们就说其中I,也就是隔离性
-
读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到.
-
读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
-
可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下。未提交变更对其他事务也是不可行。
-
串行化,顾名思义,"写"会加写锁,"读"会加读锁。当读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行
可重复读隔离级别下MVCC
-
数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在可重复读隔离级别下,这个视图是在事务启动创建的,整个事务存在期间都用这个视图。在读提交隔离级别下,这个视图是每个SQL语句开始执行的时候创建。读未提交的隔离级别下直接返回记录上的最新值,没有视图概念;而串行化隔离级别下直接用加锁的方式来避免并行访问。
-
Orecal默认隔离级别是读提交,迁移过程的时候需要修改Mysql隔离级别
`
mysql> show variables like 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+`
事务隔离实现
-
实际上Mysql每条记录在更新的时候都会同时记录记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
-
假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。

-
当前值是4,但是查询这条记录的时候,不同时刻启动的事务会有不同的read-view。如图看到的,在视图A、B、C里面,这一个记录的值分别是1、2、4,同一条记录在系统可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于read-view A就必须将当前值一次执行图中所有回滚操作得到。
-
同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。
-
回滚日志再不需要的时候删除,当系统没有比这个回滚日志更早的read-view的时候
-
尽量不要使用长事务,由于这些事务可能随时访问数据库里面任何数据,所以这个事务提交之前,数据库里面它可能用到回滚记录都必须保留,这就会导致大量占用存储空间。
-
5.5以及之前版本,回滚日志跟数据字典一起放在ibdata文件里 ,即使长事务最终提交,回滚段被清理,文件也不会变小。
-
对于回滚段的影响,长事务还会占用锁资源,也可能拖垮整个库
事务的启动的方式
Mysql事务的启动方式有以下几种:
-
显示启动事务语句,begin或start transaction。配套提交语句commit,回滚语句是rollback
-
set autocommit=0,这个命令会将这个线程自动提交关闭掉。意味着如果你执行一个select语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到commit或rollback
-
如果客户端连接框架默认连接成功后先执行一个set autocommit=0的命令。这就导致接下来的事务,如果是长连接,就导致意外长事务
因此,我会建议你总是使用 set autocommit=1, 通过显式语句的方式来启动事务
你可以在 information_schema 库的 innodb_trx 这个表中查询长事务,比如下面这个语句,用于查找持续时间超过 60s 的事务。
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
避免长事务的方案
从数据库层面
- 从数据库方面:
- 设置autocommit=1,不要设置为0
- 写脚本监控information_schemal.innodb_trx表中的数据内容,发现长事务,kill掉
- 配置SQL语句所能执行的最大运行时间,如果查过最大运行时间后,中断这个运行事情长的SQL语句。
- 设置回滚表空单独存放,便于回收表空间
- 从业务代码方面:
- 确认是否使用了autocommit=0的配置,如果有关闭它,然后再业务代码中手动的使用begin;commit来操作。
- 检查业务逻辑代码,能拆分为小事务的不要用大事务。
- 检查代码,把没有必要的select语句被事务包裹的情况去掉。

浙公网安备 33010602011771号