zoukankan      html  css  js  c++  java
  • mysql-选择使用Repeatable read的原因

    mysql-选择使用Repeatable read的原因

    问题背景

    在mysql调优的过程中发现,mysql的默认隔离级别是可重复读(repeatable read),其他几类关系型数据库pg,以及sybase,oracle,sqlserver的默认的隔离级别都是读已提交(read committed)。

    我们都知道隔离级别一共有四种,读未提交,读已提交,可重复读,序列化。隔离级别越高,并发性能也就越低。

    疑问

    1、那么mysql为什么要选择使用可重复读来作为默认的隔离级别呢?

    2、可重复读,会带来哪些问题?

    3、我们在开发过程中是否要修改默认值,将其改为我们常见的读已提交呢?

    • 可重复读(Repeatable Read),简称为RR;
    • 读已提交(Read Commited),简称为RC;

    四种隔离级别

    首先我们了解下四种隔离级别的区别。

    • READ UNCOMMITTED

      :未提交读

      • 读取未提交内容,所有事务可看到其他未提交事务的结果,很少实际使用
      • 读取未提交的数据称为脏读(Dirty Read)
    • READ COMMITTED

      :提交读

      • 多数数据库的默认隔离级别(MySQL默认不是,默认为REPEATABLE-READ)
      • 满足隔离的简单定义:一个事务只能看到已提交事务所做的改变
      • 这种隔离级别,支持所谓的不可重读(Non-repeatable Read),同一事务的其他实例在该实例过程中可能有新commit,所以同一个select可能返回不同结果(同一个事务如何做到其他实例?
    • REPEATABLE READ

      :重复读

      • 可重复读(MySQL默认事务隔离),但可能出现幻读(Phantom Read)
      • 幻读(Phantom Read):当用户读取某范围数据行时,另一事务在此范围内插入新行,当用户再次读取此范围数据行时,读取到新的幻影行
      • InnoDB通过多版本并发控制MVCC机制解决该问题
      • PS:新版MySQL采用Next-Key锁来解决幻读问题
    • SERIALIZABLE

      :串行化

      • 最高隔离级别,强制事务排序(串行化),不会互相冲突
      • 每个读数据航增加共享锁
      • 此级别,可能导致大量超时现象和锁竞争

    按事务隔离级别来说,级别越低数据一致性保障效果越差,而并发能力则越强。

    为什么选择REPEATABLE READ?

    mysql为什么选择使用可重复读来作为默认的隔离级别呢?

    查了下文档,发现是有历史原因的,这和mysql的复制有关系,mysql的复制基于binlog,在配置文件中我们可以发现有一个参数binlog_format,binlog有三种格式

    # binary logging format - mixed recommended
    binlog_format=row
    
    statement:记录的是修改SQL语句
    row:记录的是每行实际数据的变更    
    mixed:statement和row模式的混合  
    

    在mysql5.0以前binlog只支持statement这种格式,这种格式在读已提交(read commited)这个隔离级别下主从复制是有bug的。

    产生bug的原因如下图:在主库上面执行先删除后插入,但是在从库如果binlog为statement格式,记录的顺序就是先插入后删除,从库执行的顺序和主库不一致,最后主库有数据,从库的数据被删掉了。

    mysql隔离级别.jpg

    因此mysql将可重复读(repeatable read)作为默认的隔离级别!

    当然在可重复读上面也有解决方案

    一是使用间隙锁,当session 1执行delete语句的时候,锁住间隙,session 2就会被阻塞

    二是将binlog_format设置为row格式,基于行的复制,就不会出现sql执行顺序不一样的问题。但是这个格式是mysql5.1以后才有的。由于历史的原因,mysql将默认的隔离级别设置为可重复读,并一直延续了下来,保证主从复制不出问题。

    可重复读,会带来哪些问题?

    1、隔离级别越高,并发能力越低。

    2、在可重复读级别下,如果使用间隙锁的方式,那么导致死锁的几率比读已提交大的多。

    select *from test where n_id < 5 for update
    在可重复读级别下,可以锁住间隙,防止其他事务插入数据。
    在读已提交级别下,不会影响插入,其他事务任然可以插入数据。
    

    3、在可重复读级别下,条件列未命中索引会锁表!而在读已提交隔离级别下只锁行

    INSERT INTO order_record SELECT
        * 
    FROM
        order_today 
    WHERE
        pay_success_time < '2020-03-08 00:00:00';
        
    在可重复读的隔离级别下,执行上面的sql,会对order_record加表锁,order_today逐步锁(扫描一个锁一个)
    当pay_success_time没有索引,或者因为其它原因导致没有走锁定的时候order_today就会被锁住。
    

    测试:

    mysql> show profiles;
    Empty set (0.00 sec)
    --默认隔离级别 REPEATABLE-READ  
    mysql> select @@global.tx_isolation,@@session.tx_isolation;
    +-----------------------+------------------------+
    | @@global.tx_isolation | @@session.tx_isolation |
    +-----------------------+------------------------+
    | REPEATABLE-READ       | REPEATABLE-READ        |
    +-----------------------+------------------------+
    1 row in set (0.00 sec)
    
    
    --测试1 使用默认的binlog_format
    mysql> select @@binlog_format;
    +-----------------+
    | @@binlog_format |
    +-----------------+
    | STATEMENT       |
    +-----------------+
    1 row in set (0.00 sec)
    --session 1 
    mysql> insert into test2 select * from test limit 500000;
    Query OK, 500000 rows affected, 1 warning (31.10 sec)
    Records: 500000  Duplicates: 0  Warnings: 0
    --session 2 可以看到插入test的数据会一直等待session 1执行完成才能插入
    mysql> insert into test values('4028e481513e66bc015156c3e359001a','1','1');
    Query OK, 1 row affected (29.48 sec)
    
    --测试2 将binlog_format设置为row
    mysql> set global binlog_format='row';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select @@binlog_format;
    +-----------------+
    | @@binlog_format |
    +-----------------+
    | ROW             |
    +-----------------+
    1 row in set (0.00 sec)
    -- session 1
    mysql>  insert into test2 select * from test limit 500000;
    Query OK, 500000 rows affected (32.09 sec)
    Records: 500000  Duplicates: 0  Warnings: 0
    --session 2 可以看到session 1不会阻塞session 2 的插入
    mysql> insert into test values('4028e481513e66bc015156c3e359001a','1','1');
    Query OK, 1 row affected (0.00 sec)
    
    --测试3 将binlog_format设置为mixed
    mysql> set global binlog_format='mixed';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select  @@binlog_format;
    +-----------------+
    | @@binlog_format |
    +-----------------+
    | MIXED           |
    +-----------------+
    1 row in set (0.00 sec)
    --session 1
    mysql>  insert into test2 select * from test limit 500000;
    Query OK, 500000 rows affected (32.16 sec)
    Records: 500000  Duplicates: 0  Warnings: 0
    --session 2 ,session 1会阻塞session2的插入
    mysql> insert into test values('4028e481513e66bc015156c3e359001a','1','1');
    Query OK, 1 row affected (30.71 sec)
    
    
    mysql在默认隔离级别可重复读(REPEATABLE-READ)时,binlog_format设置为statement和mixed都会阻塞,设置为row模式时不会阻塞。
    

    insert into select 有两种阻塞方式,一种是逐行锁住,一种是全表锁住。详情

    insert into select引起的问题

    解决这个阻塞的方式有两种:一是可以先把查询出来的数据落地,然后在还原到另外一张表。或者将binlog_format改为row

    最好的方式是使用读已提交的模式,并且将binlog_format设置为row

    另外。在mysql中设置隔离级别为读已提交时,binlog_format如果设置为statement插入数据的时候会报错:

    --默认binlog_format=statement会报错
    mysql> create table test3(id int)engine=innodb;
    Query OK, 0 rows affected (0.16 sec)
    
    mysql> insert into test3 values(1);
    ERROR 1598 (HY000): Binary logging not possible. Message: Transaction level 'READ-COMMITTED' in InnoDB is not safe for binlog mode 'STATEMENT'
    --设置binlog_format=row无问题
    mysql> set session binlog_format=row;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> insert into test3 values(1);
    Query OK, 1 row affected (0.06 sec)
    
    --设置binlog_format=mixed无问题
    mysql> set session binlog_format=mixed;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> insert into test3 values(1);
    Query OK, 1 row affected (0.07 sec)
    报错的原因是因为read committed可能会导致不可重复读,也就是说可以读取到后面进入并提交的数据,如果基于STATEMENT格式的话,会导致主从数据不一样,因为STATEMENT是基于SQL语句的复制模式。
    

    使用读已提交的时候,binlog_format只能设置为row或者mixed。建议使用row

    总结

    1、mysql为什么选择使用可重复读来作为默认的隔离级别?

    原因是在mysql5.0以前binlog只支持statement这种格式,这种格式在读已提交(read commited)这个隔离级别下主从复制是有bug的,因此mysql将可重复读(repeatable read)作为默认的隔离级别!

    2、可重复读会带来那些问题?

    ​ 1)、隔离级别越高,并发能力越低。

    ​ 2)、在可重复读级别下,如果使用间隙锁的方式,那么导致死锁的几率比读已提交大的多。

    ​ 3)、在可重复读级别下,条件列未命中索引会锁表!而在读已提交隔离级别下只锁行

    3、是否可以将mysql的默认隔离级别改为读已提交(read commited)

    这个是可以的,在修改隔离级别为读已提交的同时,将binlog_format修改为row,可以提高并发能力。

  • 相关阅读:
    webpack 打包性能分析工具
    npm 使用
    npm 构建时,次要版本变化引起的问题
    AtomicStampedReference、AtomicMarkableReference 区别
    vue-cli 中的静态资源处理
    vue-cli 构建项目中 config/index.js 文件解读
    webpack的3个路径配置项: assetsRoot、assetsSubDirectory、assetsPublicPath
    Vue2 dist 目录下各个文件的区别
    DllPlugin、DllReferencePlugin 可以提取的第三方库列表
    JUC集合之 CopyOnWriteArrayList
  • 原文地址:https://www.cnblogs.com/zhangfx01/p/14872430.html
Copyright © 2011-2022 走看看