MySQL学习-事务是如何实现隔离的

572 阅读5分钟

「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战」。

作者:汤圆

个人博客:javalover.cc

前言

mysql中的事务是用来确保我们在操作数据库时,整个操作过程要么全部成功,要么全部失败,不会出现前半部分成功了,后半部分却失败的情况;

比如我们日常生活中常见的转账操作,如果没有事务的支持,就可能出现你卡里的钱都被扣了,但是小王那边却没收到的情况;

事务这个功能是在数据库的引擎层面实现的,InnoDB支持事务,MyISAM不支持;

下面我们就介绍下事务相关的概念

目录

  1. 事务的启动方式
  2. 事务的隔离级别
  3. 事务隔离的实现

正文

1. 事务的启动方式

启动方式分为显式启动和隐式启动;

  • 显式启动

通过 begin 或者start transaction 启动事务;

通过 commit 提交事务;

通过 rollback 回滚事务;

显式启动的好处就是启动和提交(回滚)总是配套出现的,如果忘记提交也会很容易查出来;

这样就减少了长事务的出现;

  • 隐式启动

不需要通过begin 或者 start transaction 启动事务;

当你执行一条SQL语句时(比如select * from t),一个事务就启动了;

此时如果你 set autocommit = 0,那么事务不会自动提交,需手动 commit 提交 或者 rollback回滚;这就可能导致长事务的出现(因为没有配套的启动事务命令,所以会容易忘记提交命令);

而如果 set autocommit = 1,事务就自动提交,此时无需手动commit;每条语句都会自动启动一个事务,语句执行成功后会自动提交事务,语句执行失败后会自动回滚事务;

但是设置autocommit=1会存在一个问题:就是事务失去了它的作用,无法保证数据的一致性,比如转账操作;

推荐的做法:建议都用显式启动的方式来 操作事务;同时 set autocommit = 1,这样就可以防止隐式启动带来的长事务问题;

2. 事务的隔离级别

有4种:读未提交、读提交、可重复读、串行化;

  • 读未提交(READ-UNCOMMITED)

定义:当前事务运行期间,可以读到其他事务还未提交的数据

比如我现在启动一个事务A,去读取id=1这条数据,刚开始读到的结果为1;

这时候我再开启一个事务B,去更新id=1这行数据;

此时,我再去事务A中读取数据,就会读到最新的数据;

下面我们用例子来演示下:

先修改隔离级别为读未提交

mysql> set transaction_isolation='READ-UNCOMMITTED';
Query OK, 0 rows affected (0.00 sec)

查看当前的隔离级别:可以看到设置成功

mysql> show variables like 'transaction_isolation';
+-----------------------+------------------+
| Variable_name         | Value            |
+-----------------------+------------------+
| transaction_isolation | READ-UNCOMMITTED |
+-----------------------+------------------+
1 row in set (0.05 sec)

显式启动事务A,同时查询id=1的数据:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select * from test where id = 1;
+----+------+
| id | name |
+----+------+
|  1 | 1    |
+----+------+
1 row in set (0.00 sec)

显式启动事务B,同时更新id=1的数据:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> update test set name = 2 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

再次回到事务A,查询id=1的数据:

mysql> select * from test where id = 1;
+----+------+
| id | name |
+----+------+
|  1 | 2    |
+----+------+
1 row in set (0.01 sec)

可以看到,虽然我们没有提交事务B,但是在事务A中还是可以看到最新的数据,这就是读未提交的功劳;

这种隔离级别的特点就是永远都可以读到最新的数据;缺点也很明显,就是在一个事务操作中,会读到一个数据的不同版本,这样就会给人一种数据紊乱的错觉;

下面几种隔离级别的例子类似,都是先改隔离级别的参数,然后在多个事务中进行读写操作

  • 读提交

定义:当前事务运行期间,可以读到其他事务提交过的数据,没提交的读不了

  • 可重复读(默认)

定义:当前事务运行期间,读到的数据始终保持一致,即事务启动时读到的是什么数据,那么在整个事务期间都是读到这个数据,不会再变

  • 串行化

定义:从字面意思来理解,有点类似单线程运行,比如现在有一个事务在对一行数据进行写操作,那么另一个事务就必须等待前一个事务提交后才可以继续操作;

3. 事务隔离的实现

事务的隔离主要是通过视图的概念来实现的;

假设现在事务的隔离级别是可重复读:

mysql> show variables like 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.05 sec)

那么当事务启动时,就会创建一个视图,用来记录当前的数据;

此时不管其他事务怎么更新数据,前面启动的事务都不会受到影响;

这时我们就可以说通过不同的视图,实现了事务的隔离;

上面我们用读未提交的隔离级别举了一个例子,因为读未提交的隔离级别是最低的,所以看不出隔离的效果;

感兴趣的可以用默认的隔离级别(可重复读)重复上面的例子,就会看到不管怎么改,之前启动的事务都不会受到新事务的影响;

总结

事务的启动方式有显式启动,隐式启动;推荐显式启动,通过begin启动,commit提交,rooback回滚;同时 set autocommit = 1,这样就可以防止隐式启动带来的长事务问题;

事务的隔离级别有四种:读未提交读提交、可重复读、串行化;隔离级别依次递增;

事务的隔离是通过创建不同的视图来实现的:不同时刻启动的事务,会创建不同的视图;