理解MySQL数据库事务-隔离性
Transaction事务是指一个逻辑单元,执行一系列操作的SQL语句。
事务中一组的SQL语句,要么全部执行,要么全部回退。在Oracle数据库中有个名字,叫做transaction ID
在关系型数据库中,事务必须ACID的特性。
原子性,事务中的操作,要不全部执行,要不都不执行
一致性,事务完成前后,数据的必须保持一致。
隔离性,多个用户并发访问数据库时,每一个用户开启的事务,相互隔离,不被其他事务的操作所干扰。
持久性,事务一旦commit,它对数据库的改变是持久性的。
目前重点讨论隔离性。数据库一共有四个隔离级别
未提交读(RU,Read Uncommitted)。它能读到一个事物的中间状态,不符合业务中安全性的保证,违背 了ACID特性,存在脏读的问题,基本不会用到,可以忽略
提交读(RC,Read Committed)。顾名思义,事务提交之后,那么我们可以看到。这是一种最普遍的适用的事务级别。我们生产环境常用的使用级别。
可重复读(RR,Repeatable Read)。是目前被使用得最多的一种级别。其特点是有GAP锁,目前还是默认级别,这个级别下会经常发生死锁,低并发等问题。
可串行化,这种实现方式,其实已经是不是多版本了,而是单版本的状态,因为它所有的实现都是通过锁来实现的。
因此目前数据库主流常用的是RC和RR隔离级别。
隔离性的实现方式,我们通常用Read View表示一个事务的可见性。
RC级别,事务可见性比较高,它可以看到已提交的事务的所有修改。因此在提交读(RC,Read Committed)隔离级别下,每一次select语句,都会获取一次Read View,得到数据库最新的事务提交状态。因此对于数据库,并发性能也最好。
RR级别,则不是。它为了避免幻读和不可重复读。保证在一个事务内前后数据读取的一致。其可见性视图Read View只有在自己当前事务提交之后,才会更新。
那如何保证数据的一致性?其核心是通过redo log和undo log来保证的。
而在数据库中,为了实现这种高并发访问,就需要对数据库进行多版本控制,通过事务的可见性来保证事务看到自己想看到的那个数据版本(或者是最新的Read View亦或者是老的Read View)。这种技术叫做MVCC
多版本是如何实现的?通过undo日志来保证。每一次数据库的修改,undo日志会存储之前的修改记录值。如果事务未提交,会回滚至老版本的数据。其MVCC的核心原理,以后详谈
举例论证:
## 开启事务
MariaDB [scott]> begin;
Query OK, 0 rows affected (0.000 sec)
##查看当前的数据
MariaDB [scott]> select * from dept;
+--------+------------+----------+
| deptno | dname | loc |
+--------+------------+----------+
| 10 | ACCOUNTING | beijing |
| 20 | RESEARCH | DALLAS |
| 30 | SALES | CHICAGO |
| 40 | OPERATIONS | beijing |
| 50 | security | beijing |
| 60 | security | nanchang |
+--------+------------+----------+
6 rows in set (0.001 sec)
##更新数据
MariaDB [scott]> update dept set loc ='beijing' where deptno = 20;
Query OK, 1 row affected (0.001 sec)
## 其行记录| 20 | RESEARCH | DALLAS |已经被放置在undo日志中,目前最新的记录被改为'beijing':
MariaDB [scott]> select * from dept;
+--------+------------+----------+
| deptno | dname | loc |
+--------+------------+----------+
| 10 | ACCOUNTING | beijing |
| 20 | RESEARCH | beijing |
| 30 | SALES | CHICAGO |
| 40 | OPERATIONS | beijing |
| 50 | security | beijing |
| 60 | security | nanchang |
+--------+------------+----------+
##事务不提交,回滚。数据回滚至老版本的数据。
MariaDB [scott]> rollback;
Query OK, 0 rows affected (0.004 sec)
MariaDB [scott]> select * from dept;
+--------+------------+----------+
| deptno | dname | loc |
+--------+------------+----------+
| 10 | ACCOUNTING | beijing |
| 20 | RESEARCH | DALLAS |
| 30 | SALES | CHICAGO |
| 40 | OPERATIONS | beijing |
| 50 | security | beijing |
| 60 | security | nanchang |
+--------+------------+----------+
6 rows in set (0.000 sec)
因为MVCC,让数据库有了很强的并发能力。随着数据库并发事务处理能力大大增强,从而提高了数据库系统的事务吞吐量,可以支持更多的用户并发访问。但并发访问,会出现带来一系列问题。如下:
数据库并发带来的问题
概述解释
脏读(Dirty Reads)
当一个事务A正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务B也访问这同一个数据,如不控制,事务B会读取这些”脏”数据,并可能做进一步的处理。这种现象被称为”脏读”(Dirty Reads)
不可重复读(Non-Repeatable Reads)
指在一个事务A内,多次读同一数据。在这个事务还没有结束时,另外一个事务B也访问该同一数据。那么,在事务A的两次读数据之间,由于第二个事务B的修改,那么第一个事务两次读到的的数据可能是不一样的 。出现了”不可重复读”(Non-Repeatable Reads)的现象
幻读(Phantom Reads)
指在一个事务A内,按相同的查询条件重新检索以前检索过的数据,同时发现有其他事务插入了数据,其插入的数据满足事务A的查询条件。因此查询出了新的数据,这种现象就称为”幻读”(Phantom Reads)
隔离级别和上述现象之间的联系。
隔离级别有:未提交读(RU,Read Uncommitted),提交读(RC,Read Committed),可重复读(RR,Repeatable Read),可串行化(Serializable)
隔离级别
脏读
不可重复读
幻读
未提交读(RU,Read Uncommitted)
可能
可能
可能
提交读(RC,Read Committed)
不可能
可能
可能
可重复读(RR,Repeatable Read)
不可能
不可能
可能
(间隙锁解决)
可串行化(Serializable)
不可能
不可能
不可能
实验环节
举例在隔离级别RR和RC下,说明“不可重复读”问题。
MySQL的默认级别是Repeatable Read,如下:
MariaDB [(none)]> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.000 sec)
这里修改当前会话级别为Read Committed
MariaDB [scott]> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.001 sec)
MariaDB [scott]> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set (0.000 sec)
在隔离级别已提交读(RC,Read Committed)下,出现了不可重复读的现象。在事务A中可以读取事务B中的数据。
在隔离级别可重复读(RR,Repeatable Read),不会出现不可重复读现象,举例如下:
举例说明“幻读”的现象。
行锁可以防止不同事务版本的数据在修改(update)提交时造成数据冲突的问题。但是插入数据如何避免呢?
在RC隔离级别下,其他事务的插入数据,会出现幻读(Phantom Reads)的现象。
而在RR隔离级别下,会通过Gap锁,锁住其他事务的insert操作,避免”幻读”的发生。
因此,在MySQL事务中,锁的实现方式与隔离级别有关,如上述实验所示。在RR隔离级别下,MySQL为了解决幻读的问题,已牺牲并行度为代价,通过Gap锁来防止数据的写入。这种锁,并行度差,冲突多。容易引发死锁。
目前流行的Row模式可以避免很多冲突和死锁问题,因此建议数据库使用ROW+RC(Read Committed)模式隔离级别,很大程度上提高数据库的读写并行度,提高数据库的性能。