Mysql事务隔离级别与MVCC


1、为什么要有事务

 提到事务,我们总是能想到那个最经典的转账案例。用户A给用户B转账¥200,在数据库上实现即:用户A账户减少¥200,用户B账户增加¥200.如果没有事务,当A用户的账户数据减少后,突然出现异常,导致用户B账户增加操作没有执行到。那么显然这样是会有问题的,明明A用户账户少了,B用户账户却毫无变化。
因此需要事务来保证整条转账过程的完整性,要么全部执行成功,要么如果有一条语句执行失败就全部失败。
注:由于MyIsam不支持事务,所以文章全篇都是默认在Innodb存储引擎下讨论。

2、什么是事务

 事务即保证当前一组数据库的操作完整性,要么全部执行成功,要么就全部执行失败。
事务四大特性ACID。

  • 原子性(Atomicity):一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性,大部分的回滚操作是基于undo log的回滚日志来完成。undo log 也是实现MVCC机制的关键
  • 一致性(Consistency):在度娘和谷哥查阅都是说要保证开始和结束的状态保持一致性,对于这句话还是有一点困惑。个人理解,如果只有当前一个事务,那么最终的结果只能有开始和结束两种状态,这里可以参考原子性。如果在当前事务执行过程中,还有另外一个事务也在操作当前事务中的数据,那么就要根据一致性读规则来保证事务的一致性,具体可以参考后面的mvcc实现机制中的一致性视图
  • 隔离性(Isolation):在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。因此Mysql也定义了对应的四个隔离级别。读未提交(RU),读已提交(RC),可重复度(RR),串行化(SERIALIZABLE)
  • 持久性(Duralibity):一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。–即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态

 在Mysql数据库中的事务是在引擎中来实现的,原生的MyIsam引擎是不支持事务的,因此多采用Innodb引擎。在建表后添加engine=Innodb可设置引擎类型。Mysql在5.5.5版本后默认采用了Innodb引擎。
 相比于MyIsam存储引擎,Innodb有较多的优势如支持事务、支持更细粒度的行锁,当然也因此带来了一些并发访问问题,如脏读、不可重复读和幻读等。后面会围绕这几个问题开展讨论。

3、隔离级别

 数据表准备:

mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `k` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);
  • 读未提交(READ UNCOMMITTED):一个事务A还没有提交就可以被另一个事务B读取到(感觉叫未提交读更容易理解)。这样就会产生一个问题就是脏读。如果事务B用读取到的脏数据去做一系列操作,一旦事务A回滚,那么事务B的操作很大概率会产生一系列业务问题。
  • 读已提交(READ COMMITTED):事务B读取到了一行数据.之后事务A对其执行update操作,将其更新为k=3后提交事务,事务B再去读取的时候,会发现数据不一致,这种情况称为不可重复读。
  • 可重复读(REPEATABLE READ):在读已提交的基础上,可重复读是要求事务B只有自己当前的事务已经提交了才可以查看到最新值。但是也会产生新的问题,当事务A中插入一条数据,事务B在查询时会发现多一条数据,这个就叫幻读。
  • 串行化(SERIALIZABLE):采用加锁机制,不允许不同的事务对同一行数据执行操作。一旦该行数据开启一个事务,后面所有在当前行所在的事务都要阻塞等待持有锁的事务提交后才可执行。

4、MVCC工作原理

基本原理:
 基于上述对隔离级别的介绍,现在开始讲述一下隔离级别的实现,核心原理就是MVCC(多版本并发控制),行数据有多个版本,每个数据版本有自己的 row trx_id,每个事务或者语句有自己的一致性视图。普通查询语句是一致性读,一致性读会根据 row trx_id 和一致性视图确定数据版本的可见性。对于可重复读,查询只承认在事务启动前就已经提交完成的数据;对于读提交,查询只承认在语句启动前就已经提交完成的数据
一致性视图:
 可以将一致性视图理解为一个快照,事务中创建一个快照后,就只关心自己的,外界如何操作与我无关。快照结束后可能会结束事务(RR),也可能会开启新的快照(RC)。再次开启快照就要基于外界的改动之后来判断。如果外界有update并且commit,那么下一次开启的视图就会受此影响,查询的结果与上一次视图不同。
RC隔离级别下的一致性视图:
 事务中的每一条select都会生成一个视图。
RR隔离级别下的一致性视图:
 在事务开启的第一个select语句执行后才会开启视图,并且只有一个。
隐藏字段:
 DB_ROW_ID:隐藏主键,当数据表没有主键时会默认创建
 DB_TRX_ID:事务id,全局递增
 DB_ROLL_PTR:回滚指针,在执行undo log 回滚操作时会使用该数据
注:由于读未提交隔离级别没有视图概念,更新完会被立刻读取到,串行化是通过加锁来控制并发访问,因此下面两组实验仅有RC和RR级别。

查询判断规则:
 先判断事务是否提交,再判断视图创建先后顺序。

  1. 版本未提交,不可见;
  2. 版本已提交,但是是在视图创建后提交的,不可见;
  3. 版本已提交,而且是在视图创建前提交的,可见。

1、RR隔离级别

 当前表信息:

mysql> desc t;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id    | int(11) | NO   | PRI | NULL    |       |
| k     | int(11) | YES  |     | NULL    |       |
+-------+---------+------+-----+---------+-------+
2 rows in set (0.00 sec)
mysql> select * from t;
+----+------+
| id | k    |
+----+------+
|  1 |    1 |
|  2 |    2 |
+----+------+
2 rows in set (0.00 sec)
mysql> show variables like '%isolation%';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
| tx_isolation          | REPEATABLE-READ |
+-----------------------+-----------------+
2 rows in set (0.00 sec)

查询过程:

  1. 事务A先开启事务,事务id记为100。
  2. 事务B开启事务,事务id记为200。
  3. 事务A执行查询操作,开启视图。
  4. 事务B执行更新操作并且提交。开启视图commit之后视图结束。
  5. 事务A执行查询操作,而事务A还处在自己第一条sql语句创建的视图中,此时是不会受到外界所影响,事务B的提交事务A是不能接受的,所以当前的查询结果依旧是1,只有事务A提交后再查询的时候用才可以看到最新值。

2、RC隔离级别

当前表信息:

mysql> desc t;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id    | int(11) | NO   | PRI | NULL    |       |
| k     | int(11) | YES  |     | NULL    |       |
+-------+---------+------+-----+---------+-------+
2 rows in set (0.00 sec)
mysql> select * from t;
+----+------+
| id | k    |
+----+------+
|  1 |    1 |
|  2 |    2 |
+----+------+
2 rows in set (0.00 sec)
mysql> show variables like '%isolation%';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | READ-COMMITTED  |
| tx_isolation          | READ-COMMITTED  |
+-----------------------+-----------------+
2 rows in set (0.00 sec)

查询过程

  1. 事务A先开启事务,事务id记为100。
  2. 事务B开启事务,事务id记为200。
  3. 事务A执行查询操作,开启视图结果k=1。
  4. 事务B执行更新操作并且提交后数据k=3。开启视图commit之后视图结束。
  5. 事务A执行查询操作,是一个视图1已经结束,此时事务A会重新开启新的视图3,所以在当前新的视图下视图2已经commit,所以是能被视图3读到,因此此时读到的数据是k=3。

3、begin与start transaction with consistent snapshot

 在实验过程中有遇到使用begin替代start transaction with consistent snapshot开启事务,最终并没有出现预期的结果,主要是因为begin虽然表示开启事务,但是此时并没有分配事务id,而start transaction with consistent snapshot是执行后立即开启事务id。

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

mysql> select * from information_schema.innodb_trx \G;
Empty set (0.00 sec)

ERROR: 
No query specified

mysql> start transaction with consistent snapshot;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> select * from information_schema.innodb_trx \G;
*************************** 1. row ***************************
                    trx_id: 422130645993296
                 trx_state: RUNNING
               trx_started: 2021-03-01 08:30:14
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 0
       trx_mysql_thread_id: 23
                 trx_query: select * from information_schema.innodb_trx
       trx_operation_state: NULL
         trx_tables_in_use: 0
         trx_tables_locked: 0
          trx_lock_structs: 0
     trx_lock_memory_bytes: 1136
           trx_rows_locked: 0
         trx_rows_modified: 0
   trx_concurrency_tickets: 0
       trx_isolation_level: READ COMMITTED
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
1 row in set (0.00 sec)

ERROR: 
No query specified

4、相关配置sql语句。

#调整session失效时间参数:
set global wait_timeout=500;
set  global interactive_timeout=500;
#设置隔离级别:
set tx_isolation='read-committed';
set transaction_isolation='read-committed';
#事务id查询:
select * from information_schema.innodb_trx \G;

总结

 该篇文章主要是对Mysql事务、事务隔离级别以及隔离级别的实现原理进行了一个简单的描述。主要是区分RR和RC下,隔离级别的实现区别,并且讲述了实现的关键一致性视图。Mysql是一个很庞大的体系,学习Mysql的架构对于后面的深入研究会有很大的帮助,在这里我列出了暂时想到的学习目录(可能会有改动)由浅到深,逐步击破。

  • Mysql锁机制
  • 索引以及查询优化
  • Mysql 日志文件(binlog与redolog)
  • mybatis框架
  • mybatis plus以及tkmybatis使用
  • mybatis 插件原理以及实战
  • Mysql 分表分库实现
  • 集群搭建
  • 高可用架构设计

引用

1、Mysql实战45讲
2、高性能Mysql
3、Mysql官方手册

系列文章链接:

  1. Mysql架构概览

文章仅用于学习记录分享,如有问题,感谢纠正!!!
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农进行时

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值