1. ACID属性

  1. Atomicity:原子性。一个事务中的操作是不可再分割执行的,这些操作要么都执行,要么都不执行。
  2. Consistency:一致性。事务必须从一个一致状态转换成另一个一致状态。一致性又有如下几种情况:
    1. 强一致性:读操作可以立即读到提交的更新操作。
    2. 弱一致性:提交的更新操作,不一定立即会被读操作读到,此种情况会存在一个不一致窗口,指的是读操作可以读到最新值的一段时间。
    3. 最终一致性:是弱一致性的特例。事务更新一份数据,最终一致性保证在没有其他事务更新同样的值的话,最终所有的事务都会读到之前事务更新的最新值。如果没有错误发生,不一致窗口的大小依赖于:通信延迟,系统负载等。
    4. 其他一致性变体还有:
    5. 单调一致性:如果一个进程已经读到一个值,那么后续不会读到更早的值。
    6. 会话一致性:保证客户端和服务器交互的会话过程中,读操作可以读到更新操作后的最新值。
  3. Isolation:隔离性。 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  4. Durability:持久性。 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

2. 隔离级别

当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(unrepeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。隔离级别越高,效率越低,系统的并发度越低。
SQL标准的事务隔离级别包括:

  1. 读未提交(read uncommitted):一个事务还没提交时,它做的变更就能被别的事务看到。
  2. 读已提交(read committed):一个事务提交之后,它做的变更才能被其他事务看到。
  3. 可重复读(repeatable read):一个事务执行过程中看到的数据,总和这个事务再启动时看到的数据时一致的。
  4. 串行化(serializable):对于同一行记录,“写”操作会加“写锁”,“读”操作会加“读锁”。当出现读写锁冲突时,后访问的事务必须等待前一个事务执行完成之后才能继续执行。

2.1 隔离级别的实现

在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。
在“读已提交”隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的。
在“读未提交”隔离级别下,直接返回记录上的最新值,没有视图概念。
在“串行化”隔离级别下,直接使用加锁的方式来避免并行访问。

2.2 默认级别

Oracle数据库的默认隔离级别是“读已提交”,MySQL数据库的默认隔离级别是“可重复读”。所以Oracle迁移到MySQL一定要记得将MySQL的隔离级别设置为“读已提交”。将启动参数transaction-isolation的值设置为READ-COMMITTED即可。也可以用show variables来查看当前的值,如下:

  1. show variables like 'transaction_isolation';

3. 事务隔离的实现

展开说明“可重复读”的实现。在MySQL中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
假设一个值从1被按顺序改成了2、3、4,在回滚日志里面就会有类似下面的记录。
图片.png 当前值已经是4了,但是在查询记录的时候,不同时刻启动的事务会有不同的read-view。在视图A、B、C里面,这一个记录的值分别是1、2、4,同一条记录在系统中可以存在多个版本,这就是数据库的多版本并发控制(MVCC)。对于read-view A,要得到1,就必须将当前值依次执行图中所有的回滚操作得到。即使现在有另外一个事务正在将4改成5,这个事务跟read-view A、B、C对应的事务是不会冲突的。
系统会判断,当没有事务需要再用到这些回滚日志时,回滚日志就会被删除。什么时候才不需要了呢?就是当系统里没有比这个回滚日志更早的read-view的时候。
尽量不要使用长事务。长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就导致大量占用存储空间。
在MySQL 5.5之前的版本,回滚日志是跟数据字典一起放在ibdata文件里的,即使长事务最终提交,回滚段被清理,文件也不会变小。除了对回滚段的影响,长事务还占用锁资源,可能拖垮整个库。

4. 事务的启动方式

MySQL的事务启动方式有以下几种:

  1. 显示启动事务语句,begin或start transaction。配套的提交语句是commit,回滚语句是rollback。
  2. set autocommit=0或者false,这个命令会将这个线程的自动提交关闭。意味着如果你只执行一个select语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在到你主动执行commit或rollback语句,或者断开连接。

有些客户端连接框架默认在连接成功后先执行一个set autocommit=false的命令。这会导致接下来的查询都在事务中,如果是长连接,就导致了意外的长事务。

5. 查询长事务

可以在information_schema库的innodb_trx这个表中查询长事务,比如下面这个语句,用于查询持续时间超过60s的事务。

  1. select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(), trx_started)) > 60;