MySQL事务是由MySQL引擎支持的,不同的MySQL的引擎对事务的支持程度不同。事务有四种特性,针对事务的隔离性,MySQL也实现了不同的隔离级别。
事务
事务有四个特性,通常被称为ACID,分别为原子性(Automicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
1、原子性:整个事务的操作是原子操作,一次操作要么全部成功,要么回滚到原始状态,不会停滞在中间的某个环节。
2、一致性:多个事务并发时,确保数据库正确改变状态后,成功提交事务,保证数据的一致性。
3、隔离性:事务彼此之间的操作是透明的,不会相互影响。
4、持久性:事务执行完成,成功提交后,对数据库的改变会持久的保存在数据库中,不会因为事务的原因丢失。
隔离级别
虽然事务因为隔离性,彼此之间操作互不影响,但是不同的隔离级别使得不同事务操作相同数据时会使数据出现错误情况。例如,脏数据、不可重复读、幻读的情况。为了解决这些问题,数据库设计时可以采用不同的隔离级别,因为一方面要考虑到数据的安全性,一方面也要考虑到数据库的性能。MySQL的InnoDB引擎默认的事务级别是重复读,当然也可以对其进行设置。下面就针对事务的不同隔离级别对MySQL做一个测试。
准备工作
首先查看一下自己的数据库引擎,默认安装不修改的话,数据库引擎是InnoDB。
mysql> show variables like '%engine%';
+----------------------------+--------+
| Variable_name | Value |
+----------------------------+--------+
| default_storage_engine | InnoDB |
| default_tmp_storage_engine | InnoDB |
| storage_engine | InnoDB |
+----------------------------+--------+
3 rows in set
查看默认隔离级别,可以查看当前会话的,也可以查看全局的。当然,更改的时候也可以更改当前会话以及全局的。
mysql> select @@global.tx_isolation, @@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation |
+-----------------------+-----------------+
| REPEATABLE-READ | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set
可以看到,MySQL默认的全局和当前会话的隔离级别都是可重复读,下面进行更改后,查看多个事务操作数据的变化。下面的操作是对testTransaction表的列A进行数据加减操作,数据开始时是1。
mysql> select * from testTransaction;
+---+
| A |
+---+
| 1 |
+---+
1 row in set
未提交读
因为要模拟两个事务,所以现在开两个会话,各自会话都设置隔离级别为未提交读。两个会话分别执行以下操作:
mysql> set tx_isolation='read-uncommitted';
Query OK, 0 rows affected
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set
未提交读时的情况是,事务A开启事务-->对列A的数据进行查询-->事务不提交-->事务B开启事务-->对列A的数据进行加1操作-->事务不提交-->在A事务内再次查询列A的数据,此时就会得到事务B操作后的数据,但是事务B尚未提交,所以此时就会出现脏读和幻读的情况,而且事务A也无法再读到事务B操作之前的数据,所以也会有不可重复读的问题。下面的命令顺序执行:
会话1:
mysql> start transaction;
Query OK, 0 rows affected
mysql> select * from testTransaction;
+---+
| A |
+---+
| 1 |
+---+
1 row in set
会话2:
mysql> start transaction;
Query OK, 0 rows affected
mysql> select * from testTransaction;
+---+
| A |
+---+
| 1 |
+---+
1 row in set
mysql> update testTransaction set A = 2;
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
会话1:
mysql> select * from testTransaction;
+---+
| A |
+---+
| 2 |
+---+
1 row in set
由上面的命令执行结果就可以看到,事务B尚未提交,事务A就可以读到事务B操作后的数据。现在假设事务B是对某一个数据进行从1加到10的操作,那么事务A就可能读到事务B循环操作过程中的每一个数值,这样算是安全级别最低的情况了。
提交读
既然事务尚未提交,别的事务就可以读到数据,为了解决这一脏读问题,出现了提交读的隔离级别。下面就把两个会话的隔离级别都设置为提交读,两个会话都进行以下操作:
mysql> set tx_isolation='read-committed';
Query OK, 0 rows affected
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set
提交读是当某一事务对数据进行操作时,别的事务是不能读到该事务操作过程中的数据,而是要么读到的是原始数据,要么读到的是事务提交之后的数据。但是这样仍然会出现不可重复读和幻读的问题。按下列顺序操作:
会话1:
mysql> start transaction;
Query OK, 0 rows affected
mysql> select * from testTransaction;
+---+
| A |
+---+
| 1 |
+---+
1 row in set
会话2:
mysql> select * from testTransaction;
+---+
| A |
+---+
| 1 |
+---+
1 row in set
mysql> update testTransaction set A = 2;
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
会话1:
mysql> select * from testTransaction;
+---+
| A |
+---+
| 1 |
+---+
1 row in set
会话2:
mysql> commit;
Query OK, 0 rows affected
会话1:
mysql> select * from testTransaction;
+---+
| A |
+---+
| 2 |
+---+
1 row in set
由上面的执行结果可以看到,事务A在事务B提交之前是无法读到事务B操作后的数据,这样就解决了脏读的问题。但是在事务B提交后,事务A再次查询,就会发现查到的数据和之前的数据不一样了,这是因为它没有解决不可重复读和幻读的问题。为了解决不可重复读问题,就出现了新的隔离级别,那就是重复读。
重复读
重复读隔离级别设置后,事务A不管事务B的操作是否提交,只要事务A尚未提交,那么它读到的数据就是不变的。下面把两个会话的隔离级别都设置为重复读,两个会话都进行以下操作:
mysql> set tx_isolation = 'repeatable-read';
Query OK, 0 rows affected
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set
下面两个会话的命令按顺序执行:
会话1:
mysql> start transaction;
Query OK, 0 rows affected
mysql> select * from testTransaction;
+---+
| A |
+---+
| 1 |
+---+
1 row in set
会话2:
mysql> start transaction;
Query OK, 0 rows affected
mysql> select * from testTransaction;
+---+
| A |
+---+
| 1 |
+---+
1 row in set
mysql> update testTransaction set A = 2;
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
会话1:
mysql> select * from testTransaction;
+---+
| A |
+---+
| 1 |
+---+
1 row in set
会话2:
mysql> commit;
Query OK, 0 rows affected
会话1:
mysql> select * from testTransaction;
+---+
| A |
+---+
| 1 |
+---+
1 row in set
由上面的执行结果可见,即使是事务B提交之后,事务A读到的依然是它最开始读到的数据。
序列化
为了解决幻读的问题,出现序列化的隔离级别。它是安全级别最高的,但同时也是性能最低的,MySQL默认支持到重复读,有兴趣的读者可以自己试试序列化的方式。
事务操作
事务操作有三种,开启、提交和回滚。MySQL开启事务有两种方式,start transaction和begin,别的写法都是错误的。提交事务只需要commit就可以了,但是MySQL的命令列操作都是需要在命令结尾加分号的。至于回滚事务,就是直接rollback就好了,当然也不要忘了分号。