事务4个特性
- 事务满足ACID原则,即
- A: Atomicity(原子性)
一个事务中的所有操作,要么全部完成,要么全部不完成,最小的执行单位。 - C:Consistency(一致性)
事务执行前后,都处于一致性状态。 - I:Isolation(隔离性)
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。 - D:Durability(持久性)
事务执行完成后,对数据的修改就是永久的,即便系统故障也不会丢失。
事务的隔离级别
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读(Read Uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read Committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable Read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable) | 不可能 | 不可能 | 不可能 |
未提交读(Read Uncommitted)
- 一个事务能够读取到 别的事务中没有提交的更新数据。事务可以读取到未提交的数据,这也被称为脏读(dirty read)。所以这种级别很有可能读到脏数据,隔离级别最低。
已提交读(Read Committed)
- 一个事务只能读取到别的事务提交的更新数据。
- 在RC级别中,数据的读取都是不加锁的,但是数据的写入、修改和删除是需要加锁的。
- 数据库通过给数据加行锁,达到已提交读级别,但仍会出现不可重复读。
- 注意MySQL在RC的优化:数据库通过有索引的列加行锁,如果是非索引列,那么MySql会在存储引擎层面给整张表的行加锁后返回,再由MySQL Server层进行过滤。实际使用过程中,MySQL做了一些改进,在MySQL Server过滤条件,发现不满足后,会调用unlock_row方法,把不满足条件的记录释放锁 (违背了二段锁协议的约束)。这样做,保证了最后只会持有满足条件记录上的锁,但是每条记录的加锁操作还是不能省略的。可见即使是MySQL,为了效率也是会违反规范的。
可重复读(Repeatable Read)
- 保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务的影响。这种隔离级别可能出现幻读。(mysql默认的)
- 什么是不可重复读:
2个事务A,B,事务A在查询过程中,事务B修改了事务A的数据行,导致事务A在第一次读和最后一次读的结果不一样,即不可重复读 - 不可重复读重点在于update和delete,而幻读的重点在于insert。
快照读(snapshot read)
- 读取历史数据的方式,我们叫它快照读 (snapshot read)
-为了保证快照读的可重复读,数据库可以采用2种方式避免,大部分采用乐观锁
- 悲观锁
正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据。 - 乐观锁(MVCC)
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
- MySQL的InnoDB的MVCC会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。 在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。
大致意思:
InnoDB的MVCC在snapshot read时,在事务的前后都会只查询出当前版本号之前的数据记录,版本号之后的删除,不查询;新增删除时,都是操作相关当前事务版本号;修改时,先新增当前事务版本号的记录,再保存当前事务版本号到原来删除的行
当前读(current read)
- 读取数据库当前版本数据的方式,叫当前读 (current read)
- 特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁。
- 为了解决当前读中的幻读问题,MySQL事务使用了Next-Key锁。
Next-Key锁
- Next-Key锁是行锁和GAP(间隙锁)的合并
- 行锁防止别的事务修改或删除,GAP锁防止别的事务新增,行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题。
- GAP(间隙锁)
GAP锁是锁的索引区间,会根据索引加主键排序,再确定区间,如果索引相同,则根据主键排序,预测更新后的数据排序,需要更新的数据在2个索引加主键中间的则会等待,因为GAP锁的存在,如果超过这个区间的,则可以插入。
可串行化(Serializable)
- 不允许事务并发执行,强制事务串行执行,就是在读取的每一行数据上都加上了锁,读写相互都会阻塞。这种隔离级别最高,是最安全的,性能最低,不会出现脏读,不可重复读,幻读,丢失更新。