• mysql innodb 事务隔离性实现原理


    隔离性是什么?

    数据库的四大特性ACID

    • 原子性(A):事务中的操作要么全部成功要么全部失败(通过undo log实现)
    • 一致性©:事务执行前后数据是处于一个一致性的状态(通过原子性,隔离性,持久性实现)
    • 隔离性(I):事务之间是隔离开的,互相不会干扰(通过mvcc和锁实现)
    • 持久性(D):事务一旦提交,之后的其他操作或者故障都不该对此结果产生影响(通过redo log实现)

    事务没有隔离性会存在什么问题?

    • 脏读:脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
    • 幻读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
    • 不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

    事务的隔离级别

    为了解决脏读,幻读,不可重复读的问题,数据库为我们提供了四种隔离级别

    • 读未提交
    • 读已提交 -解决脏读
    • 可重复读 -解决不可重复读,脏读
    • 串行化-解决脏读,幻读,不可重复读

    什么是MVCC?

    事务的隔离性使用的是MVCC和锁实现的,那么什么是MVCC呢?

    • 定义:多版本并发控制,MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
    • 作用:MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。

    重要概念

    • 当前读:select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
    • 快照读:不加锁的select就是快照读,如果数据库的隔离级别是串行化的话回退化为当前读,产生快照读的原因是为了提高并发查询效率,快照读是基于MVCC实现的,可以将MVCC作为行锁的变种理解,多数情况下,避免了加锁操作,降低了开销,提高数据库的并发访问能力。

    MVCC解决了什么问题?

    数据库的并发有三种情景

    • 读读:无任何问题
    • 读写:有线程安全问题,可能会造成事务隔离问题,可能遇到脏读,幻读,不可重复读
    • 写写:有线程安全问题,可能存在更新丢失问题

    MVCC解决的问题
    MVCC是一种用来解决读写冲突的无锁并发控制,也就是为事务分配单项增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照,所以MVCC可以为数据库解决以下问题:

    • 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。
    • 解决脏读、幻读、不可重复读等事务隔离问题。

    MVCC的实现原理

    三个隐藏字段

    • DB_TRX_ID:6字节,最近修改事务id,记录创建这条记录或者最后一次修改该记录的事务id
    • DB_ROLL_PTR:7字节,回滚指针,指向这条记录的上一个版本,用于配合undolog,指向上一个旧版本
    • DB_ROW_ID: 6字节,隐藏的主键,如果数据表没有主键,那么innodb会自动生成一个6字节的row_id,数据库有主键用主键没有主键用唯一键,没有唯一键则生成这个ID

    undo log

    • 在进行insert,delete,update操作的时候产生的方便回滚的日志。

    • 当进行insert操作的时候,产生的undolog只在事务回滚的时候需要,并且在事务提交之后可以被立刻丢弃

    • 当进行update和delete操作的时候,产生的undolog不仅仅在事务回滚的时候需要,在快照读的时候也需要,所以不能随便删除,只有在快照读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

    • 当一个事务要对一条数据进行修改的时候会对这行数据加排他锁,将当前记录拷贝到undolog中,拷贝完毕后,进行对应修改,并将隐藏字段中的事务ID改为新事务ID,回滚指针指向之前拷贝的副本记录。
      undo log 历史版本图
      read view

    • 事务进行快照读操作的时候生成的读视图,在改事务执行快照读的那一刻,会生成一个数据系统当前的快照,记录并维护系统当前活跃的事务id,事务的id值是递增的。主要作用是用来进行可见性分析

    可见性分析

    • 目的:之前做的修改能否对读有影响

    • 过程:将要被修改的数据的最新记录中的DB_TRX_ID取出来,与系统当前其他活跃事务ID进行比较,如果DB_TRX_ID跟read view的属性做了比较不符合可见性,那么就通过DB_ROLL_PTR回滚指针去去undolog中的DB_TRX_ID进行遍历操作直到找到符合条件的版本。

    • 重要属性:trx_list(read view生成时刻所有正在活跃的事务ID放到这个集合中),up_limit_id(记录trx_list中事务ID最小的ID),low_limit_id(read view 生成时刻下一个暂未分配的事务ID)

    • 规则:1、首先比较DB_TRX_ID < up_limit_id,如果小于,则当前事务能看到DB_TRX_ID所在的记录,如果大于等于进入下一个判断,2、接下来判断DB_TRX_ID >= low_limit_id,如果大于等于则代表DB_TRX_ID所在的记录在Read View生成后才出现的,那么对于当前事务肯定不可见,如果小于,则进入下一步判断, 3、判断DB_TRX_ID是否在活跃事务中,如果在,则代表在Read View生成时刻,这个事务还是活跃状态,还没有commit,修改的数据,当前事务也是看不到,如果不在,则说明这个事务在Read View生成之前就已经开始commit,那么修改的结果是能够看见的。

      概述图及流程图如下

    在这里插入图片描述
    在这里插入图片描述

    可重复读&读已提交实现原理

    原理

    • 生成read_view时间不同
    • 可重复读是在事务开始时生成的read_view
    • 读已提交是在每次的快照读生成新的read_view

    不可重复读演示

    建表如下
    
    create table t1
    (
        id   int         not null
            primary key,
        name varchar(20) null,
        age  int         null
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    插入数据
    
    INSERT INTO test_db.t1 (id, name, age) VALUES (1, '1', 1);
    INSERT INTO test_db.t1 (id, name, age) VALUES (2, '2', 2);
    
    • 1
    • 2
    • 3
    • 4
    事务1执行
    SELECT @@transaction_isolation;
    
    ###################RR隔离级别下read view生成时机##########################
    
    # 1.关闭自动事务提交
    set autocommit  = 0;
    # 2.开启事务
    begin ;
    # 4.查询表数据
    select * from t1;
    # 8.查询表数据
    select * from t1;
    # 10.再次查询表数据(查不到说明read view是在事务开始的时候生成的)
    select * from t1;
    # 11.提交事务
    commit ;
    
    ###################RC隔离级别下read view生成时机##########################
    
    # 1.设置隔离级别为RC
        set session transaction isolation level read committed ;
    
    # 2.开启事务
    begin ;
    # 6.查询表数据
    select * from t1;
    
    # 7.在此查询表数据(可以查到数据)
    select * from t1;
    
    # 8.提交
    commit ;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    事务2执行
    ###################RR隔离级别下read view生成时机##########################
    # 3.开启事务
    begin ;
    # 5.查询表数据
    select * from t1;
    # 6.新增一条数据
    insert into t1 values (3,'3',3);
    # 7.在此查询表数据
    select * from t1;
    # 9.提交事务
    commit;
    
    ###################RC隔离级别下read view生成时机##########################
    
    # 3.开启事务
    begin ;
    # 4.新增数据
    insert into t1 values (4,'4',4);
    # 5.查询表数据
    select * from t1;
    # 6.提交事务
    commit;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    幻读演示

    建表
    -- auto-generated definition
    create table user
    (
        id   int auto_increment
            primary key,
        name varchar(255) null,
        age  int          null
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    事务1执行
    ###################幻读产生##########################
    truncate table  user;
    # 0.数据准备
    INSERT into user VALUES (1,'1',20),(5,'5',20),(15,'15',30),(20,'20',30);
    # 1.开启事务
    begin ;
    # 3.查看数据
    select * from user;
    # 7.修改数据
    update user set name = '9' where age = 20;
    # 8.在此查询数据获取到了最新的数据
    select * from user;
    # 提交事务
    commit ;
    
    
    ###################解决幻读##########################
    
    # 1.设置锁的输出信息为on
    set global innodb_status_output_locks = on;
    # 2.开启事务
    begin ;
    # 5.查看锁信息
    show engine innodb status;
    # 6.加锁操作
    select * from user where age = 20 for update ;
    commit ;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    事务2执行
    ###################幻读产生##########################
    
    # 2.开启事务
    begin ;
    # 4.查看数据
    select * from user;
    # 5.新增数据
    insert into user values (25,'25',20);
    # 6.提交事务
    commit ;
    
    
    
    
    ###################解决幻读##########################
    # 3.开启事务
    begin ;
    # 4.查询数据
    select * from user where age = 20;
    # 7.新增数据会阻塞
    insert into user values (30,'30',20);
    # 8.提交事务
    commit ;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
  • 相关阅读:
    快速搭建SSM框架
    数据仓库任务调度器-Azkaban | 案例测试2<电话报警通知机制>
    【C语言】错题本(3)
    使用云API管理你的云服务器
    SpringBoot 整合【MybatisPlus、Shiro】实现权限认证信息
    转行网络工程师,软考和华为认证选哪个?
    2023天津公租房网上登记流程图,注册到信息填写
    netsh interface portproxy端口转发,从本地端口到本地端口不起作用的解决办法
    基于Echarts实现可视化数据大屏通用大数据可视化展示平台模板
    mmdet之Loss模块详解
  • 原文地址:https://blog.csdn.net/zyc1234576/article/details/126562655