一、简介
事务(Transaction)是数据库区别于文件系统的重要特性之一。在文件系统中,如果正在写件,但是操作系统突然崩溃了,这个文件就很有可能被破坏。当然,有一些机制可以把文件恢复到某个时间点。不过,如果需要保证两个文件同步,这些文件系统可能就显得无能为力了。例如,在需要更新两个文件时,更新完一个文件后,在更新完第二个文件之前系统重启了,就会有两个不同步的文件。 这正是数据库系统引入事务的主要目的 ∶ 事务会把数据库从一种一致状态转换为另一种一致状态。在数据库提交工作时,可以确保要么所有修改都已经保存了,要么所有修改都不保存。 InnoDB 存储引擎中的事务完全符合 ACID 的特性。ACID是以下 4个词的缩写;□原子(atomicity)□一致性(consistency)□隔离性(isolation)□持久性(durability)
二、ACID
1.原子性
所有操作为一个不可拆分的整体,同时成功或者同时失败,例如电商系统下单扣款和减少库存就需要保证他的原子性
实现机制:回滚日志
想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,而在 MySQL 中,恢复机制是通过回滚日志(undo log)实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后在对数据库中的对应行进行写入。
这个过程其实非常好理解,为了能够在发生错误时撤销之前的全部操作,肯定是需要将之前的操作都记录下来的,这样在发生错误时才可以回滚。
回滚日志除了能够在发生错误或者用户执行 ROLLBACK
时提供回滚相关的信息,它还能够在整个系统发生崩溃、数据库进程直接被杀死后,当用户再次启动数据库进程时,还能够立刻通过查询回滚日志将之前未完成的事务进行回滚,这也就需要回滚日志必须先于数据持久化到磁盘上,是我们需要先写日志后写数据库的主要原因。
回滚日志并不能将数据库物理地恢复到执行语句或者事务之前的样子;它是逻辑日志,当回滚日志被使用时,它只会按照日志逻辑地将数据库中的修改撤销掉看,可以理解为,我们在事务中使用的每一条 INSERT
都对应了一条 DELETE
,每一条 UPDATE
也都对应一条相反的 UPDATE
语句。
2.一致性
一致性指事务将数据库从一种状态转变为下一种一致的状态。在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。例如,在表中有一个字段为姓名,为唯一约束,即在表中姓名不能重复。如果一个事务对姓名字段进行了修改,但是在事务提交或事务操作发生回滚后,表中的姓名变得非唯—了,这 就破坏了事务的一致性要求,即事务将数据库从一种状态变为了一种不一致的状态。因此,事务是一致性的单位,如果事务中某个动作失败了,系统可以自动撤销事务——返回初始化的状态
3.隔离性
隔离性还有其他的称呼,如并发控制(concurrency control)、可串行化(serializability)、锁(locking)等。事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,通常这使用锁 来实现。当前数据库系统中都提供了一种粒度锁(granular lock)的策略,允许事务仅锁 住一个实体对象的子集,以此来提高事务之间的并发度。
实现机制-锁:
锁是一种最为常见的并发控制机制,在一个事务中,我们并不会将整个数据库都加锁,而是只会锁住那些需要访问的数据项, MySQL 和常见数据库中的锁都分为两种,共享锁(Shared)和互斥锁(Exclusive),前者也叫读锁,后者叫写锁。
4.持久性
事务一旦提交,其结果就是永久性的。即使发生宕机等故障,数据库也能将数据恢复。需要注意的是,只能从事务本身的角度来保证结果的永久性。例如,在事务提交后,所有的变化都是永久的。即使当数据库因为崩溃而需要恢复时,也能保证恢复后提交的数据都不会丢失。但若不是数据库本身发生故障,而是—些外部的原因,如 RAID 卡损坏、自然灾害等原因导致数据库发生问题,那么所有提交的数据可能都会丢失
实现机制:重做日志
与原子性一样,事务的持久性也是通过日志来实现的,MySQL 使用重做日志(redo log)实现事务的持久性,重做日志由两部分组成,一是内存中的重做日志缓冲区,因为重做日志缓冲区在内存中,所以它是易失的,另一个就是在磁盘上的重做日志文件,它是持久的。
三、事务的执行过程
- Active:事务的初始状态,表示事务正在执行;
- Partially Commited:在最后一条语句执行之后;
- Failed:发现事务无法正常执行之后;
- Aborted:事务被回滚并且数据库恢复到了事务进行之前的状态之后;
- Commited:成功执行整个事务;
四、事务的隔离级别
1. ISOLATION_READ_UNCOMMITTED:(读未提交)这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。 这种隔离级别会产生脏读,不可重复读和幻像读。
2. ISOLATION_READ_COMMITTED:(读已提交,oracle和大多数数据库默认级别)保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
3. ISOLATION_REPEATABLE_READ(可重复读mysql默认隔离级别):这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。 它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
4. ISOLATION_SERIALIZABLE(序列化,效率低用于访问量小的系统):这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读。
五、mysql如何解决不可重复读和幻读?
脏读的解决办法:
mysql利用mvcc快照读,读到的是快照解决了脏读
可重复读的解决办法:
首先不可重复读重点在于update,在可重复读中,该sql第一次读取到数据后,就将这些数据加行锁,其它事务无法修改这些数据,就可以实现可重复读了
幻读的解决办法:
mysql默认隔离级别是可重复读,上述解决不可重复读的方法可以锁住行却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会 发现莫名其妙多了一条之前没有的数据,这就是幻读
幻读的重点在于insert和delete所以mysql使用间隙锁,间隙锁是innodb中行锁的一种,顾名思义可以将行之间的缝隙锁住,也就是说锁住的是一个范围,从而不可新增数据可以在一定程度上解决幻读问题,但是锁扩大会出现问题可以设置innodb_locks_unsafe_for_binlog参数关闭间隙锁
mysql使用间隙锁解决幻读的说法其实也不完全对如果是普通的范围查找其实是没有间隙锁的
是通过mvcc的快照读机制解决的幻读问题,只有当前读如select... for update或者select . .. lock这种当前读的方式才会有间隙锁