Atomicity(原子性)html
一个事务必须被视为一个不可分割的最小工做单位,整个事务中的全部操做要么所有提交成功,要么所有失败回滚。c++
Consistency(一致性)sql
数据库老是从一个一致性状态转换到另外一个一致性状态,事务执行以前和执行以后都必须处于一致性状态。数据库
Isolation(隔离性)segmentfault
一般来讲,一个事务所作的修改在最终提交以前,对其它事务是不可见的。关于事务的隔离性,数据库提供了多种隔离级别。并发
Durability(持久性)性能
一旦事务提交,则其所作的修改就会永久保存到数据库中。即使是数据库系统遇到故障的状况下也不会丢失。this
一个事务正在对一条记录进行修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态。这时,另外一个事务也来读取同一条记录,若是不加控制,第二个事务读取了这些“脏”数据,并据此作进一步的处理,就会产生未提交的数据依赖关系。spa
时间 | 事务A | 事务B |
---|---|---|
T1 | 开启事务 | 开启事务 |
T2 | 查询帐户余额为1000 | |
T3 | 充值500,余额修改成1500 | |
T4 | 查询余额为1500 | |
T5 | 撤销事务,余额改回1000 | |
T6 | 汇入500,余额修改成2000 | |
T7 | 提交事务 |
一个事务在读取某些数据后的某个时间,再次读取之前读过的数据,却发现其读出的数据已经发生了变动、或者某些记录已经被删除了。3d
时间 | 事务A | 事务B |
---|---|---|
T1 | 开启事务 | 开启事务 |
T2 | select * from user where user_id=100 假设为小明的用户信息 | |
T3 | 将user_id=100的用户信息对应的年龄修改成18 | |
T4 | 提交事务 | |
T5 | 再次查询发现用户的年龄变动了 | |
T6 | ... | |
T7 | 提交事务 |
一个事务按相同的查询条件从新读取之前检索过的数据,却发现其它事务插入了知足其查询条件的新数据。
时间 | 事务A | 事务B |
---|---|---|
T1 | 开启事务 | 开启事务 |
T2 | select * from user where age=18 假设获得两条记录 | |
T3 | 向user表插入一条age=18的新记录 | |
T4 | 提交事务 | |
T5 | 再次查询获得三条记录 | |
T6 | .. | |
T7 | 提交事务 |
SQL标准定义了4类隔离级别,每一种级别都规定了一个事务中所作的修改,哪些在事务内和事务间是可见的,哪些是不可见的。
全部事务均可以看到其它未提交事务的执行结果,该隔离级别通常不会使用。
一个事务只能看到已经提交的事务所作的变动。
确保同一事务的多个实例在并发读取数据时会看到相同的数据行。
彻底串行化读,每次读都须要得到表级共享锁,读写相互阻塞。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read Uncommited | Yes | Yes | Yes |
Read Committed | No | Yes | Yes |
Repeatable Read | No | No | Yes |
Serializable | No | No | No |
脏读、不可重复读和幻读都是数据库读一致性问题,须要由数据库提供必定的事务隔离机制来解决。
(1)锁机制
解决写-写冲突问题。在读取数据前,对其加锁,防止其它事务对该数据进行修改。
悲观锁
每每依靠数据库提供的锁机制。
乐观锁
大可能是基于数据版本记录机制来实现。
(2)MVCC多版本并发控制
解决读-写冲突问题。不用加锁,经过必定机制生成一个数据请求时间点时的一致性数据快照, 并用这个快照来提供必定级别 (语句级或事务级) 的一致性读取。这样在读操做的时候不须要阻塞写操做,写操做时不须要阻塞读操做。
Mysql的大多数事务型存储引擎实现都不是简单的行级锁,基于并发性能考虑,通常都实现了MVCC多版本并发控制。MVCC是经过保存数据在某个时间点的快照来实现的。无论事务执行多长时间,事务看到的数据都是一致的。
读操做分红两类:快照读和当前读。
快照读:简单的select操做属于快照读,不加锁。
当前读:特殊的读操做,插入/更新/删除操做,属于当前读,须要加锁。
innodb存储引擎中,每行数据都包含了一些隐藏字段:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR和DELETE_BIT。
insert
建立一条记录,DB_TRX_ID为当前事务ID,DB_ROLL_PTR为NULL。
delete
将当前行的DB_TRX_ID设置为当前事务ID,DELELE_BIT设置为1。
update 复制一行,新行的DB_TRX_ID为当前事务ID,DB_ROLL_PTR指向上个版本的记录,事务提交后DB_ROLL_PTR设置为NULL。
select
一、只查找建立早于当前事务ID的记录,确保当前事务读取到的行都是事务以前就已经存在的,或者是由当前事务建立或修改的;
二、行的DELETE BIT为1时,查找删除晚于当前事务ID的记录,确保当前事务开始以前,行没有被删除。
Mysql的一致性读是经过read view结构来实现。 read view主要是用来作可见性判断的,它维护的是本事务不可见的当前其余活跃事务。其中最先的事务ID为up_limit_id
,最迟的事务ID为low_limit_id
。
trx_id_t low_limit_id;
/*!< The read should not see any transaction with trx id >= this value. In other words, this is the "high water mark". */
trx_id_t up_limit_id;
/*!< The read should see all trx ids which are strictly smaller (<) than this value. In other words, this is the "low water mark". */
复制代码
能够参考知乎这个答案来理解。low_limit_id应该是当前系统还没有分配的下一个事务ID(从这个语义来更容易理解),也就是目前已经出现过的事务ID的最大值+1。
MySQL 在 RC 隔离级别下是如何实现读不阻塞的? 呵呵一笑百媚生的答案
![]()
假设要读取的行的最后提交事务id(即当前数据行的稳定事务id)为 trx_id,可见性比较过程以下:
Repeatable Read和Read Committed隔离级别都是基于read view来实现,不一样之处在于:
Repeatable Read
read view是在执行事务中第一条select语句的瞬间建立,后续全部的select都是复用这个对象,因此能保证每次读取的一致性。(可重复读的语义)
Read Committed
事务中每条select语句都会建立read view,这样就能够读取到其它事务已经提交的内容。
对于InnoDB来讲,Repeatable Read虽然比Read Committed隔离级别高,开销反而相对较小。