MySQL的事务与事务隔离

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL的事务与事务隔离相关的知识,希望对你有一定的参考价值。

   mysql中自从引入InnoDB引擎后,在MySQL中就支持事务,事务就是一组原子性的查询语句,也即将多个查询当作一个独立的工作单元,平时通过提交工作单元来完成在事务中的相应的查询或修改,在能支持事务的数据库中必须要满足ACID测试,即事务的四个特性:

    A:Atomicity,原子性(都执行或者都不执行)

    C:Consistency,一致性(从一个一致性状态转到另外一个一致性状态)

    I:Isolaction,隔离性(一个事务的所有修改操作在提交前对其他事务时不可见的)

    D: Durability,持久性(旦事务得到提交,其所做的修改会永久有效)

而在早期默认的引擎MyISAM是不支持事务的,所以如果是在MyISAM表中是不支持事物的,想要知道数据中具体支持哪些表引擎可以通过”SHOW ENGINES;”查看在所使用版本中MySQL所支持的所有引擎。在这里先创建一张表transaction_tbl用于测试:

CREATE TABLE transaction_tbl (id int(4)) ENGINE=InnoDB; 
INSERT INTO transaction_tbl VALUE (1);
INSERT INTO transaction_tbl VALUE (2);
INSERT INTO transaction_tbl VALUE (3);
INSERT INTO transaction_tbl VALUE (4);
INSERT INTO transaction_tbl VALUE (5);

在正常的使用过程中需要关闭自动提交的功能默认系统下是开启的

mysql> SHOW GLOBAL VARIABLES LIKE ‘autocommit‘;                       
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.00 sec)

在使用事务之前需要关闭,在平时使用事务之前需要先检查是否有开启autocommit,当然如果对事务的依赖比较大建议可以永久关闭全局的自动提交,但是在平时使用的过程中只要在使用时关闭自动提交,而使用手动启动事务,这样在事务中需要回滚时才能根据相关的事务隔离级别得到想要的效果,可以在使用事务时关闭autocommit,在所有的事务结束后再开启autocommit

mysql> SET GLOBAL AUTOCOMMIT=off;#当然这里也可以使用布尔值的0和1
Query OK, 0 rows affected (0.00 sec)

其中事务的控制语句也很简单,如下:

BEGIN; 或 START TRANSACTION;
显式地开启一个事务

COMMIT;
提交事务即结束事务,并使已对数据库进行的所有修改为持久性的

ROLLBACK;
事务回滚,会结束用户的事务并撤销正在进行的所有未提交的修改

SAVEPOINT identifier;
允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT

RELEASE SAVEPOINT identifier;
删除一个事务的保存点,当没有指定的保存点时,执行该SQL语句会报异常

ROLLBACK TO identifier;
把事务回滚到标记点

而在事务中的隔离级别不同,则事物的安全性就不同,但是事物得安全性越高,并发性越低,当然需要根据实际情况选择,在MySQL中事务的隔离级别有四种,安全级分别由低至高:

    READ UNCOMMITTEND:读未提交

    READ COMMITTEND:读提交

    REPEATABLE READ :可重读

    SERIALIZABLE:可串行化

查看当前使用的默认的事务隔离级别:

mysql> SHOW GLOBAL VARIABLES LIKE ‘tx_isolation‘;
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)

而在使用四个隔离级别中,所带来效果都是不相同的,此时测试需要开启2个session更为直观,在这里就用A、B来代表两个session中开启事务A、B:

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.01 sec)

    一、READ UNCOMMITTEND:读未提交,顾名思义即所有的事务都可以读取到其他事务中未提交的内容,该隔离模式在平时一般都不使用,因为使用READ UNCOMMITTEND会带来脏读问题,下面就用transaction_tbl举一个简单例子说明下:

mysql> SET GLOBAL tx_isolation=‘READ-UNCOMMITTED‘;
Query OK, 0 rows affected (0.00 sec)
mysql> SHOW GLOBAL VARIABLES LIKE ‘tx_isolation‘;
+---------------+------------------+
| Variable_name | Value            |
+---------------+------------------+
| tx_isolation  | READ-UNCOMMITTED |
+---------------+------------------+
1 row in set (0.00 sec)

事务A中做了操作,但不提交:

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE transaction_tbl SET id = ‘6‘ WHERE id=‘1‘;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  6 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.00 sec)

但是此时事务B是可以看见事务A中数据

事务B:

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  6 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.01 sec)

此时事务A回滚:

mysql> ROLLBACK;
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.00 sec)

事务B中查出来的是事务A中未提交的数据:

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.00 sec)

这样就是由READ UNCOMMITTEND所带来的脏读,一般数据库生产环境中都不用这种事务隔离级别。

    二、READ COMMITTEND,读提交,同理根据名字可知事物的隔离级别会比读未提交高一个事务隔离级别更高,从而解决了脏读的问题,这也是大多数数据库中所用的默认事务隔离级别,但并不是MySQL的默认事务隔离级别,该事务隔离级别虽然解决了脏读问题,但是带来新的问题是不可重读,如果此时恰好有2个事务对相同的一张表做操作时,在一个事务中执行相同的查询时会查出不同的结果:

mysql> SET GLOBAL tx_isolation=‘READ-COMMITTED‘;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW GLOBAL VARIABLES LIKE ‘tx_isolation‘;       
+---------------+----------------+
| Variable_name | Value          |
+---------------+----------------+
| tx_isolation  | READ-COMMITTED |
+---------------+----------------+
1 row in set (0.00 sec)

在事务A中:

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.00 sec)

此时事务B中也开启事务做了一个操作:

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE transaction_tbl SET id = ‘6‘ WHERE id=‘1‘;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  6 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.01 sec)

此时看下事务A中的查询:

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.00 sec)
#但此时事务B中COMMIT提交后,事务A中
mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  6 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.00 sec)

这样就是由READ COMMITTEND所带来的不可重读问题,所以在一般数据库生产环境中也不建议采用这种事务隔离级别。

    三、REPEATABLE READ,可重读,同理该事务隔离级别解决了不可重读的问题,在REPEATABLE READ中使用MVCC(多版本并发控制)在每个事务启动时,InnoDB会为每个启动的事务提供一个当下时刻的快照,为实现此功能,InnoDB会为每个表提供两隐藏的字段,一个用于保存行的创建时间,一个用于保存行的失效时间,里面存储的系统版本号,MVCC旨在READ COMMITTEND和REPEATABLE READ两个事务隔离中生效,但是在REPEATABLE READ同以上事务隔离级别一样,在解决了不可重读的问题同时也带来新的问题幻读,此时恰好有2个事务对相同的一张表做操作时,在一个事务中提交之前其中一个事务在另一事务提交前后查询的结果不一样:

mysql> SET GLOBAL tx_isolation=‘REPEATABLE-READ‘; 
Query OK, 0 rows affected (0.01 sec)

mysql> SHOW GLOBAL VARIABLES LIKE ‘tx_isolation‘;
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.01 sec)

事务A:

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  6 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.02 sec)

事务B中做相关操作并提交:

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  6 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.00 sec)

mysql> UPDATE transaction_tbl SET id = ‘1‘ WHERE id=‘6‘;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.01 sec)

mysql> COMMIT;
Query OK, 0 rows affected (0.02 sec)

此时再来看下事务A中:

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.02 sec)

mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  6 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.00 sec)

这样就是REPEATABLE READ带来的幻读问题,当然在实际生产中这么恰好的事比较少,所以一般都做为MySQL的默认事务隔离级别。

    四、SERIALIZABLE,可串行化,强事务排序也是最高级别的事务隔离,所有的事务都有使用共享锁,这样就解决相应的幻读问题,但是因为共享锁的原因从而使写入的性能降低,从而降低了MySQL的性能:

mysql> SET GLOBAL tx_isolation=‘SERIALIZABLE‘;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW GLOBAL VARIABLES LIKE ‘tx_isolation‘;
+---------------+--------------+
| Variable_name | Value        |
+---------------+--------------+
| tx_isolation  | SERIALIZABLE |
+---------------+--------------+
1 row in set (0.00 sec)

在事务A中插入一条数据不提交:

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO transaction_tbl VALUE (‘7‘);
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM transaction_tbl;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
|  7 |
+----+
6 rows in set (0.00 sec)

此时在事务B中,在事务A未提交前是无法写入提交的

mysql> SHOW GLOBAL VARIABLES LIKE ‘tx_isolation‘;
+---------------+--------------+
| 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 transaction_tbl;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+
5 rows in set (0.00 sec)

mysql> UPDATE transaction_tbl SET id = ‘6‘ WHERE id=‘1‘;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

只有在事务A中COMMIT提交后才能在事务B中提交,但是在此需要注意的是在MySQL 5.7开始经过大量的代码重构优化后事务A、B在使用前三种隔离级别都是默认为REPEATABLE READ的事务隔离级别,而在平时利用事务时多用于存储过程中大量使用,而不同数据库(包括MySQL的不同版本都存在差异),语法差别很大,移植困难,换了数据库,需要重新编写,所以把过多业务逻辑写在存储过程不好维护,不利于分层管理,容易混乱,一般存储过程适用于个别对性能要求较高的业务,其它的必要性不是很大,在平时使用需要根据实际情况而定。

本文出自 “Jim的技术随笔” 博客,谢绝转载!

以上是关于MySQL的事务与事务隔离的主要内容,如果未能解决你的问题,请参考以下文章

MySQL:事务四大特性与隔离级别

Mysql事务隔离与Spring

MySql事务隔离的特点与实现

MySQL的事务与事务隔离

Mysql事务并发问题与隔离级别深入解析

事务与Mysql隔离级别