03 | 事务隔离:为什么你改了我还看不见?
MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!(摘抄自菜鸟教程)。
由上文可知,MySQL事务在效果上可以视为一个为达到某个操作目的而完成一系列操作的函数。在执行过程上,又可视其为较负责的原语,即不可被打断。
简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在 MySQL 中,事务支持是在引擎层实现的。同时,事务支持并非所有的存储引擎都支持,但MySQL的默认存储引擎InnoDB是支持的。因此,本节课的举例均通过InnoDB。
隔离性与隔离级别
首先,介绍一下事务的四个特性,即ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性)。
而本节要介绍的就是隔离性。当数据库被多个事务自由访问或操作时,事务间的冲突是不可避免的。因此为了解决脏读、不可重复读、幻读等问题,就有了隔离级别的概念。
SQL标准的事务隔离级别有:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。其隔离程度依次递增,但效率却依次递减。具体解释如下:
读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。
事务隔离的实现
事务隔离是通过回滚操作实现的。当某表的记录值发生变化时,MySQL将会将其变化的过程进行记录形成回滚操作。当存在隔离级别较高的事务时,该事务建立的视图中此表的记录值将回退到较早版本以保证达到相应隔离级别。
当没有事务再需要用到这些回滚日志时,回滚日志才会被删除。
基于以上,使用长事务应该尽量被避免。
长事务,意味着回滚日志更多,需要回滚到更早版本的状态。因此,长事务提交前,相关的回滚日志都将保留,这就导致了大量内存空间被占据。长事务还占用锁资源,也可能会拖垮整个库。
事务的启动方式
显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。
set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接。
由于 set autocommit=0容易产生长事务,因此建议将其赋值为1。