一、前提

时过一年重新拾起博文记录,希望后面都能坚持下来。 接着之前MySql的学习,先记录下这篇。

以下都是基于mysql8 innodb存储引擎进行分析的。

二、事务的ACID特性

  1. A(Atomicity) 原子性

指整个数据库事务是不可分割的单位,整个事务中的所有操作要么全部提交成功,要么全部失败回滚。只有使事务中所有的数据库操作都执行成功,才算整个事务执行成功。否则事务中任何一个SQL语句执行失败,那么这个事务就是执行失败的, 已执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态。
  1. C(consistency) 一致性

一致性事务将数据库从一种状态转变为下一种一致的状态。在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
例如:在表中有个字段为姓名为唯一约束,即在表中姓名不能重复。如果一个事务对姓名字段进行了修改,但是在事务提交或事务操作发生回滚后,表中的姓名变得非唯一了,这就破坏了事务的一致性要求,即事务将数据从一种状态变为了一种不一致的状态。
  1. I(isolation) 隔离性

事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见。通常可以使用锁来实现。
  1. D(durability) 持久性

事务一旦提交,其结果就是永久性的。即使发生宕机等故障,数据库也能将数据恢复。
需要注意的是:只能从事务本身的角度来保证结果的永久性,例如:在事务提交后,所有变化都是永久的。即使当数据因为崩溃而需要恢复时,也能保证恢复后提交的数据都不会丢失。但如果不是数据库本身发生故障,而是一些外部的原因导致数据库发生问题,则有可能是提交的数据丢失(RAID卡损坏)。
因此持久性保证事务系统的高可靠性,而不是高可用性。对于高可用性的实现,事务本身并不能保证,需要一些系统共同配合完成。

三、事务的4种隔离级别

  • Read Uncommitted - 未提交读

在该隔离级别下的事务会读取到其未提交事务的数据,此种现象也称之为脏读
步骤 事务1 事务2
1

设置隔离级别

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

mysql> select @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | READ-UNCOMMITTED | +-------------------------+ 1 row in set (0.00 sec)
2

开启事务1

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

mysql> select * from t;
Empty set (0.00 sec)
3
无需管隔离级别,只开启事务2并插入记录
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t select 1, '1';
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0
4

此时能查到事务2中未提交事务中的数据,这就是脏读。

mysql> select * from t; +------+------+ | id | name | +------+------+ | 1 | 1 | +------+------+ 1 row in set (0.00 sec)
注意:设置隔离级别之前,记得查看当前隔离级别的key, 有可能版本不一样对应的key不一样。(另外表可以自己随意选择)
1 mysql> show variables like '%isolation%'; 2 +-----------------------+------------------+ 3 | Variable_name | Value | 4 +-----------------------+------------------+ 5 | transaction_isolation | READ-UNCOMMITTED | 6 +-----------------------+------------------+ 7 1 row in set (0.00 sec)
在实际的业务场景中应该都不允许脏读出现,既然这么评价不好为什么还会出现呢? 但其实这个隔离级别下的数据库并发性能是最好的。


  • Read Committed - 提交读

一个事务可以读取另一个已提交的事务,多次读取会造成不一样的结果。这种现象也被称为不可重复读。举例说明:
步骤 事务1 事务2
1 先设置隔离级别
mysql> set @@session.transaction_isolation = 'READ-COMMITTED';
Query OK, 0 rows affected (0.00 sec)

mysql> select @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | READ-COMMITTED | +-------------------------+ 1 row in set (0.00 sec)
2 开启事务1,查询t表记录为空
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t;
Empty set (0.00 sec)
3

无需管隔离级别,只开启事务2并插入记录

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

mysql> insert into t select 2, '2';
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0
4

继续查询表t依然显示为空

(没有读取到事务2未提交的数据,所以不存在脏读问题)

mysql> select * from t;
Empty set (0.00 sec)
5

提交事务2

mysql> commit;
6

继续查询表t,此时能查询事务2已提交的数据记录(出现前后查询不一致)

mysql> select * from t; +------+------+ | id | name | +------+------+ | 2 | 2 | +------+------+
总结:在隔离级别中解决了脏读问题,但存在不可重复读的问题(事务中会读取其他已提交事务中的数据)
  • Repeatable Read - 可重复读

该隔离级别是MySQL默认的隔离级别,在同一个事务里select的结果是事务开始时间时间点的状态,解决了不可重复读问题。
但其中会出现幻读现象:基于可重复读的基础上查询结果是一样的,但是当对某些行进行更新或者插入时却会受到影响操作不了,就形成了幻读。例如:
步骤 事务1 事务2
1

设置隔离级别

mysql> set @@session.transaction_isolation = 'REPEATABLE-READ';

mysql> select @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | REPEATABLE-READ | +-------------------------+
2

开启事务1,并查询

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

mysql> select * from t; +------+------+ | id | name | +------+------+ | 3 | 3 | +------+------+
3

无需管隔离级别,只开启事务2并插入记录

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

mysql> insert into t select 4, '4';
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0
4 继续查询表t依然只有一条记录id=3

(没有读取到事务2未提交的数据,所以不存在脏读问题)

mysql> select * from t; +------+------+ | id | name | +------+------+ | 3 | 3 | +------+------+
5
将第二个窗口中的事务提交。
mysql> commit;
6 继续查询表t依然只有一条记录id=3

(没有读取到事务2已提交的数据,所以不存在不可重复读问题)

mysql> select * from t; +------+------+ | id | name | +------+------+ | 3 | 3 | +------+------+
7
接着插入一条4的记录,但提示插入不了,提示主键冲突问题。
然而查询却没有这条id=4记录。 这就是幻读现象
mysql> insert into t select 4, '4';
ERROR 1062 (23000): Duplicate entry '4' for key 'PRIMARY' mysql> select * from t; +----+------+ | id | name | +----+------+ | 3 | 3 | +----+------+ 1 row in set (0.00 sec)
8 另一种幻读现象:接着上面操作,不插入记录只更新记录,将name 更新成 '5'后,发现有两行受影响
mysql> update t set name = '5';
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0 mysql> select * from t; +----+------+ | id | name | +----+------+ | 3 | 5 | | 4 | 5 | +----+------+
总结:在隔离级别中解决了脏读问题、不可重复读的问题,但会存在幻读问题。
但Innodb引擎提供了间隙锁:innodb-next-key-locks, 解决了幻读问题。基于上面结果演示下:
步骤 事务1 事务2
9

查询时加上间隙锁

mysql> begin;
mysql> select * from t where id > 0 for update; +----+------+ | id | name | +----+------+ | 3 | 5 | | 4 | 5 | +----+------+


10

插入记录为6的数据就会发现插入这条记录获取锁超时,自动异常

insert into t select 6, '6';
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction


这样成功的避免了幻读问题,阻止了其他事务可能影响到我当前事务所涉及到的数据范围。
  • Serializable - 可串行化

在该隔离级别下事务都是串行顺序执行的,MySQL 数据库的 InnoDB 引擎会给读操作隐式加一把读共享锁,从而避免了脏读、不可重读复读和幻读问题。

四 总结

1、脏读:在一个事务中会读取到其未提交事务的数据,此种现象也称之为脏读

2、不可重复读:一个事务可以读取另一个已提交的事务,多次读取会造成不一样的结果。这种现象也被称为不可重复读

3、幻读:基于可重复读的基础上查询结果是一样的,但是当对某些行进行更新或者插入时却会受到影响操作不了,就形成了幻读。

隔离级别
脏读
不可重复读
幻读
读未提交(uncommitted read)
可能出现
可能出现
可能出现
读提交(committed read)
不会出现
可能出现
可能出现
可重复读(Repeatable Read)
不会出现
不会出现
可能出现(加上间隙锁就不会)
可串行化(Serializable)
不会出现
不会出现
不会出现

五 参考文献

《MySql 技术内幕(Innodb)第二版》

https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html

Mysql 四种事务隔离级别的更多相关文章

  1. MySQL创建数据库并支持中文字符的操作方法

    我们首先看一下MySQL官方文档:5.7CREATE {DATABASE | SCHEMA} [IF NOT EXI......

  2. 详解MySQL与Spring的自动提交(autocommit)

    1 MySQL的autocommit设置MySQL默认是开启自动提交的,即每一条DML(增删改)语句都会被作为一个单......

  3. MySQL 5.6.35 索引优化导致的死锁案例解析

    本文描述了在一次压测过程中,由于Index Merge优化导致的死锁,详细描述了死锁产生的原因以及解决方案,并顺便介......

  4. MySQL全面瓦解16:存储过程相关

    概述大多数SQL语句都是针对一个或多个表的单条语句。但并非所有业务都这么简单,经常会有复杂的操作需要多条语句才能完成......

  5. MySql8 WITH RECURSIVE递归查询父子集的方法

    背景开发过程中遇到类似评论的功能是,需要时用查询所有评论的子集。不同数据库中实现方式也不同,本文使用Mysql数据库......

  6. PostgreSQL 实现给查询列表增加序号操作

    利用 ROW_NUMBER() over( ) 给查询序列增加排序字段SELECT ROW_NUMBER() ove......

  7. MySQL中外键的创建、约束以及删除

    前言在MySQL 3.23.44版本后,InnoDB引擎类型的表支持了外键约束。外键的使用条件:1.两个表必须是In......

  8. ORACLE 利用 REPLACE函数替换字段字符串

    REPLACE(string,s1,s2) string 希望被替换的字符或变量 s1 被替换的字符串 s2 要替换......

  9. CentOS PostgreSQL 12 主从复制(主从切换)操作

    主从复制1. 基于文件的日志传送创建一个高可用性(HA)集群配置可采用连续归档,集群中主服务器工作在连续归档模式下,......

  10. 基于Redo Log和Undo Log的MySQL崩溃恢复流程

    在之前的文章「简单了解InnoDB底层原理」聊了一下MySQL的Buffer Pool。这里再简单提一嘴,Buffe......

随机推荐

  1. 爬虫-urllib3模块的使用

    urllib3是一个功能强大,对SAP健全的 HTTP客户端,许多Python生态系统已经使用了urllib3。一、......

  2. Java中的深浅拷贝问题,你清楚吗?

    一、前言拷贝这个词想必大家都很熟悉,在工作中经常需要拷贝一份文件作为副本。拷贝的好处也很明显,相较于新建来说,可以节......

  3. Java压缩集合的三种方法

    前言这个问题算是开发当中偶尔会遇到的一个小问题,比如如何将两个集合压缩成为一个逻辑集合。如果你不理解,我们可以看一个......

  4. Linux中让终端输入变为非阻塞的三种方法

    介绍在linux下每打开一个终端,系统自动的就打开了三个文件,它们的文件描述符分别为0,1,2,功能分别是“标准输入......

  5. python工具系列-弱口令工具

    python弱口令扫描工具-初始版本篇博客记录下基于python写的gui小工具,也是我学python以来自己动手写......

  6. Q-Q图原理详解及Python实现

    【导读】在之前的《数据挖掘概念与技术 第2章》的文章中我们介绍了Q-Q图的概念,并且通过调用现成的python函数,......

  7. 为什么.NET Standard 仍然有意义?

    .NET Standard 是.NET 官方的API规范,可在许多.NET环境中使用。之所以存在,面向.NET St......

  8. 在JavaScript中查找字符串中最长单词的三种方法(推荐)

    本文基于Free Code Camp基本算法脚本“查找字符串中最长的单词”。在此算法中,我们要查看每个单词并计算每个......

  9. 详解如何使用Pytest进行自动化测试

    为什么需要自动化测试自动化测试有很多优点,但这里有3个主要的点可重用性:不需要总是编写新的脚本,除非必要,即使是新的......

  10. python日志通过不同的等级打印不同的颜色(示例代码)

    1,不用第三方库# coding: utf-8import loggingBLACK, RED, GREEN, YE......