事务初探与MySQL事务隔离级别详解

技术文档网 2021-04-25

事务初探

为啥要引入事务

  • 事务是一个独立的工作单元

  • 事务内的语句要么全部执行成功,要么全部失败

  • 保持数据一致性

事务的ACID特性

事务拥有四个重要的特性(ACID):原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)

  • 原子性Atomicity

    事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样

  • 一致性Consistency

    指事务将数据库从一种状态转变为另一种一致的的状态。事务开始前和结束后,数据库的完整性约束没有被破坏。例如工号带有唯一属性,如果经过一个修改工号的事务后,工号变的非唯一了,则表明一致性遭到了破坏。

  • 隔离性Isolation

    要求每个读写事务的对象对其他事务的操作对象能互相分离,即该事务提交前对其他事务不可见。 也可以理解为多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。这指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。例如一个用户在更新自己的个人信息的同时,是不能看到系统管理员也在更新该用户的个人信息(此时更新事务还未提交)。

    注:MySQL 通过锁机制来保证事务的隔离性。

  • 持久性Durability

    事务一旦提交,则其结果就是永久性的。即使发生宕机的故障,数据库也能将数据恢复,也就是说事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。这只是从事务本身的角度来保证,排除 RDBMS(关系型数据库管理系统,例如 Oracle、MySQL 等)本身发生的故障。

    注:MySQL 使用 redo log 来保证事务的持久性

MySQL事务的四种隔离级别

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

Trade-off:隔离级别越高,效率越低

隔离级别 脏读可能性 不可重复读可能性 幻读可能性 加锁读
未提交读(Read uncommitted) 可能 可能 可能 不可能
已提交读(Read committed) 不可能 可能 可能 不可能
可重复读(Repeatable read) 不可能 不可能 可能 不可能
可串行化(Serializable ) 不可能 不可能 不可能 可能

MySQL中事务相关常用命令

  1. 显式开启事务

    BEGIN;
    START TRANSACTION;
    
  2. 事务提交语句

    COMMIT
    
  3. 事务回滚语句

    ROLLBACK;
    
  4. 查看当前隔离级别

    SHOW VARIABLES LIKE 'transaction_isolation';
    select @@tx_isolation;
    
  5. 设置事务语句

    SET [GLOBAL | SESSION] TRANSACTION
        transaction_characteristic [, transaction_characteristic] ...
    
    transaction_characteristic: {
        ISOLATION LEVEL level
      | access_mode
    }
    
    level: {
         REPEATABLE READ
       | READ COMMITTED
       | READ UNCOMMITTED
       | SERIALIZABLE
    }
    
    access_mode: {
         READ WRITE
       | READ ONLY
    }
    
    set session transaction isolation level read committed;
    

实验相关数据库表

CREATE DATABASE trx_test;
USE trx_test;

CREATE TABLE test(
    `id`    bigint(20) unsigned NOT NULL,
    `username`  varchar(64) NOT NULL
) ENGINE=InnoDB;

INSERT INTO test VALUES (1, 'Tom');

READ UNCOMMITTED(读未提交)

  • 在READ UNCOMMITTED隔离级别,事务中的修改即使没有提及,对其他事务也是可见的。
  • 事务可以读取到未提交的数据,此现象也称之为脏读
  • READ UNCOMMITTED这种级别一般都不会使用,而且任何操作都不会加锁

实验步骤

  1. 开启两个mysql终端,查看并设置当前session事务隔离级别为READ UNCOMMITTED

    use trx_test;
    show variables like 'transaction_isolation';
    set session transaction isolation level read uncommitted;
    
  2. 在console 1中开启一个事务,插入一条新数据

    begin;
    insert into test values (2, 'LaoWang');
    select * from test;
    
  3. 在console 2中开启一个事务后查看表中的数据

    begin;
    select * from test; -- 可看到id = 2的数据
    
事务A 事务B
设置事务隔离级别为READ UNCOMMITTED 设置事务隔离级别为READ UNCOMMITTED
开始事务A:begin;
insert into test values (2);
select * from test;
开始事务A:begin;
select * from test;

READ COMMITTED(读提交)

  • 一个事务开始时只能看到已经提交的事务所做的修改

  • 这个级别也叫做不可重复读,因为执行多次同样的查询可能会得到不同的结果

  • 大多数数据库的默认隔离级别为READ COMMITTED,但MySQL不是

实验步骤

  1. 开启两个mysql终端,查看并设置当前session事务隔离级别为READ COMMITTED

    use trx_test;
    show variables like 'transaction_isolation';
    set session transaction isolation level read committed;
    
  2. 在console 1中开启一个事务,插入一条新数据

    begin;
    insert into test values (4, 'Leon');
    
  3. 在console 2中开启一个事务后查看表中的数据

    begin;
    select * from test; -- 看不到id = 4的数据
    
  4. 在console 1中提交事务

    commit;
    
  5. 此时在console 2中可以看到

    select * from test; -- 看得到id = 4的数据
    

不可重复读表现在于在同一个事务之中,两个相同的查询得到的查询结果却不同

Console 1 Console 2
设置事务隔离级别为READ COMMITTED 设置事务隔离级别为READ COMMITTED
开始事务A:begin;
插入一条新数据:insert into test values (4, 'Leon');
开始事务B:begin;
查询数据:select * from test; (看不到id = 4的数据)
事务 A 提交: commit;
再次查询数据:select * from test; (id = 4的数据可见)
事务 B提交:commit;

REPEATABLE READ(可重复读)

  • 可重复读保证了在同一事务中多次读取同样记录的结果是一致的

  • 可重复读无法解决幻读问题

    幻读指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新记录,当之前的事务再次读取时该范围的记录会产生幻行

  • 可重复读是MySQL的默认事务隔离级别, InnoDB存储引擎通过多版本并发控制(MVCC)解决了幻读的问题

实验步骤

  1. 开启两个mysql终端,查看并设置当前session事务隔离级别为REPEATABLE READ

    use trx_test;
    show variables like 'transaction_isolation';
    set session transaction isolation level  repeatable read;
    
  2. 在console 1中开启一个事务

    begin;
    select * from test;
    
  3. 在console 2中开启一个事务后查看表中的数据

    begin;
    select * from test;
    
  4. 在console 1中插入一条数据后提交事务

    insert into test(id,username) values(10,'zhangsan');
    commit;
    
  5. 此时在console 2中执行查询语句

    select * from test;
    
Console 1 Console 2
设置事务隔离级别为REPEATABLE READ 设置事务隔离级别为REPEATABLE READ
开始事务A:begin;
查询:select * from test;
开始事务B:begin;
查询数据:select * from test; (看不到id = 10的数据)
插入一条数据
insert into test(id,username) values(10,'zhangsan');
事务 A 提交: commit;
再次查询数据:select * from test; (看不到id = 10的数据)

注:关于幻读

MySQL使用next-key lock + MVVC解决幻读问题: https://dev.mysql.com/doc/refman/5.7/en/innodb-next-key-locking.html

为复现幻读,需要设置innodb_locks_unsafe_for_binlog为1 (禁用Gap Lock)

在事务B中插入一条数据insert into test(id,username) values(10,'john'); 会产生主键冲突

SERIALIZABLE(序列化)

  • SERIALIZABLE是最高隔离级别,通过强制事务串行执行,避免了幻读问题
  • SERIALIZABLE可简单理解为在读取每一行数据时都加锁

实验步骤

实验步骤

  1. 开启两个mysql终端,查看并设置当前session事务隔离级别为SERIALIZABLE

    use trx_test;
    show variables like 'transaction_isolation';
    set session transaction isolation level serializable;
    
  2. 在console 1中开启一个事务,插入一条数据

    begin;
    insert into test(id, usename) values(15, 'LaoZhang');
    
  3. 在console 2中开启一个事务后查看表中的数据

    begin;
    select * from test;
    
  4. 在console 1中提交事务

    commit;
    
  5. 此时在console 2中会立即返回数据

Console 1 Console 2
设置事务隔离级别为SERIALIZABLE 设置事务隔离级别为SERIALIZABLE
开始事务A:begin;
插入一条数据
insert into test(id, usename) values(15, 'LaoZhang');
开始事务B:begin;
查询数据:select * from test; (看不到任何数据,一直等待)
事务 A 提交: commit; 立即返回数据

相关文章

  1. supervisor使用教程

    一、安装 1:easy_install 安装: easy_install supervisor 2:pip 安装: pip install supervisor 3:Debian / Ubuntu

  2. gitlab的ssh key不生效的问题

    在用 gitlab 的管理代码时发现一个问题:如果用 http 协议,每次 push 的时候都需要输入用户名和密码,如果 用 ssh 协议,先要生成公钥: ssh-keygen -t rsa -C

  3. UOJ安装指南

    这是一个UOJ的docker版本。在安装之前,请确认Docker已经安装在您的操作系统中。这个docker的映像是64位的版本,在32位的系统上安装可能会出现错误。 安装 请先下载 JDK7u76

  4. untu14.04下创建用户并赋予执行sudo命令的权限

    untu14.04下创建用户并赋予执行sudo命令的权限 创建用户:adduser +用户名(该命令在home下生成用户目录并创建用户) 1. 切换到root用户下 2. /etc/sud

  5. 如何查看当前apache的工作模式prefork worker还是event模式?

    查看apache工作模式 $ apachectl -V (注:apachectl可理解为apache control,其实是一段bash脚本) Server version: Apache/2.4.

随机推荐

  1. supervisor使用教程

    一、安装 1:easy_install 安装: easy_install supervisor 2:pip 安装: pip install supervisor 3:Debian / Ubuntu

  2. gitlab的ssh key不生效的问题

    在用 gitlab 的管理代码时发现一个问题:如果用 http 协议,每次 push 的时候都需要输入用户名和密码,如果 用 ssh 协议,先要生成公钥: ssh-keygen -t rsa -C

  3. UOJ安装指南

    这是一个UOJ的docker版本。在安装之前,请确认Docker已经安装在您的操作系统中。这个docker的映像是64位的版本,在32位的系统上安装可能会出现错误。 安装 请先下载 JDK7u76

  4. untu14.04下创建用户并赋予执行sudo命令的权限

    untu14.04下创建用户并赋予执行sudo命令的权限 创建用户:adduser +用户名(该命令在home下生成用户目录并创建用户) 1. 切换到root用户下 2. /etc/sud

  5. 如何查看当前apache的工作模式prefork worker还是event模式?

    查看apache工作模式 $ apachectl -V (注:apachectl可理解为apache control,其实是一段bash脚本) Server version: Apache/2.4.