zoukankan      html  css  js  c++  java
  • MySQL深入研究--学习总结(5)

    前言

    接上文,继续学习后续章节。细心的同学已经发现,我整理的并不一定是作者讲的内容,更多是结合自己的理解,加以阐述,所以建议结合原文一起理解。

    第20章《幻读是什么,幻读有什么问题?》

    先看下幻读的定义:

    在一个事务中,两次执行同一个查询SQL,后一次执行结果比前一次执行结果数量变多了,称之为幻读。

    在隔离级别中的定义,我们知道RR级别是无法避免幻读的?但是在innoDB中是如果做到避免幻读问题呢?其实innoDB在RR级别下解决幻读问题也并不完美。

    现有一张表t,有三个字段 id主键,普通索引C,不带索引d, 目前有数据id=1,d=5,c=5;id=4,d=5,c=5;

    事务A 事务B 事务C
    begin;
    select * from t where d =5 for update;Q1
    update t set d =5 where id =4;
    select * from t where d =5 for update;Q2
    insert into t values (5,5,5)
    select * from t where d =5 for update;Q3
    commit;

    首先我们知道innoDB在RR级别下,select语句是快照读,不存在幻读问题,快照读的实现方式我们之前阐述过,通过MCVV多版本并发控制解决的,本质就是通过undo log实现的,如果还需要更详细的研究,可以参考《一文讲透MVCC原理》

    Q2读到事务B的更新预警,属于当前读看到的,不属于幻读.幻读指的是新插入的行。

    首先我们看下,幻读不解决可能会出现哪些问题?

    1、首先如果只加行锁,执行Q1时,我们希望的是所有d=5的数据都锁定,但d=5的是id=1的数据,只会对id=1加上行锁.当事务B执行时,是对id=4进行更新操作,所以可以操作.同样的事务C也可以执行,那么就违背了,Q1的语义.

    2、数据一致性问题:如果事务A在Q1时候又执行了一个按d=5条件更新语句,那么在所有事务都提交后,落入到binlog中的顺序是,事务B更新一个语句把id=4的d改成了5,事务C插入了一个id=5,d=5的数据,事务A执行了一个把d=5的数据更新。此时如果按binlog的执行顺序,则会把事务B事务C的逻辑全部改掉,造成了数据不一致的问题。

    所以innoDB为了解决此类幻读问题,就引入了间隙锁(Gap lock);

    顾名思义就是在行与行之间也加上锁.

    比如上面例子中,有id=1和id=4两个数据,那么我不仅在行上加锁,我再(- ∞ ,1](1,4],(4, + supremum】上也加上锁,那么当你再插入数据的时候,就无缝可入了就会被阻塞住。

    但需要注意的是,在行锁中,读写锁,写写锁互相冲突,当一个事务加间隙锁,另外一个事务也可以对同样的范围加间隙锁。

    注意:

    当我们执行更新加间隙锁时,如果where条件不是索引,那么就会对所有行加行锁和行之间数值范围加间隙锁。

    如果执行的where条件是等值并且是非唯一索引列,比如id=5,那么会对id=5加行锁,以及(id=5上一个id,id=5]和(id=5,id=5的下一个id值]加间隙锁。如果是唯一索引,那么就只加行锁。

    第21章《为什么只改一行代码,锁这么多》

    这章节,老师总结的加锁规则,非常受用:两个原则,两个优化,一个BUG

    两个原则:

    1、加锁的基本单位是以next-key lock。就是左开右闭。

    2、查找过程中访问的到对象才会加锁。

    两个优化:

    1、在索引上等值查询条件,如果是唯一索引,会优化成行锁。

    2、在索引上等值查询条件,会向右遍历找到第一个不符合条件的为止,并会优化成间隙锁。

    一个bug:

    在唯一索引上的范围查询,会访问到不满足条件为止。

    带着这些规则,下面看八个案例,以表T为背景,来实践下。

    CREATE TABLE`t`(
    `id`int(11)NOT NULL,
    `c`int(11)DEFAULT NULL,
    `d`int(11)DEFAULT NULL,
    PRIMARY KEY(`id`),
    KEY`c`(`c`)
    ) ENGINE=InnoDB;
    
    insert intot values(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);
    

    案例一:等值查询的情况

    update t set d=d+1 where id =7;
    

    根据主键id进行更新时,由于id=7不存在。根据规则1加锁单位next-key lock :(5,10] 根据优化2: 退化成间隙锁(5,10);

    此时分别执行两个SQL:

    insert into t values(8,8,8) --由于间隙锁,会block
    update t set d=d+1 where id =10; --不在间隙锁范围内,执行成功
    

    经过实验,证实如此。

    案例二:非唯一索引等值查询

    begin;
    select id from t where c=5 lock in share mode;
    

    根据优化2 可知会在索引C间隙锁 (5,10);

    但需要注意的是:根据规则2 只有访问到的对象才会加锁,由于使用了覆盖索引,所以不会再主键id上再加锁。

    update t set d=d+1 where id =5; --执行不受阻
    insert into t values(7,7,7); --blocked
    

    案例三:主键索引范围查询

    select * from t where id =10 for update;
    select * from t where id>=10 and id <11;
    

    首先从结果上看,这两个SQL执行结果是一样的,但是按照上面总结的加锁规则,加锁过程是不一样的。

    SQL1 唯一索引等值查询,则加的是id=10的行锁。

    SQL2 首先会查询到id=10这行,加锁单位next-key lock,(5,10],根据优化1,退化成行锁,只加id=10这一行,

    再查询id>10,向右找到第一个不满足条件的行id=15,加next-key lock (10,15]

    案例四:非唯一索引范围锁

    select * from t where c>= 10 and c<11 for update;
    

    首先找到c=10这一行,加next-key (5,10],再根据优化2,向右查询到第一个不满足条件的值,退还成间隙锁(10,15]

    案例五:唯一索引范围锁bug

    select * from t where id>10 and id<=15 for update;
    

    正常id是唯一索引,找到id=15这时 ,应该就停止扫描了,但是innoDB会依然扫描到第一个不满足条件的id=20为止。

    所以就会加(10,15],(15,20],导致id=20这样也会锁上,这是没有必要的。这个bug已被官方证实,但还未修复。

    案例六:未唯一索引存在多个相同值的情况

    --先插入一条c=10的值,这样表中存在2个c=10的行
    insert into t values(30,10,30);
    

    当我们在c=10加锁时,加锁的范围是next-key lock (5,10] next-key lock (10,10]和间隙锁 (10,15)

    案例七:limit 语句加锁

    delete from t where c =10 limit2;
    

    与案例7的加锁范围不同的时,不会再加间隙锁(10,15);

    因为当扫描到c=10,id=30这一行时,已经满足limit2的条件,不会再往后扫描,也就不会再加间隙锁(10,15),从而缩小了锁的范围。

    所以在删除数据的时候,我们尽量加limit ,即可以控制删除数量,又可以缩小锁的范围。

    案例八:一个说死锁的例子

    A执行第一句SQL时,按加锁规则,会加next-key lock (5,10]和间隙锁(10,15)。

    B执行时,也会加next-key lock(5,10],会被阻塞.

    A再次执行插入数据(8,8,8)时,也会被阻塞,从而导致死锁。

    这是为什么了?B 不是没有加锁成功吗?但是我们要注意的时,加next-key lock 本质就是加间隙锁再加行锁。由于间隙锁加之间不是互斥的,所以B加(5,10)的间隙锁时成功了,加c=10的行锁失败。

    所以当A再次插入数据时,已被间隙锁阻塞。

    第23章《MySQL怎么保证数据不丢失的?》

    binlog写入机制

    事务执行时,先把日志写到binlog cache,事务提交后再把binlog cache 写入binlog文件。

    binlog_cache_size控制了每个线程的binlog cache的内存的大小。如果超过了这个参数就临时存到磁盘中。

    但注意的是,binlog cache并不一定是直接落入磁盘的,是先写入binlog files,再刷入磁盘。

    这个可以通过参数sync_binlog控制:

    设置0,表示每次只写入binlog files,不刷磁盘

    设置1,表示每次事务提交,也会刷磁盘

    设置n,表示每次都写入binlog files,但是等累计了n个事务才刷磁盘

    常见设置为"100-1000",但同时也会有丢失的风险.

    redo log写入机制

    事务执行时,先把日志写入 redo log buffer,事务提交时,写入文件系统page cache 或者刷入磁盘.也是通过参数来设置:innoDB_flush_log_at_trx_commit

    设置0,表示每次事务提交时,依然保留在redo log buffer

    设置1,表示每次事务提交直接刷入磁盘

    设置2,表示每次事务提交都只写到page cache.

    那是不是0,2状态是不是就永不刷到磁盘了,那不是会有丢失的风险吗?

    其实不是的,innoDB有一个后台线程,每隔1秒,会把redo log buffer中的日志,写到page cache,然后再调用fsync刷到磁盘.

    由于这个后台线程的存在,所以也会把还未提交事务的redo log 也持久化到磁盘.

    如果你的如果你的MySQLMySQL现在出现了性能瓶颈,而且瓶颈在现在出现了性能瓶颈,而且瓶颈在IO上,可以通过哪些方法来提升性能呢?上,可以通过哪些方法来提升性能呢?
    针对这个问题,可以考虑以下三种方法:
    1.设置binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count参数,减少binlog的写盘次数。这个方法是基于“额外的故意等待”来实现的,因此可能会增加语句的响应时间,但没有丢失数据的风险。
    2.将sync_binlog设置为大于1的值(比较常见是100~1000)。这样做的风险是,主机掉电时会丢binlog日志。
    3.将innodb_flush_log_at_trx_commit设置为2。这样做的风险是,主机掉电的时候会丢数据。

  • 相关阅读:
    代码高亮测试
    自定义Edit控件控制输入范围
    多字节字符与界面 manifest
    实现类成员函数回调
    [VIM插件]fedora22编译vim7.4对perl组件支持的问题
    火车头Ecshop2.7文章采集发布模块
    js 创建对象
    js 属性类型
    JS函数的属性
    JS 函数中返回另一个函数
  • 原文地址:https://www.cnblogs.com/whgk/p/14541700.html
Copyright © 2011-2022 走看看