数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。 事务由事务开始与事务结束之间执行的全部数据库操作组成。
并非任意的对数据库的操作序列都是数据库事务。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的。
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交(commit)到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。这是由于查询时系统中其他事务修改的提交而引起的。比如事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。
一种更易理解的说法是:在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间。由于第二个事务的修改,那么第一个事务读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。
幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样.一般解决幻读的方法是增加范围锁RangeS,锁定检锁范围为只读,这样就避免了幻读。
幻读是不可重复读的一种特殊场景:当事务没有获取范围锁的情况下执行SELECT … WHERE操作可能会发生幻读。
为了解决脏读、幻读、不可重复读等异常情况,定义了 4 种事务的隔离级别:可串行化(Serializable)、可重复读(Repeatable reads)、提交读(Read committed)、未提交读(Read uncommitted)。
也称读未提交,是最低的隔离级别。在这种事务隔离级别下,一个事务可以读到另外一个事务未提交的数据。这种隔离级别下会存在幻读、不可重复读和脏读的问题。
也称读已提交,在一个事务修改数据过程中,如果事务还没提交,其他事务不能读该数据。所以,这种隔离级别是可以避免脏读的发生的。
比提交读更高一个级别的隔离级别就可以解决不可重复读的问题。这种隔离级别就叫可重复读。但是这种隔离级别没办法彻底解决幻读。
是最高的隔离级别,可以解决脏读,幻读,不可重复读的问题
隔离级别 | 脏读(DR) | 不可重复读(NR) | 幻读(PR) |
---|---|---|---|
读未提交(RU) | Y | Y | Y |
读已提交(RC) | N | Y | Y |
可重复读(RR) | N | N | Y |
串行(SERIALIZABLE) | N | N | N |
MySQL 默认采用可重复读(RR)
首先从四种隔离级别中排除 Serializable 和 Read Uncommitted 这两种,主要是因为这两个级别一个隔离级别太高,一个太低。太高的就会影响并发度,太低的就有脏读现象。
CREATE TABLE `t1` (
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
KEY `b` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
insert into t1 values(10,1);
接下来开始执行两个事务:
事务 1 | 事务 2 |
---|---|
set session transaction isolation level read committed; | |
set autocommit=0; | set session transaction isolation level read committed; |
set autocommit=0; | |
begin; | begin; |
delete from t1 where b< 100; | |
insert into t1 values (10,99); | |
commit; | |
commit; | |
以上两个事务执行之后,数据库里面的记录会只有一条记录(10,99)。 | |
即使 Session 1 的删除操作在 Session 2 的插入操作之后提交,由于 READ COMMITTED 的隔离级别,Session 2 的插入操作不会看到 Session 1 的删除操作,所以最后数据库中仍然会留下 Session 2 插入的记录 (10,99)。 | |
以上两个事务执行之后,会在bin log中记录两条记录,因为事务2先提交,所以insert into t1 values(10,99);会被优先记录,然后再记录delete from t1 where b < 100; | |
这样bin log同步到备库之后,SQL语句回放时,会先执行insert into t1 values(10,99);,再执行delete from t1 where b < 100;。 | |
这时候,数据库中的数据就会变成 EMPTY SET,即没有任何数据。这就导致主库和备库的数据不一致了!!! | |
为了避免这样的问题发生。MySQL就把数据库的默认隔离级别设置成了Repetable Read,那么,Repetable Read的隔离级别下是如何解决这样问题的那? | |
那是因为Repetable Read这种隔离级别,会在更新数据的时候不仅对更新的行加行级锁,还会增加GAP锁和临键锁。上面的例子,在事务2执行的时候,因为事务1增加了GAP锁和临键锁,就会导致事务2执行被卡住,需要等事务1提交或者回滚后才能继续执行。 | |
除了设置默认的隔离级别外,MySQL 还禁止在使用 statement 格式的 bin log 的情况下,使用 READ COMMITTED 作为事务隔离级别。 | |
一旦用户主动修改隔离级别,尝试更新时,会报错: |
ERROR 1598 (HY000): Binary logging not possible. Message: Transaction level 'READ-COMMITTED' in InnoDB is not safe for binlog mode 'STATEMENT'