事务及隔离级别
1.事务
1.1事务的ACID属性
-
原子性(Atomicity): 一个事务中的操作要求要么全部执 行,要么全部不执行.
-
一致性(Consistency): 在外部看来,数据库中的数据总 是正确的.
-
隔离性(Isolation): 尽管多个事务在并发执行,但从外 部看来,具有多个事务串行执行的效果.
-
持久性(Durability):一个事务一旦提交了,即使随后发 生故障,其结果在数据库中不会丢失.
2.隔离级别
2.1四种隔离级别
- 读未提交、读提交、可重复读、串行化
- 读未提交(Read-uncommitted):一个事务还没提交时,它做的变更就能被别的事务看到。
- 读提交(Read-committed):一个事务提交之后,它做的变更才会被其他事务看到。
- 可重复读(Repeatable-read):一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然未提交变更对其他事务也是不可见的。
- 串行化(Serializable):对于同一行记录,“写”会加写锁,读会加“读锁”,当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
2.2事务的并发问题
-
脏读(dirty read):如果一个事务读到了另一个未提交事务修改过的数据。
事务A 事务B 事务开始 余额100元 事务开始 存入100元 查询余额为200元 提交事务 回滚 -
不可重读(non-repeatable read):如果一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一行修改并提交后,该事务都能查询得到最新值。
事务A 事务B 查询余额为200元,事务先不提交 取出100元,并提交事务 再次查询余额为100元 -
幻读(phantom read):如果一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。
事务A 事务B 查询表中有几条数据,结果为100条 插入一条新数据 查询表中有几条数据,结果为101条 -
不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
2.3隔离级别会发生的事务并发问题
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | ✔ | ✔ | ✔ |
读提交 | ✖ | ✔ | ✔ |
可重复读 | ✖ | ✖ | ✔ |
串行化 | ✖ | ✖ | ✖ |
2.4隔离级别实现的原理
- 排他锁(X锁,Exclusive Lock):T事务给A对象加X锁后,只允许T事务读取和修改A对象,其他事务不能再对A对象加任何类型的锁。
- 共享锁(S锁,Share Lock):主要用于读数据,一数据加了S锁还可以加S锁,但不能加X锁。
- 更新锁(Update Lock):更新锁是为了防止死锁而设立的。当SQL Server 准备更新数据时,它首先对数据对象作更新锁锁定,这样数据将不能被修改,但可以读取。等到SQL Server 确定要进行更新数据操作时,它会自动将更新锁换为排他锁。但当对象上有其它锁存在时,无法对其作更新锁锁定。
- 隔离级别的原理:(通过加锁或MVCC)
- 读未提交的原理:读取的时候不加锁;更新的时候加排他锁,事务结束就释放。
- 读提交的原理:读取的时候加共享锁,读完就释放;更新的时候加排他锁,事务结束就释放。
- 可重复读的原理:读取的时候加共享锁,事务结束才释放;更新的时候加排他锁,事务结束就释放。
- 串行化的原理:严格有序串行执行,事务不能并发执行。
3.MVCC(Multi-Version Concurrency Control多版本并发控制)
3.1出现原因
- “串行化”隔离级别,虽然不会出错,但是效率实在太低了。 避免使用!!
- “可重复读”,虽然会出现幻读,但是也能忍受。但为了实现可重复读, 需要在事务中对读操作加锁,并且得持续到整个事务结束,效率也一般,可选择使用。
3.2实现原理
- MVCC的实现,通过保存数据在某个时间点的快照来实现的。这意味着一个事务无论运行多长时间,在同一个事务里能够看到数据一致的视图。根据事务开始的时间不同,同时也意味着在同一个时刻不同事务看到的相同表里的数据可能是不同的。
- 隐藏字段:
- DB_TRX_ID(6字节):表示最近一次对本记录行作修改(insert | update)的事务ID。至于delete操作,InnoDB认为是一个update操作,不过会更新一个另外的删除位,将行表示为deleted。并非真正删除。
- DB_ROLL_PTR(7字节):回滚指针,指向当前记录行的undo log信息。
- DB_ROW_ID(6字节):随着新行插入而单调递增的行ID。理解:当表没有主键或唯一非空索引时,innodb就会使用这个行ID自动产生聚簇索引。如果表有主键或唯一非空索引,聚簇索引就不会包含这个行ID了。
- Read View结构
- 其实Read View(读视图),跟快照、snapshot是一个概念。
- Read View主要是用来做可见性判断的, 里面保存了“对本事务不可见的其他活跃事务”。
- 其中包括几个变量:
- low_limit_id:目前出现过的最大的事务ID+1,即下一个将被分配的事务ID。
- up_limit_id:活跃事务列表trx_ids中最小的事务ID,如果trx_ids为空,则up_limit_id 为 low_limit_id。因为trx_ids中的活跃事务号是逆序的,所以最后一个为最小活跃事务ID。
- trx_ids:Read View创建时其他未提交的活跃事务ID列表。意思就是创建Read View时,将当前未提交事务ID记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。
- creator_trx_id:当前创建事务的ID,是一个递增的编号。
- Undo log
- undo log是为回滚而用,具体内容就是copy事务前的数据库内容(行)到undo buffer,在适合的时间把undo buffer中的内容刷新到磁盘。undo buffer与redo buffer一样,也是环形缓冲,但当缓冲满的时候,undo buffer中的内容会也会被刷新到磁盘;与redo log不同的是,磁盘上不存在单独的undo log文件,所有的undo log均存放在主ibd数据文件中(表空间),即使客户端设置了每表一个数据文件也是如此。
3.3一般认为的MVCC
- 每行数据都存在一个版本,每次数据更新时都更新该版本
- 修改时Copy出当前版本随意修改,各事务之间无干扰
- 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)
3.4INNODB的实现方式
- 事务以排他锁的形式修改原始数据
- 把修改前的数据存放于undo log,通过回滚指针与主数据关联
- 修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)
3.5区别
- 二者最本质的区别是,当修改数据时是否要排他锁定,如果锁定了还算不算是MVCC?
- Innodb的实现真算不上MVCC,因为并没有实现核心的多版本共存
- undo log中的内容只是串行化的结果,记录了多个事务的过程,不属于多版本共存。
- 但理想的MVCC是难以实现的,当事务仅修改一行记录使用理想的MVCC模式是没有问题的,可以通过比较版本号进行回滚;但当事务影响到多行数据时,理想的MVCC就无能为力了。
- 比如,如果Transaciton1执行理想的MVCC,修改Row1成功,而修改Row2失败,此时需要回滚Row1,但因为Row1没有被锁定,其数据可能又被Transaction2所修改,如果此时回滚Row1的内容,则会破坏Transaction2的修改结果,导致Transaction2违反ACID。
- 理想MVCC难以实现的根本原因在于企图通过乐观锁代替二段提交。修改两行数据,但为了保证其一致性,与修改两个分布式系统中的数据并无区别,而二提交是目前这种场景保证一致性的唯一手段。二段提交的本质是锁定,乐观锁的本质是消除锁定,二者矛盾,故理想的MVCC难以真正在实际中被应
用,Innodb只是借了MVCC这个名字,提供了读的非阻塞而已。