0%

事务及事务隔离性

事务的几个概念:

​ A(atomicity)原子性

​ 是指,一个事务里的多条SQL命令要么全部执行,要么全部不执行。

C(consistency)一致性

​ 数据更改状态:及如果数据更新成功,要和预期值一致,如果失败要保持到更新之前的状态不变。

I (isolation) 隔离性

​ 事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,通常这使用锁来实现

D(isolation)持久性

​ 事务一旦提交,其结果就是永久性的。即使发生宕机等故障,数据库也能将数据恢复。需要注意的是,只能从事务本身的角度来保证结果的永久性。例如,在事务提交后,所有的变化都是永久的。即使当数据库因为崩溃而需要恢复时,也能保证恢复后提交的数据都不会丢失。但若不是数据库本身发生故障,而是一些外部的原因,如RAID卡损坏、自然灾害等原因导致数据库发生问题,那么所有提交的数据可能都会丢失。因此持久性保证事务系统的高可靠性(High Reliability),而不是高可用性(High Availability)。对于高可用性的实现,事务本身并不能保证,需要一些系统共同配合来完成。

伴随事务产生的几个概念:(通过锁定机制可以实现事务的隔离性要求,使得事务可以并发地工作。锁也带来问题。)

​ 脏读:是指一个事务读取到了另外一个事务未提交的数据【也称为脏数据】则显然违反了数据库的隔离性。

幻读:在同一事务下,连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL语句可能会返回之前不存在的行。既是,一个事务读取到另外一个事务提交的数据。导致记录数不一致,这种问题在read-commited提交读下会产生,在MySQL的REPEATABLE READ下一般不会产生,InnoDB存储引擎采用Next-Key Locking的算法避免Phantom Problem。对于上述的SQL语句SELECT*FROM t WHERE a>2 FOR UPDATE,其锁住的不是5这单个值,而是对(2,+∞)这个范围加了X锁。因此任何对于这个范围的插入都是不被允许的,从而避免了部分Phantom Problem。

不可重复读:不可重复读是指在一个事务内多次读取同一数据集合。在这个事务还没有结束时,另外一个事务也访问该同一数据集合,并做了一些DML操作。因此,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的情况,这种情况称为不可重复读。违反了事务隔离性。

PS:从《MySQL技术内幕:InnoDB存储引擎》 一书看,不可重复读和幻读应该是一个意思。网上有资料说:幻读能读取到新的记录(多次读取一个范围内的记录,记录可能增加减少】,不可能重复读特指,同一行【或者同一集合内的数据多次读取,值会不一样】。

事务的4种隔离级别:

read uncommitted:未提交读。会产生脏读【是指未提交的数据,如果读到了脏数据,即一个事务可以读到另外一个事务中未提交的数据,则显然违反了数据库的隔离性。】,不可重复读问题。

​ 既是,一个事务能获取到另外一个事务未提交的数据。违反了事务的隔离性.

​ 需要注意的一点事,即使在未提交读级别下,对于同一行的修改,多个事务之间也会对行加锁,先获得锁先执行,后续执行,不会造成事务之间的丢失更新问题。

1:设置为未提交读隔离级别 事务1:set @@tx_isolation=’read-uncommitted’; 事务2:set @@tx_isolation=’read-uncommitted’;
2: BEGIN;
3 select * from user where id=1;+—-+——+——+| id | name | sex |+—-+——+——+| 1 | 13 | 1 |+—-+——+——+ BEGIN;
4 update user set name=14 where id=1;
5:此时事务1读取到了事务2未提交的修改,违反了事务隔离性原则 select * from user where id=1;+—-+——+——+| id | name | sex |+—-+——+——+| 1 | 14 | 1 |+—-+——+——+
6 COMMIT; COMMIT;

read committed:提交读。避免了脏读,会产生不可重复读和幻读问题。

​ 既是,一个事务能获取到另外一个事务提交的数据。

1:设置为未提交读隔离级别 事务1:set @@tx_isolation=’read-committed’; 事务2:set @@tx_isolation=’read-committed’;
2: BEGIN;
3 select * from user where id=1;+—-+——+——+| id | name | sex |+—-+——+——+| 1 | 13 | 1 |+—-+——+——+ BEGIN;
4:更新并且提交数据 update user set name=14 where id=1;COMMIT;
5:此时事务1读取到了事务2提交的修改,违反了事务隔离性原则 select * from user where id=1;+—-+——+——+| id | name | sex |+—-+——+——+| 1 | 14 | 1 |+—-+——+——+
6 COMMIT;

REPEATABLE READ:可重复读。避免了脏读,不可重复读,【MySQL可以说避免了幻读】

​ 既另外一个事务对数据的更新及提交,不会造成现有事务多次读取的状态变化。

1:设置为未提交读隔离级别 事务1:set @@tx_isolation=’ REPEATABLE-READ’; 事务2:set @@tx_isolation=’ REPEATABLE-READ’;
2: BEGIN;
3 select * from user where id=1;+—-+——+——+| id | name | sex |+—-+——+——+| 1 | 15 | 1 |+—-+——+——+ BEGIN;
4:更新并且提交数据 update user set name=name+1 where id=1;COMMIT;
5:此时事务1读取的数据状态不变 select * from user where id=1;+—-+——+——+| id | name | sex |+—-+——+——+| 1 | 15 | 1 |+—-+——+——+
6:事务1:进行数据更新 update user set name=name+1 where id=1;
7:事务1再次查询:值变为17,说明值的更新是依据两外一个事务已提交的数据更新,不会造成更新丢失。 select * from user where id=1;+—-+——+——+| id | name | sex |+—-+——+——+| 1 | 17 | 1 |+—-+——+——+
commit;

MySQL在此级别可以避免幻读的出现。

1:设置为未提交读隔离级别 事务1:set @@tx_isolation=’ REPEATABLE-READ’; 事务2:set @@tx_isolation=’ REPEATABLE-READ’;
2: BEGIN;
3 select * from user where sex=1;+—-+——+——+| id | name | sex |+—-+——+——+| 1 | 18 | 1 || 2 | 22 | 1 |+—-+——+——+ BEGIN;
4:更新并且提交数据 insert user (id,name,sex) values (4,’22’,1);COMMIT;
5:此时事务1读取的数据条数不变 select * from user where sex=1;+—-+——+——+| id | name | sex |+—-+——+——+| 1 | 18 | 1 || 2 | 22 | 1 |+—-+——+——+
6 commit;

SERIALIABLE:序列化

在SERIALIABLE的事务隔离级别,InnoDB存储引擎会对每个SELECT语句后自动加上LOCK IN SHARE MODE,即为每个读取操作加一个共享锁。因此在这个事务隔离级别下,读占用了锁,对一致性的非锁定读不再予以支持。这时,事务隔离级别SERIALIZABLE符合数据库理论上的要求,即事务是well-formed的,并且是two-phrased的。