数据库ACID、隔离级别与MVCC

来源:互联网 发布:熊本熊mac电脑壁纸 编辑:程序博客网 时间:2024/05/19 14:16
首先需要明确事务的概念:一组原子性的SQL查询,如果数据库引擎能够成功的对数据库应用该组查询的全部语句,那么就执行该组语句,否则所有语句都不执行。

事务有ACID四个特性,即:

原子性:一个事务是一个不可分割的最小工作单元,其操作要么全部成功,要么全部失败;

一致性:数据库总是从一个一致性状态转换为另一个一致性状态。所谓一致性状态,就是数据库的所有完整性约束(尤其注意用户定义约束)都被遵守,以银行转账为例,“转账操作必然导致一个账户减少金额,另一个账户增加金额,且这两个账户总金额之和不变”就是一个完整性约束。

持久性:一旦事务提交,则其所作的修改就会永久保存到数据库中

隔离性:隔离性用于定义事务之间的相互隔离程度,存在四个隔离级别。

首先需要解释一下几个跟隔离性相关的概念定义:

(1)脏读:指事务读到脏数据,所谓脏数据,指的是不正确的数据,例如事务执行过程中修改了某记录,然后回滚,如果其他事务读到了该记录的中间修改值,则为脏读。

(2)不可重复读:事务在执行过程中,多次对同一个已经存在的记录进行读取,各次读取的值不同。读提交隔离级别存在不可重复读的问题,事务1、2并发执行,事务2首先读取记录1,然后事务1修改记录1并提交,事务2继续读取记录1,则事务2两次读取到的值不同。

(3)幻读:幻读是指使用某个条件读取一批记录时,可能读到的记录数不同。幻读与脏读、不可重复读的区别在于,脏读、不可重复读都是针对某个确定的已经存在的记录出现的值不要求(读到脏数据或多次读的值不同),而幻读则是多次使用同一个条件查询一批记录,多次读到的记录数不同,也就是说,脏读、不可重复读是由于多个事务并行执行update引起的,而幻读则是由于多个事务并行执行insert引起的(并发delete引起的问题看起来算哪个都行……)。

四个隔离级别为:

(1)Read Uncommited:读未提交,其含义为多个并发事务,任何一个事务可以读到其他事务尚未提交的修改:

    存在脏读、不可重复读、幻读可能性。

(2)Read Commited:读已提交,含义为多个并发事务,任何一个事务只可以读到其他事务已经提交的修改:

    解决脏读,存在不可重复读、幻读可能性。

(3)Repeatable Read:可重复读,含义为多个事务并发执行时,任何一个事务反复读取已存在的记录,每次读到的值都是相同的

    解决脏读、不可重复读,存在幻读可能性。

(4)Serializable:串行化,含义为所有事务串行执行,因此不存在事务并发执行的情况。

    解决脏读、不可重复读、幻读。

 

 

多版本并发控制MVCC

上述四个隔离级别中,读未提交隔离性最差,且相对于读已提交,性能并没有多少提升,几乎不会使用;串行化隔离性最好,可是性能太差,也几乎不会使用。一般数据库的默认隔离级别要么是读已提交,要么是可重复读(例如MySQL的InnoDB引擎),要么是读已提交(例如Oracle )。

如果使用行级读锁、写锁来实现读已提交或可重复读,应当是以下的步骤:

1、事务1会修改行1,则会在行1加上写锁,开始事务;

2、事务2为纯读取操作,需要读取行1,试图在行1上加上读锁,由于事务1已加写锁,因此事务2等待直到事务1完成。

3、如果事务2先开始,则事务1也需要等到事务2完成并释放读锁后才可以开始执行。

也即使说,对某行的写操作会阻塞所有对该行的读取操作,对某行的读操作会阻塞所有对该行的写操作,在系统存在读、写并发时,不论系统IO能力有多高,会受限于锁而导致性能低下。

MVCC用于解决这个问题来提高系统性能,MVCC并没有统一的标准,各个数据库实现均采用不同方式来实现MVCC,InnoDB的实现方式如下:

准备工作:

(1)对每行记录增加行标志和删除标志两个字段;

(2)维护一个全局的系统版本号,每开始一个事务(注意select也是事务,读事务),将该系统版本号加1并作为事务的版本号

插入记录的行标志设置为本事务版本号,删除标志为空;

删除记录的删除标志设置为本事务版本号;

修改的处理过程:将原记录的删除版本号修改为本事务版本号;新插入一条记录,包含原记录数据及本次修改,行记录标志设置为本事务版本号,删除标志为空;

读取的处理过程:

仅读取同时满足以下条件的记录行:

(1)行标志小于或等于本事务版本号(等于用于保证能够读取到本事务内提交的增加);

(2)删除标志为空或者大于本事务版本号(不包括等于以保证不会读取到本事务删除的记录);

相当于在读事务开始的时刻点,建立了一个系统的快照,该事务读取的所有数据,均是从快照中读取的,因此满足可重复读的条件,并且可解决幻读的问题,并且也不会读到产生“同样查询条件,事务中第一次读到的记录数大于第二次读到的记录数的问题“(由并发删除引起)

从上可知,使用MVCC后,大部分读都不再需要加读锁,因此读不再阻塞写,写也不再阻塞读。读操作只再受限于系统IO能力。



昨天去去哪儿网面试,老周和老赵问了很多问题,大多关于细节,其中就包括事务隔离级别和MVCC,由于准备不够充分,所以今天特地进行验证。

其中隔离级别中,比较让人难以理解的是repeatable read可重复读,和serializable串行读,下面依次进行试验,查看彼此区别。


serializable隔离级别:

session 1session 2

mysql> show variables like ‘%iso%‘;

+---------------+--------------+

| Variable_name | Value        |

+---------------+--------------+

| tx_isolation  | SERIALIZABLE |

+---------------+--------------+

1 row in set (0.00 sec)

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from t;

+---+

| i |

+---+

| 1 |

| 2 |

| 3 |

| 4 |

| 5 |

+---+

5 rows in set (0.00 sec)

mysql> show variables like ‘%iso%‘;

+---------------+--------------+

| Variable_name | Value        |

+---------------+--------------+

| tx_isolation  | SERIALIZABLE |

+---------------+--------------+

1 row in set (0.00 sec)

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from t;

+---+

| i |

+---+

| 1 |

| 2 |

| 3 |

| 4 |

| 5 |

+---+

5 rows in set (0.00 sec)

mysql> insert into t values (6);

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction



mysql> insert into t values (10);

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

上面现象说明:当隔离级别为serializable的时候,不兼容MVCC,严格遵循锁机制,当session1 和session2都进行全表查询时,两个会话都会全表加读锁,由于读锁只和读锁相兼容,所以此时任何一个会话都无法修改、插入数据,会进入所等待。


repeatable read隔离级别:


session 1session 2

mysql> show variables like ‘%iso%‘;

+---------------+-----------------+

| Variable_name | Value           |

+---------------+-----------------+

| tx_isolation  | REPEATABLE-READ |

+---------------+-----------------+

1 row in set (0.00 sec)

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from t;

+---+

| i |

+---+

| 1 |

| 2 |

| 3 |

| 4 |

| 5 |

+---+

5 rows in set (0.00 sec)

mysql> show variables like ‘%iso%‘;

+---------------+-----------------+

| Variable_name | Value           |

+---------------+-----------------+

| tx_isolation  | REPEATABLE-READ |

+---------------+-----------------+

1 row in set (0.00 sec)

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from t;

+---+

| i |

+---+

| 1 |

| 2 |

| 3 |

| 4 |

| 5 |

+---+

5 rows in set (0.00 sec)

mysql> insert into t values (6); 

Query OK, 1 row affected (0.00 sec)


mysql> select * from t;

+---+

| i |

+---+

| 1 |

| 2 |

| 3 |

| 4 |

| 5 |

| 6 |

+---+

6 rows in set (0.00 sec)

mysql> select * from t;

+---+

| i |

+---+

| 1 |

| 2 |

| 3 |

| 4 |

| 5 |

+---+

5 rows in set (0.00 sec)


mysql> insert into t values (6); 

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

上面现象说明:当隔离级别为repeatable read时,兼容使用MVCC(使用undo),此时两个事物读取数据到各自的undo中,事物之间独立,但是不同事务对于同一行数据的修改会根据顺序加上排他锁。其中上面session 2最后插入一条数据,是因为session 1已经存在并将该行数据锁定,同时此刻出现在repeatable read隔离级别所特有的幻读现象(本会话内并没有该数据,却依然无法插入)。


总结:

MVCC:多版本控制,多个未提交事务所看到的数据都是自己的,彼此不同,在客户端总体看来仿佛多个版本个数据库。

MVCC只和隔离级别read-committed和repeatable-read相兼容,MACC对于不同事物的同一行的读写之间是不加锁的,对于不同事务的同一行的写写加锁。

MVCC和read-uncommitted和serializable不兼容,其中serializable完是由锁来控制,所有事务均符合锁特征。


以下内容摘自:http://www.blogjava.net/neverend/archive/2012/04/05/373357.html

在事务隔离级别设定为repeatable read的情况下,一般的select语句采取的是一致性非阻塞读的方式。
一致性是指在事务的范围内读取的数据是可重现的,不会出现不可重复读的情况。非阻塞是指这种读取数据的模式不会对数据上任何一种锁,其它操作全都不会被阻塞。
在这种模式下,事务执行读取语句后,相关的数据会有一套副本出现,并会为这个数据副本附加一个时间戳,其它事务在这个时间戳之后执行的写操作都不会反映到这个副本中,这种机制被称之为多版本并发控制。
如果用select …… lock in share mode,则不是一致性非阻塞读,该语句会等待其它事务的写语句提交或回滚之后再读取数据;如果事务隔离级别设置为read committed,也不是一致性非阻塞读,该语句会读取其它事务提交的数据。

0 0
原创粉丝点击