Transaction事务是指一个逻辑单元,执行一系列操作的SQL语句。

事务中一组的SQL语句,要么全部执行,要么全部回退。在Oracle数据库中有个名字,叫做transaction ID

在关系型数据库中,事务必须ACID的特性。

  • 原子性,事务中的操作,要不全部执行,要不都不执行
  • 一致性,事务完成前后,数据的必须保持一致。
  • 隔离性,多个用户并发访问数据库时,每一个用户开启的事务,相互隔离,不被其他事务的操作所干扰。
  • 持久性,事务一旦commit,它对数据库的改变是持久性的。

目前重点讨论隔离性。数据库一共有四个隔离级别

  • 未提交读(RU,Read Uncommitted)。它能读到一个事物的中间状态,不符合业务中安全性的保证,违背 了ACID特性,存在脏读的问题,基本不会用到,可以忽略

  • 提交读(RC,Read Committed)。顾名思义,事务提交之后,那么我们可以看到。这是一种最普遍的适用的事务级别。我们生产环境常用的使用级别。

  • 可重复读(RR,Repeatable Read)。是目前被使用得最多的一种级别。其特点是有GAP锁,目前还是默认级别,这个级别下会经常发生死锁,低并发等问题。

  • 可串行化,这种实现方式,其实已经是不是多版本了,而是单版本的状态,因为它所有的实现都是通过锁来实现的。

因此目前数据库主流常用的是RCRR隔离级别。

隔离性的实现方式,我们通常用Read View表示一个事务的可见性。

RC级别,事务可见性比较高,它可以看到已提交的事务的所有修改。因此在提交读(RC,Read Committed)隔离级别下,每一次select语句,都会获取一次Read View,得到数据库最新的事务提交状态。因此对于数据库,并发性能也最好。

RR级别,则不是。它为了避免幻读和不可重复读。保证在一个事务内前后数据读取的一致。其可见性视图Read View只有在自己当前事务提交之后,才会更新。

那如何保证数据的一致性?其核心是通过redo logundo 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,让数据库有了很强的并发能力。随着数据库并发事务处理能力大大增强,从而提高了数据库系统的事务吞吐量,可以支持更多的用户并发访问。但并发访问,会出现带来一系列问题。如下:

隔离级别和上述现象之间的联系。

隔离级别有:未提交读(RU,Read Uncommitted),提交读(RC,Read Committed),可重复读(RR,Repeatable Read),可串行化(Serializable)

实验环节

举例在隔离级别RRRC下,说明“不可重复读”问题。

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中的数据。

理解MySQL数据库事务-隔离性-LMLPHP

在隔离级别可重复读(RR,Repeatable Read),不会出现不可重复读现象,举例如下:

理解MySQL数据库事务-隔离性-LMLPHP

举例说明“幻读”的现象。

行锁可以防止不同事务版本的数据在修改(update)提交时造成数据冲突的问题。但是插入数据如何避免呢?

在RC隔离级别下,其他事务的插入数据,会出现幻读(Phantom Reads)的现象。

理解MySQL数据库事务-隔离性-LMLPHP

而在RR隔离级别下,会通过Gap锁,锁住其他事务的insert操作,避免"幻读"的发生。

理解MySQL数据库事务-隔离性-LMLPHP

因此,在MySQL事务中,锁的实现方式与隔离级别有关,如上述实验所示。在RR隔离级别下,MySQL为了解决幻读的问题,已牺牲并行度为代价,通过Gap锁来防止数据的写入。这种锁,并行度差,冲突多。容易引发死锁。

目前流行的Row模式可以避免很多冲突和死锁问题,因此建议数据库使用ROW+RC(Read Committed)模式隔离级别,很大程度上提高数据库的读写并行度,提高数据库的性能。

11-23 00:37