深入理解MySQL事务

1.事务的基本概念或者什么是事务

通俗易懂地来理解事务,它指的就是一系列不可分割的原子操作的集合。通常是用来描述数据库中一系列操作的集合,这一系列操作,要么都执行成功,要么都执行失败。而这种保证一系列操作成功或者失败的技术,就是由事务来完成的。下面我们就来深入了解一下MySQL数据库事务方面的知识,当然,其他数据库虽然有不同,但也是大同小异的。

2.事务的四大特性

(1)原子性(Atomic)

表示的是一系列操作中的最小操作单元。假如一个事务中进行了两次DML操作,其中的一次就可以理解为一个原子操作。

(2)一致性(Consistent)

表示的是数据库中的数据从一个状态改到另一个状态时,这个过程中的操作全部执行或者全部不执行。中间如果有某一个步骤操作失败,就会存在着有一些数据保存到数据库中,另一些数据未正常保存到数据库中,这时,数据库中的数据就处于不一致的状态。

(3)隔离性(Isolation)

表示同时执行的多个事务之间互相不影响,即当前执行的事务不会影响到另外一个事务的执行,他们之间是完全独立的。

(4)持久性(Durability)

表示如果事务执行成功,则事务中多数据库中数据的操作将会永久地保存到数据库中,即使数据库此时崩溃也不影响。

3.MySQL的隔离级别

事务的隔离级别表示的是并发事务执行的时候,事物之间数据的可见性,MySQL中将事务的隔离级别分为4中,读未提交,读已提交,可重复度,串行化。默认为可重复度的隔离级别,在Oracle中,默认隔离级别为读已提交。可见,不同数据库中,对数据的可见性要求是不同的。隔离级别越高,执行时的效率越低,比如串行化的效率最低,因为它在执行的过程中,需要等待其他事务提交完成之后才可执行。

(1)读未提交(Read-Uncommitted)

表示当前正在执行的事务可以看到其他事务未提交的数据。存在着脏读的问题,该隔离级别在实际工作中基本不会使用。

(2)读已提交(Read-Committed)

表示当前正在执行的事务可以读取到其他事务已经提交的数据,但是存在着不可重复读的问题。即在一次事务中多次查询到的结果可能不一样,其他事务可能在查询期间发生了修改并且commit,导致当前事务查询的时候结果不同。

(3)可重复读(Repeatable-Read)

表示当前一个事物中的多次并发查询得到的数据相同。但是存在着幻读的问题,即:如果在查询期间,其他事务在这个查询范围内插入了数据,此时再次查询,就会看到幻影的行,也就是发生了幻读。这种情况在InnoDB中,是通过MVCC解决的,即:通过每行数据中的一个隐藏版本号,每次查询的时候根据版本号对比,从而过滤掉了事务执行过程中其他事务插入的数据。

(4)串行化(Serializable)

表示同一个事务的多次查询按照顺序依次执行,不会并发执行,即每次查询的时候需要先去获取锁,然后执行查询。严格保证了数据的正确性,但是由于是加了共享锁,所以如果访问量比加大,将会存在着大量的锁竞争和超时。

4.各个隔离级别中的问题总结

(1)读未提交存在的问题:存在着脏读,不可重复读和幻读问题
(2)读已提交存在的问题:存在着不可重复读和幻读的问题
(3)可重复读存在的问题:存在着幻读的问题
(4)序列化存在的问题:序列化可以防止脏读,不可重复读和幻读的问题,但是效率比较地下
(5)不可重复读和幻读的区别
a.不可重复读表示的是在一个事务执行期间,其他事务对数据进行了删除或者修改,及多次读取到的数据内容或者条数不一样;
b.幻读表示的是在一个事务执行期间,其他事务插入了新数据,导致多次可能看到幻影行的数据

5.隔离级别修改

set session transaction isolation level <隔离级别>;

该方法为修改session级别的事务,退出mysql命令行之后就会失效。如果需要永久修改,可以通过修改配置文件来完成.

6.MySQL隔离级别测试

创建如下的测试表结构:

create table t_test(
   id int primary key auto_increment,
   age int
) engine = InnoDB default charset = UTF8;

插入初始测试数据:

insert into t_test(age) values(1),(2);

查询结果如下:

+—-+——+
| id   | age |
+—-+——+
| 1   | 1      |
| 2   | 2      |
+—-+——+
2 rows in set (0.00 sec)

(1)测试读未提交隔离级别read uncommited

开启两个session窗口A和B,A窗口中修改mysql的默认隔离级别为read uncommitted,然后开启事务

mysql> set session transaction isolation level read uncommitted;
mysql> begin;
mysql> update t_test set age = 10 where id = 2;

B窗口中也修改MySQL的隔离级别为read uncommitted,然后开启事务,并查询t_test中的数据,如下

mysql> set session transaction isolation level read uncommitted;
mysql> begin;
mysql> select * from t_test;

查询结果如下:

+—-+——+
| id   | age |
+—-+——+
| 1   | 1      |
| 2   | 10    |
+—-+——+
2 rows in set (0.00 sec)

可见,A窗口中未提交的事务修改的数据被B窗口中的事务读取到了,这就是脏读。
然后执行rollback命令,将数据恢复到原始状态,供下面继续测试。

mysql> rollback;
mysql> commit;

(2)测试读已提交隔离级别read committed

开启两个session窗口A和B,然后在A窗口中修改事务的默认隔离级别为读已提交read committed,然后开启事务

mysql> set session transaction isolation level read committed;
mysql> begin;
mysql> select * from t_test;

此时的查询结果如下:

+—-+——+
| id   | age |
+—-+——+
| 1   | 1      |
| 2   | 2      |
+—-+——+
2 rows in set (0.00 sec)

在B窗口中执行修改,将id为2的数据的age改为10,然后提交数据

mysql> begin;
mysql> update t_test set age = 10 where id = 2;
mysql> commit;

然后再在B中查询,此时的执行结果如下:

mysql> select * from t_test;

+—-+——+
| id   | age |
+—-+——+
| 1   | 1      |
| 2   | 10    |
+—-+——+
2 rows in set (0.00 sec)

可见,在B执行的两次查询过程中,查询到的数据不一样,这就是不可重复读。
然后将数据还原回去。

mysql> update t_test set age = 2 where id = 2;

(3)测试可重复度隔离级别repeatable read
在A窗口中将当前的事务隔离级别改为repeatable read,然后执行查询,此时的结果如下:

mysql> set session transaction isolation level repeatable read;
mysql> begin;
mysql> select * from t_test;

+—-+——+
| id   | age |
+—-+——+
| 1    | 1     |
| 2    | 2     |
+—-+——+
2 rows in set (0.00 sec)

在B窗口中将id为2的数据age改为10

mysql> update t_test set age = 10 where id = 2;

然后在A中执行查询,结果如下:

mysql> select * from t_test;
+—-+——+
| id   | age |
+—-+——+
| 1    | 1     |
| 2    | 2     |
+—-+——+
2 rows in set (0.00 sec)

可见,在当前的事务中,未读取到其他事务读取的数据,数据也和开始读取到的相同,这就避免了幻读

将数据恢复至原始状态,然后现在A窗口中开启事务,但是不执行查询

mysql> begin;

在B窗口中插入一条数据,id为3,age为3,如下:

mysql> insert into t_test(id,age) values(3,3);

然后在B窗口中执行查询,结果如下:

mysql> select * from t_test;
+—-+——+
| id  | age  |
+—-+——+
| 1   | 1      |
| 2   | 2      |
| 3   | 3      |
+—-+——+
3 rows in set (0.00 sec)

可见,多了一条数据,这条数据是在A开启事务之后执行的,这就是幻读,表示读取到的数据多了。

(4)测试串行化
将数据还原至初始状态,然后将A窗口中的隔离级别改为serializable,然后执行查询

mysql> set session transaction isolation level serializable;
mysql> begin;
mysql> select * from t_test;
+—-+——+
| id   | age |
+—-+——+
| 1    | 1     |
| 2    | 2     |
+—-+——+
2 rows in set (0.00 sec)

在B窗口中开启事务,并插入一条数id为3,age为3,如下:

mysql> begin;
mysql> insert into t_test(id,age) values(3,3);

…阻塞等待…

发现此时B窗口中执行的insert语句被阻塞了,就是串行化的效果,因为A窗口未提交事务,未释放共享锁,所以B执行插入的时候,去添加排他锁的时候失败了,导致阻塞。
以上,就是MySQL事务相关知识。

文章属于作者原创, 转载请标注来源:www.jinnianshizhunian.vip