事务就是保证一组数据库的操作,要么全部成功,要么全部失败。
MySQL中对于事务的支持是在引擎层中实现的,通过InnoDB来剖析一下MySQL在事务支持方面的特定实现。
隔离性和隔离级别
事务的ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性)
- 原子性(Atomicity):事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚;回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。
- 一致性(Consistency):数据库在执行前后都保持一致性状态,在一致性状态下,所有事务对于一个数据的读取结果都是相同的;
- 隔离性(Isolation):一个事务所做的修改,在最终提交前对于其它事务都是不可见的;
- 持久性(Durability):一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失,使用重做日志来保证持久性。
当数据库中有多个事务同时执行的时候,就会出现脏读(dirty read)、不可重复读(non-repeatable read)、**幻读(phantom read)**等问题,
- 脏读(dirty read):事务A修改了一个数据,事务B读取了这个数据,但如果事务A撤销了这次操作,事务B读取的就是脏数据;
- 不可重复读(non-repeatable read):事务B读取一个数据,事务A对该数据进行了修改,如果 事务B 再次读取这个数据,此时读取的结果和第一次读取的结果不同;
- 幻读(phantom read):事务A读取某个范围的数据,事务B在这个范围内插入新的数据,事务A再次读取这个范围的数据,此时读取的结果和第一次读取的结果是不同的;
- 丢失修改:两个事务都对数据进行了修改,事务A先修改,事务B后修改覆盖了事务A的修改;
为了解决这些问题,就提出了隔离级别的概念。
对于隔离级别和效率来说需要寻找一个平衡点,隔离的越严,效率就会越低。
SQL标准的事务隔离级别包括:
-
读未提交(read uncommitted)
一个事务还没提交,它的变更就能被其它事务看见; -
读提交(read committed)
一个事务在提交之后,它的变更才能被其它事务看见; -
可重复读(repeatable read)
一个事务在执行过程中看到的数据和这个事务在启动时看到的数据是一致的,未提交的变更,其它事务也是看不见的; -
串行化(serializable)
对于同一行数据,“写”会加“写锁”,“读”会加“读锁”,当读写冲突时,后访问的事务必须等前一个事务执行结束后开始执行。
讨论一下,四种隔离级别下对应的值: -
读未提交,v1=v2=v3=2;
-
读提交,v1=1,v2=v3=2;
-
可重复读,v1=1,v2=v3=2;
-
串行化,v1=v2=1,v3=2;
在不同的隔离级别下,数据库的行为是不同的,MySQL的默认隔离级别为“读提交”;
配置方式,将启动参数transaction-isolation的值设置为READ-COMMITTED;
事务隔离的实现
MySQL中,每条记录在更新的时候都会记录一条回滚操作,记录上最新的值,通过回滚操作,都可以得到前一个状态的值;
假设一个值从1按顺序改为2、3、4,在回滚日志中就会有记录:
对于read-view A,要得到1,就必须将当前值依次执行图中所有的回滚操作;
系统会判断当前,当没有事务需要这些回滚日志的时候,回滚日志就会被删除;
在MySQL中不建议使用长事务的原因:
长事务的存在意味着系统里面会存在很老的事务视图,而这些事务随时可能访问数据库里面的数据,而这些事务在提交之前,数据库里面它可能用到的回滚记录都必须保留,就会占用大量的存储空间。
事务的启动方式
MySQL的事务启动方式分为以下几种:
- 显示启动事务语句,begin或start transaction,提交语句是commit,回滚语句是rollback;
- set autocommit=0,关闭线程的自动提交,在事务启动之后,直到主动执行commit或rollback,或者断开连接;
建议将set autocommit=1,通过显示语句的方式来启动事务:
在autocommit=1的情况下,采用begin显示启动事务,执行commit则提交事务,好处就是能明确的知道每个语句是否处于事务之中。