精华内容
下载资源
问答
  • 1 背景在InnoDB中,当多线程需要访问共享数据结构时,InnoDB使用互斥锁(mutex)和读写锁(rwlock)来同步这些并发操作。InnoDB的读写锁实现并不是对pthread ...本文分析了InnoDB读写锁的具体实现,所有分析基于MyS...

    1 背景

    在InnoDB中,当多线程需要访问共享数据结构时,InnoDB使用互斥锁(mutex)和读写锁(rwlock)来同步这些并发操作。InnoDB的读写锁实现并不是对pthread rwlock的直接封装,而是基于原子操作,自旋锁和条件变量进行实现,大大减少了进入内核态进行同步操作的概率,提高了性能,和在现代多核处理器架构下的可扩展性。

    本文分析了InnoDB读写锁的具体实现,所有分析基于MySQL 8.0.18代码。

    2 锁模式

    InnoDB的读写锁有三种基本模式:S(Shared),X(Excluded)和SX(Shared Excluded)。它们的锁兼容性关系如下表所示:

    S

    SX

    X

    S

    兼容

    兼容

    冲突

    SX

    兼容

    冲突

    冲突

    X

    冲突

    冲突

    冲突

    2.1 SX锁的含义

    S和X模式比较好理解是经典的读写锁两种模式。SX模式是对X模式的一种优化,它与读操作的S模式兼容,但是多个SX锁之间是冲突的。

    典型的应用场景是对dict_index_t.lock冲突的优化。在过去,当插入操作会造成B+ Tree Node分裂时,使用悲观模式插入记录。此时,需要在dict_index_t.lock上加X锁,要修改的所有相关Leaf Page上加X锁,完成后开始对Branch Node进行修改,而Branch Node上不需要加任何锁。当以这种模式插入时,将阻塞所有在该 B+ Tree上的搜索操作,因为搜索操作的第一步就是在dict_index_t.lock上加S锁。

    通过SX锁可以优化该场景:悲观模式的插入操作在dict_index_t.lock上加SX锁,同时在需要修改的Branch Node上加X锁,此时因为在dict_index_t.lock上加的是SX锁,就不会阻塞所有在B+ Tree上的搜索操作,把阻塞范围缩小到访问同一个Branch Node的插入和搜索操作之间。

    3 锁状态的维护

    InnoDB rw_lock_t 仅使用一个64 bit整型的lock_word就维护了绝大部分的锁状态,其取值含义如下图所示。

    2020-04-26-wanghu-lock_word.jpg

    4 加解锁的实现

    4.1 锁的重入

    InnoDB的每个读写锁都可以设置是否开启可重入模式(Recursive)。当使用可重入模式时,同一个线程可以多次获得锁,只需保证加锁总次数与解锁总次数相等即可。更强大的是,可重入模式下,同一个线程可以同时获得一个读写锁的X锁和SX锁,也可以同时获得一个读写锁的SX锁和S锁,但是不能同时获得X锁和S锁。

    4.2 加锁逻辑的实现

    InnoDB读写锁实现的核心思想是避免使用pthread rwlock,而尽量使用原子操作+自旋的模式来实现加解锁,这样可以在低冲突的场景下,以尽量小的开销实现加解锁。遇到实在是冲突高的读写锁,再使用InnoDB条件变量实现等待。

    下面以X锁的加锁逻辑来举例说明InnoDB读写锁加锁的实现。SX锁和S锁的加锁逻辑比较类似,对应代码可以参照阅读。X锁加锁的最终入口函数是rw_lock_x_lock_func,位于sync/sync0rw.cc中。函数签名如下:

    void rw_lock_x_lock_func(rw_lock_t *lock, ulint pass, const char *file_name, ulint line);

    其中pass参数的含义是如果当前锁上已经有X锁或者是SX锁,是否进入可重入模式。加锁逻辑可以用下面的流程图总结。

    2020-04-26-wanghu-x-lock.jpg

    4.3 解锁逻辑的实现

    下面以X锁的解锁逻辑来举例说明InnoDB读写锁解锁的实现。SX锁和S锁的解锁逻辑比较类似,对应代码可以参照阅读。X锁解锁的最终入口函数是rw_lock_x_unlock_func,位于include/sync0rw.ic中。解锁逻辑可以用下面的流程图总结。

    2020-04-26-wanghu-x-unlock.jpg

    5 X锁所有权的转移

    InnoDB读写锁上的X锁所有权是可以在不同线程间转移的,主要用于支持Change Buffer的场景。Change Buffer是一棵全局共享的B+树,存储在系统表空间中。在读取二级索引Page的时候Change Buffer需要与二级索引Page进行合并,这时如果所有IO线程都在读取二级索引Page,将没有IO线程读取Change Buffer Page,因此Change Buffer Page的读取被放到单独的IO线程。而读取二级索引Page的时候,已经对Page加上了X锁,当在异步IO线程需要把Change Buffer合并到二级索引的Page的时候,必须在不解锁的情况下让异步线程获得Page的X锁,这就是X锁所有权转移需要实现的功能。

    实现函数是rw_lock_x_lock_move_ownership,实现的逻辑也非常简单,使用CAS原子操作把读写锁的write_thread字段设置为当前线程。

    os_thread_id_t curr_thread = os_thread_get_curr_id();

    ...

    local_thread = lock->writer_thread;

    os_compare_and_swap_thread_id(&lock->writer_thread, local_thread, curr_thread);

    ...

    6 总结

    本文分析整理了InnoDB读写锁的实现,InnoDB读写锁在兼顾性能和多核可扩展性的同时,提供了强大的功能,包括在典型的读锁和写锁的基础上增加了SX锁来优化锁冲突,可重入的锁语义以及X锁所有权的转移等等,是非常有参考意义的高性能并发同步基础代码。

    展开全文
  • Mysql存储引擎Innodb读写锁、行级锁

    千次阅读 2019-06-29 12:45:58
    读写锁 Mysql存储引擎Innodb在处理并发读或者写的时候,通过两种类型的锁来解决并发问题,这两种锁通常称为共享锁和排他锁,也叫读锁和写锁。 读锁是共享的,即多个客户端可以同时读取同一资源。 写锁是排他的,...

    读写锁

    Mysql存储引擎Innodb在处理并发读或者写的时候,通过两种类型的锁来解决并发问题,这两种锁通常称为共享锁和排他锁,也叫读锁和写锁。

    读锁是共享的,即多个客户端可以同时读取同一资源。

    写锁是排他的,也就是说写锁会阻塞其他的写锁和读锁。

    举个例子:

    客户端A读取操作不需要等待客户端B读取完成并释放锁。但客户端A进行写操作的时候,会阻塞其他客户端的读和写操作,直到客户端A写操作完成并释放锁,其他客户端才可以进行读写。

    上面说了这么多,但实际上当客户端A更新某行记录的同时,客户端B任然可读取到数据,不会被阻塞,这是为什么呢?

    先来了解两个东西,Redo Log(重做日志)和Undo Log(回滚日志)

    redo log通常是物理日志,记录的是数据页的物理修改,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。
    undo用来回滚行记录到某个版本。undo log一般是逻辑日志,根据每行记录进行记录。

    如果看不懂,就这样理解:当发生写操作时,Innodb会把旧数据存储到Undo Log(回滚日志)中,新数据写到Redo Log(重做日志)中。

    当客户端A更新某行记录的同时,客户端B会被写锁阻塞,这时,客户端B会去Undo Log中读取旧数据。

    下面来实践下。

    CREATE TABLE `test001` (
      `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
      `first_name` char(50) DEFAULT NULL,
      `last_name` char(50) DEFAULT NULL,
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    
    INSERT INTO `test001`(`id`, `first_name`, `last_name`) VALUES (1, 'fn001', 'ln001');
    INSERT INTO `test001`(`id`, `first_name`, `last_name`) VALUES (2, 'fn002', 'ln002');
    INSERT INTO `test001`(`id`, `first_name`, `last_name`) VALUES (3, 'fn003', 'ln003');
    

    在一个终端执行

    BEGIN;
    UPDATE test001 SET first_name='update_name' WHERE last_name='ln001';

    另一个终端执行

    SELECT * FROM `test001` WHERE last_name='ln001';

    查询依旧可以获取数据

    ps:试验完成后,记得commit或者rollback结束事物。

    行级锁

    Innodb使用行级锁,然而这并不意味着Innodb只会锁住被修改的行。请看下面的操作。

    在一个终端连接中执行

    BEGIN;
    UPDATE test001 SET first_name='update_name' WHERE last_name='ln001';

    在另一个终端连接中执行

    BEGIN;
    UPDATE test001 SET first_name='update_name2222' WHERE last_name='ln002';

     

    第二条SQL被阻塞超时,即更新操作不仅仅锁住了 last_name='ln001'的行,还锁住了其他行。在这个例子中,实际上锁住了整张表。

    回滚上面的事物再进行下面的操作。运行  rollback

    那为什么说Innodb实现了行级锁呢?我们来给last_name行加一个索引。

    再次执行刚才的语句

     

    可以看到两条更新语句互不干扰,由此可知Innodb通过索引,筛选出了last_name='ln001'的数据行,然后仅对last_name='ln001'的数据行加了锁,其他数据行不会被锁住,所以此时可以更新last_name='ln002'的数据行。

    没加索引前,Innodb就没法通过索引筛选出指定的数据行,只能锁住整张表了。

     

    展开全文
  • (本文为了方便,英文关键词都都采用小写方式,相关知识点会简单介绍,争取做到可以独立阅读)文章开始我会先介绍本文需要的知识点如下:innodb的聚簇索引(聚集索引)和非聚簇索引(二级索引、非聚集索引)的知识...

    (本文为了方便,英文关键词都都采用小写方式,相关知识点会简单介绍,争取做到可以独立阅读)

    文章开始我会先介绍本文需要的知识点如下:

     

    • innodb的主键索引(一级索引)和二级索引(辅助索引)的知识
    • innodb的隔离级别isolation level
    • MVCC(Multi-Version Concurrent Control)多版本并发控制
    • 数据的脏读、幻读(如果有时间会详细讲一下脏读如果没时间,网上讲这个地方的也很多)
    • 简单的sql知识(能读懂sql语句)
    我们先看一个mysql表和几条语句,方便后面使用
    表名称: my_table  
    搜索引擎:innodb
    表结构:
     
    1. select * from my_table where id = 1;
    2. select * from my_table where id = 1 lock in share mode;
    3. select * from my_table where id = 1 for update;
    4. update my_table set address = 'tianjin' where id = 1;
    先说 隔离级别,mysql 隔离级别分为四种:
    未提交读( read uncommitted )、提交读(read committed)、重复读(repeatable read)、序列化(serializable
    其中mysql默认的隔离级别重复读(repeatable read),以下简称为rr,本文也只介绍这种模式

    问题1:读有几种模式、加锁有几种方式

    读的模式分为两种:

    • 快照读(snapshot read)
    • 当前读(current read)

    在聊读模式之前,我们需要先来了解一下MVCC:

    MVCC是为了实现数据库的并发控制而设计的一种协议。与其相对的是LBCC 即基于锁的并发控制(Lock-Based Concurrent Control)。要实现数据库的并发访问控制,最简单的做法就是加锁访问,即读的时候不能写( 这个读为当前读,后面介绍。允许多个线程同时对想读的内容加锁,即共享锁或叫S锁),写的时候不能读(只能有一个线程对同一内容进行写操作,即排它锁,X锁)。这样的加锁访问,其实并不算是真正的并发,或者说它只能实现并发的读,既读写串行化,这样就大大降低了数据库的读写性能。
    LBCC是四种隔离级别中级别最高的Serialize隔离级别。MVCC对比LBCC它的最大好处便是, 读不加锁,读写不冲突,在需要加锁的时候,尽可能的少锁定行(锁策略, 后面有讲 )。

    快照读(Snapshot read)和当前读(current read)释义:

    • 快照读,读取的是记录的可见版本(可能是历史版本,即最新的数据可能正在被当前执行的事务并发修改),不会对返回的记录加锁,如上面的sql语句1

    • 当前读,读取的是记录的最新版本,并且会对返回的记录加锁,保证其他事务不会并发修改这条记录。如上面的sql语句2,3,4。不同的是2加的是s锁,3、4加的是x锁,insert加的也是x锁。

    注:MVCC只在RC和RR两个隔离级别下工作,其他两个隔离级别都和MVCC不兼容

    加锁的方式:

    mysql进行并发控制有两种锁:

    • 共享锁(s锁):也称为读锁,是因为一般是读的过程中加的锁,一个事务加s锁的时候,另一个事务还可以获得s锁,例如语句2
    • 排他锁(x锁):也称为写锁,一个事务加x锁的时候,其他事务拿不到锁,只能等待。例如语句3和语句4
    先看一个sql语句
    update my_table set name ='zhang' where id = 1;
    假设id为主键:此条sql执行的时候会给此行数据加x锁,如下图

     
    mysql的innodb默认的隔离模式为 RR模式,既可重复读, Innodb的RR隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),因此不存在幻读现象。但是 标准的RR只能保证在同一事务中多次读取同样记录的结果是一致的,而无法解决幻读问题。Innodb的幻读解决是依靠MVCC的锁策略实现机制做到的。

    主键索引(一级索引)和二级索引

    innodb中在主键上存在聚簇索引类型的一级索引,其他的索引均聚簇索引类型的二级索引,这里做一下简单介绍
     
    一级索引:在innodb存储引擎中,主键的存在至关重要,及时你不为表设置主键,存储引擎也会隐式的定义一个主键,只是对用户来说透明。之所以说他重要,是因为聚簇索引的存储是和数据存储在一起的,而聚簇索引的数据就是数据存储的顺序。如果需要查找的数据是连续的,那么按照聚簇索引查找到的数据位置也是连续的,只需要按顺序读取就可以。对于聚集索引,叶子结点即存储了真实的数据行,不再有另外单独的数据页(这里和后面的二级索引有区别,二级索引的叶子节点存储的是主键,需要再进行回聚集索引上(简称回表)查询真实的数据。 。 在一张表上最多只能创建一个聚集索引,因为真实数据的物理顺序只能有一种。
    二级索引:表数据存储顺序与索引顺序无关。对于二级索引,其也是聚集索引,但是叶结点不包含真实行数据,只包含索引字段值及主键,其行数量与数据表行数据量一致。
     

    mvcc的锁策略

    update my_table set name ='zhang' where id = 1;
    看上面的sql语句,或者看之前的几条sql,这个语句执行的时候会给这条记录加x锁,这时候如果其他事务中的语句也在进行锁的操作(既更新、插入或者删除,以及语句2当前读操作加的s锁)就会造成锁争用(innodb出现锁争用的时候处理方式为回滚超时获取不到锁的事务)这种操作当然是惨痛的。
     
    我们在上面说了mvcc的锁策略是尽量减少锁定的行。而且还要解决幻读的问题,所以有了一系列的锁策略(先看总结,后面有实例)。
     

    行锁

    对于查询条件为主键和唯一索引的语句:是行级锁,只锁定满足条件的行,record lock。

    间隙锁 (gaplock)

    对于查询条件为非唯一索引:是范围锁定,锁定范围为索引上按照条件需要扫描的范围

    表锁

    对于查询条件没有用到索引的语句:直接锁定全表。

     

    实例讲解

    我们先按照上面四条语句两条并发时的相互影响的情况来
     
    情况1:id为主键
    1. select * from my_table where id = 1;
    2. select * from my_table where id = 1 lock in share mode;
    我们上面说过,语句1为快照读,对其他的读或者写没有影响。所以这两条语句并行时,1读快照,2为语句加s锁。
     
    select * from my_table where id = 1 lock in share mode;
    情况2:id为主键
    2. select * from my_table where id = 1 lock in share mode;
    3. select * from my_table where id = 1 for update;
    其中语句2加s锁,3加x锁(在数据被加s锁的时候,其他的给这条想要读取这条记录也需要给这条记录加s锁,这就是为什么s锁是共享锁。此时是不允许再给这条记录加x锁的)两种锁是不能同时存在在一条记录上的。所以两条语句谁先上锁谁先执行,另一个等待。
    情况3:id为主键
    3. select * from my_table where id = 1 for update;
    4. update my_table set address = 'tianjin' where id = 1;
    这种情况下两条语句都需要给数据加x锁,所以显然不能并发执行。
     

    下面我们来讨论一下id不为主键的情况

    id若不为主键,则不能使用主键索引,而在innodb中有一下几种情况
    • 二级唯一索引
    • 二级不唯一索引
    • 没有索引
    由于只要不是快照读则一定会加锁,我们已经了解了锁的形式,则不难明白不论是先加x锁还是s锁哪一种,都一定不能再加另一种锁,所以我们下面只分析加锁的方式
     
    情况4:假设id为二级唯一索引(unique)
     
    4. update my_table set address = 'tianjin' where id = 1;
    这里很明显需要加x锁,但是这里的加锁和id为主键(索引为主键索引)的情况加锁不完全一样,会稍微复杂一点
     
    这个时候我们需要对索引知识有一定的了解,上面说过二级索引中的叶子节点存储的除了索引信息还有主键,也就是说我们需要先在二级唯一索引中查找到这条记录的主键,然后通过主键去查找到数据实际的存储位置并给这条数据加锁。注意,这里的加锁应该是加在了索引上和数据本身上(或者说是聚簇索引上也可以,因为两者是存储在一个结构中的)而不只是二级唯一索引上。
     
    情况5:age为二级非唯一索引,id为主键
     
    5. update my_table set address = 'tianjin' where age = 25;
    此种情况比前一种情况更特殊,因为情况3和4都只能找到一条记录,只需要对这条记录加锁,则不会发生结果集被修改的情况。但是如果age为二级非唯一索引,我们看到如下表格中有两条记录age=25
     
    如果我们在update的过程中,有一个用户插入了一条age也为25的数据,那么就是发生一种现象,你明明更新了所有的age=25的数据,但是执行完了却有一条数据没有更新的幻觉,这就是幻读(可以自行查找资料,避免本文过长)。这个时候显然只给查找出的数据加锁是解决不了这个问题的。所以就有了gap锁(间隙锁字面上可能更好理解)这里需要画图大家理解一下:
    如图这里在age为25的有两个 ,id分别为1和3。我们在修改执行上面语句的时候,如果没有gap锁,则可能发生一种情况:另一个事务执行如下语句
    update my_table set age=25 where id=2;
    则发生幻读现象。gap锁可以防止在语句或者事务执行过程中有满足条件的记录插入进来造成幻读。所以说在此种情况下,除了给满足条件的二级索引和数据(或聚簇索引)加x锁之外还要给相关的间隙加锁。可以理解为这个加gap锁,不只是锁记录,还要锁边界。是在二级索引的范围内防止新的索引项加入,因为二级索引本身也是有序的。
     
    情况6:age上无索引,id为主键
     
    5. update my_table set address = 'tianjin' where age = 25;
    这种情况下,所有记录都被加上了X锁,每条记录间的间隙(GAP)也同时被加上了GAP锁,其实就是锁表了。
     
     
     
     
     
     

     

    展开全文
  • MySql InnoDB锁类型

    千次阅读 2018-10-20 10:56:14
    MySql InnoDB锁类型 从类型上来分类,InnoDB存储引擎实现了两种标准的 共享(S-Lock):允许事务读一行数据 排它(X-Lock):允许事务删除或者更新一行数据 如果一个事务获取了S,那么其他事务也可以...

    MySql InnoDB锁类型

    从类型上来分类,InnoDB存储引擎实现了两种标准的锁

    • 共享锁(S-Lock):允许事务读一行数据

    • 排它锁(X-Lock):允许事务删除或者更新一行数据

    如果一个事务获取了S锁,那么其他事务也可以立即获得S锁,但如果要对记录加X锁,必须等待该记录的所有锁(S或X)释放后才能加成功。如下表所示为S和X锁的兼容性

    XS
    X不兼容不兼容
    S不兼容兼容

    MySql支持多粒度的锁定操作,这就允许事务在表级和行级上的锁同时存在。为了支持在不同粒度上的加锁操作,InnoDB支持了额外的一种锁,意向锁(Intention Lock)。意向锁是将锁定的对象分为多个层级,意味着事务希望在更细粒度上进行加锁。

    下边是一个网友介绍的意向锁的作用:

    innodb的意向锁有什么作用?
    mysql官网上对于意向锁的解释中有这么一句话
    “The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.”
    意思是说加意向锁的目的是为了表明某个事务正在锁定一行或者将要锁定一行。
    那么,意向锁的作用就是“表明”加锁的意图,可是为什么要表明这个 意图呢?
    如果仅仅锁定一行仅仅需要加一个锁,那么就直接加锁就好了,这里要表明加锁意图的原因是因为要锁定一行不仅仅是要加一个锁,而是要做一系列操作吗?
    ​
    作者:尹发条地精
    ​
    我最近也在看这个,我说一下我的理解
    ①在mysql中有表锁,LOCK TABLE my_tabl_name READ;  用读锁锁表,会阻塞其他事务修改表数据。LOCK TABLE my_table_name WRITe; 用写锁锁表,会阻塞其他事务读和写。
    ②Innodb引擎又支持行锁,行锁分为共享锁,一个事务对一行的共享只读锁。排它锁,一个事务对一行的排他读写锁。
    ③这两中类型的锁共存的问题考虑这个例子:
    事务A锁住了表中的一行,让这一行只能读,不能写。之后,事务B申请整个表的写锁。如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。
    数据库需要避免这种冲突,就是说要让B的申请被阻塞,直到A释放了行锁。
    ​
    数据库要怎么判断这个冲突呢?
    step1:判断表是否已被其他事务用表锁锁表
    step2:判断表中的每一行是否已被行锁锁住。
    注意step2,这样的判断方法效率实在不高,因为需要遍历整个表。
    于是就有了意向锁。在意向锁存在的情况下,事务A必须先申请表的意向共享锁,成功后再申请一行的行锁。在意向锁存在的情况下,
    上面的判断可以改成
    step1:不变
    step2:发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此,事务B申请表的写锁会被阻塞。
    ​
    注意:申请意向锁的动作是数据库完成的,就是说,事务A申请一行的行锁的时候,数据库会自动先开始申请表的意向锁,不需要我们程序员使用代码来申请。
    ​
    总结:为了实现多粒度锁机制(白话:为了表锁和行锁都能用)

    意向锁分类:

    • 意向共享锁(IS):事务想要获取一张表中某几行的共享锁

    • 意向排它锁(IX):事务想要获取一张表中的某几行的排它锁

    意向锁的兼容性:

    ISIXSX
    IS兼容兼容兼容不兼容
    IX兼容兼容不兼容不兼容
    S兼容不兼容兼容不兼容
    X不兼容不兼容不兼容不兼容

    一致性非锁定读

    一致性非锁定读是指InnoDB存储引擎通过多版本控制的方式来读取当前执行时间数据库中行的数据。如果读取的行正在执行DELETE或者UPDATE操作,这时读取操作并不会因此等待该行的X锁释放,而是读取一个快照数据。

    当然在不同的事务隔离级别下,非一致锁定读读取的快照顺序也是不一样的。

    • READ COMMITTED:读取的快照数据永远是最新的一份快照数据

    • READ REPEATABLE:读取的快照是当前事务开始时的快照数据

    一致性锁定读:

    在数据库隔离级别为RR级别下,select操作使用了一致性非锁定读。在某些情况下,用户需要显示地对读操作进行加锁以保证数据的一致性逻辑。一般来说会使用以下两种方式:

    • select … for update

    • select … lock in share model

    其中select … for update会给选择的行加一个X锁,select … lock in share model会加一个S锁。并且这两种用法必须在一个事务中。当事务提交后锁也就释放了。

    锁的算法

    InnoDB有3中锁的算法

    • Record Lock:在单行上加锁

    • Gap Lock:间隙锁,锁定一个范围,但不包含当前记录

    • Next-Key Lock:Gap Lock+Record Lock,锁定一个范围,并包含当前记录。

    对于Next-Key Lock,其解决的是数据库的幻想问题,对于一个索引包含10,11,13,20的数据,那么Next-Key Lock可以锁定的区间为:

    (-无穷,10],(10,11],(11, 13],(13, 20],(20, +无穷)

    当查询的索引含有唯一属性时,InnoDB会对Next-Key Lock进行优化,将其降级为Record Key,只是锁住索引本身,并不会锁范围。

     

    但对于非聚集索引,则会在非聚集索引上加Next-Key Lock

    对于select * from z where b = 3,则会在(1, 3]上加锁,同时会在该记录后边加一个gap lock,即(3, 6),如果执行以下两个语句都会造成堵塞。

    由此可见gap 锁是为了解决数据库的幻读现象。

    对于唯一键的锁定,Next-Key Lock会降级为Record Lock的前提仅限于查询所有的唯一索引列。若唯一索引有多个列组成,而查询的是多个索引中的一个,那么查询仍然是range查询,而不是point查询,仍然使用Next-Key Lock进行锁定。

    幻读问题

    上图所示为幻读现象,A在同一个事务中读取的数据不一致,gap lock则解决了这个问题。在RR隔离级别下,gap锁会为(2, 无穷)加X锁,这样会话B插入4时就会阻塞。

    锁存在的问题

    脏读

    在READ uncommitted隔离级别下,事务B可以读取A未提交的数据,如果A rollback,则B两次读取的数据将不一致。而在READ Committed隔离级别下,事务读取的是记录最新提交的快照,因此可以避免脏读现象

    幻读

    在READ Committed隔离级别下,当使用范围查询时会出现两次读取的数据不一致,因此造成幻读现象,在READ REPEATABLE级别下,由于Gap锁的作用可以避免幻读。

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 40,893
精华内容 16,357
关键字:

innodb的读写锁