事务是由一组SQL语句组成的逻辑处理单元,具有4个属性,通常简称为事务的ACID属性。
A (Atomicity) 原子性:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。使用 undo log ,从而达到回滚 C (Consistency) 一致性:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。使用 redo log,从而达到故障后恢复 I (Isolation)隔离性:一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。使用锁以及MVCC,运用的优化思想有读写分离,读读并行,读写并行 D (Durability) 持久性:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。通过回滚,以及恢复,和在并发环境下的隔离做到一致性。
没有事务隔离,可能会导入如下问题:
- 更新丢失(Lost Update):事务A和事务B选择同一行,然后基于最初选定的值更新该行时,由于两个事务都不知道彼此的存在,就会发生丢失更新问题
- 脏读(Dirty Reads):事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
- 不可重复读(Non-Repeatable Reads):事务 A 多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
- 幻读(Phantom Reads):幻读与不可重复读类似。它发生在一个事务A读取了几行数据,接着另一个并发事务B插入了一些数据时。在随后的查询中,事务A就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
事务隔离级别
SQL-92标准中定义的四种隔离级别。在MySQL中,默认的隔离级别是REPEATABLE-READ(可重复读),并且解决了幻读问题(利用Gap Locks和Next-key可以阻止其他事务在锁定区间内插入数据)。不可重复读重点在于Update和delete,而幻读的重点在于insert
- 读未提交:一个事务可以读取到另一个事务未提交的修改。这会带来脏读,幻读,不可重复读问题
- 读已提交:一个事务只能读取另一个事务已经提交的修改。其避免了脏读,仍然存在不可以重复读和幻读问题
- 可重复读:同一个事务中多次读取相同的数据返回的结果是一样的。其避免了脏读和不可重复读问题,但是幻读依然存在
- 串行化:事务串行之行。避免了以上所有问题
【MVCC】
MVCC (MultiVersion Concurrency Control) 叫做多版本并发控制。InnoDB的 MVCC ,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间,在实际操作中,存储的并不是时间,而是事务版本号,每开启一个新事务,事务的版本号就会递增。
增删改查中对版本号的作用如下:
- select:读取创建版本小于或等于当前事务版本号,并且删除版本为空或大于当前事务版本的记录。这样可以保证在读取之前记录都是存在的
- insert:将当前事务的版本号保存至行的创建版本号
- update:新插入一行,并以当前事务版本号作为新行的创建版本号,同时将原记录行的删除版本号设置为当前事务版本号
- delete:将当前事务版本号保存至行的删除版本号
快照读和当前读:普通的select就是快照读,而update,delete,insert,select...LOCK In SHARE MODE,SELECT...for update就是当前读
- 快照读:读取的是快照版本,也就是历史版本
- 当前读:读取的是最新版版
存在这的情况,两个事务,对同一条数据做修改。最后结果应该是哪个事务的结果呢,肯定要是时间靠后的那个,并且更新之前要先读数据,这里所说的读和上面说到的读不一样,更新之前的读叫做“当前读”,总是当前版本的数据,也就是多版本中最新一次提交的那版。
假设事务A执行 update 操作, update 的时候要对所修改的行加行锁,这个行锁会在提交之后才释放。而在事务A提交之前,事务B也想 update 这行数据,于是申请行锁,但是由于已经被事务A占有,事务B是申请不到的,此时,事务B就会一直处于等待状态,直到事务A提交,事务B才能继续执行,如果事务A的时间太长,那么事务B很有可能出现超时异常。
【Gap Lock && Next-Key Lock】
并发写问题的解决方式就是行锁,而解决幻读用的也是锁,叫做间隙锁,MySQL 把行锁和间隙锁合并在一起,解决了并发写和幻读的问题,这个锁叫做 Next-Key锁。
redo log 与 undo log
【redo log】
叫做重做日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer,在内存中)以及重做日志文件(redo log,在磁盘中)。当事务提交之后会把所有修改信息都会存到该日志中。
start transaction; select balance from bank where name="zhangsan"; // 生成 重做日志 balance=600 update bank set balance = balance - 400; // 生成 重做日志 amount=400 update finance set amount = amount + 400; commit;
mysql 为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到Boffer Pool(缓冲池)里头,把这个当作缓存来用。然后使用后台线程去做缓冲池和磁盘之间的同步。如果还没来得及同步的时候宕机或断电了,这样会导致丢部分已提交事务的修改信息!所以引入了redo log来记录已成功提交事务的修改信息,并且会把redo log持久化到磁盘,系统重启之后在读取redo log恢复最新数据。redo log是用来恢复数据的 用于保障,已提交事务的持久化特性.
【undo log】
叫做回滚日志,用于记录数据被修改前的信息。他正好跟前面所说的重做日志所记录的相反,重做日志记录数据被修改后的信息。undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。
每次写入数据或者修改数据之前都会把修改前的信息记录到 undo log。undo log 记录事务修改之前版本的数据信息,因此假如由于系统错误或者rollback操作而回滚的话可以根据undo log的信息来进行回滚到没被修改前的状态。undo log是用来回滚数据的用于保障 未提交事务的原子性