mysql的并发处理机制

来源:互联网 发布:知乎起诉微博营销号 编辑:程序博客网 时间:2024/05/21 10:53
在innodb中,有4种类型的锁:IX、X、IS及S锁,其说明如下:
  
  类型 说明 场景
  
  S 共享锁 针对于RS隔离级别的查询或者添加Lock in share mode的SELECT查询而产生的锁
  
  X 排它锁 针对于update、delete、insert操作而产生的锁
  
  IS 意向共享锁 表级别的锁,在添加S锁之前对表格添加IS锁
  
  IX 意向排他锁 表级别的锁,在添加X锁之前对表格添加IX锁
  
  1.1 锁定兼容情况
  
  四个锁之间的兼容性,需要分成两种情况来讨论,锁粒度小于表级别的锁的兼容情况,表级的锁兼容情况。
  
  锁粒度小于表级别的锁的兼容情况
  
  对于这两行锁的兼容说明如下:
  
  假设有一行数据,添加了行锁S锁,那么这个行数据,可以提供给其他事务进行S锁的申请和添加,但是不支持其他事务对这一行进行X锁的申请和添加。比如,事务A,对 pk100 这一行进行了 查询操作并添加了S锁,那么其他事务仍然可以对这一行数据进行查询,但是不能对这行数据进行 UPDATE 跟 DELETE 操作,会处于锁等待情况,直到该事务A结束并释放S锁;
  
  假设有一行数据,添加了X锁,那么这个行数据,不允许其他事务对这一行数据进行加锁。比如,事务A,对pk100这一行进行了UPDATE操作,那么其他事务在事务A没有结束之前,都无法对这一行数据申请 S锁。
  
  表级的锁兼容情况
  
  对于表级别的锁兼容性如下:
  
  当一个表格持有S表锁时,不需要其他事务对该表格申请X锁跟IX锁,但是允许申请S跟IS锁。比如,事务A对表格tba全表读,加了S表锁,期间支持其他事务对tba全表读(申请S表锁成功)、支持其他事务对tba行数据查询(申请IS表锁成功),但是不支持对表格全表的修改操作(申请X表锁等待)跟不支持对表格行数据修改操作(申请IX表锁等待);
  
  当一个表格持有X表锁时,持有锁期间,不支持其他所有锁的申请;
  
  当一个表格持有IS表锁时,允许申请 S表锁、IS表锁、IX表锁,但是不支持X表锁申请。
  
  比如,事务A对表格tba 查询了 id = 10(id为主键)这一行数据,这个时候,表格tba持有IS表锁,id = 10 这一行持有 S 行锁,期间,支持其他事务对 tba 全表查询(申请表锁S成功)或者 基于索引查找(申请表锁IS成功)
  
  如果需要对行 id = 20 进行数据修改,则会先申请 tba 的表锁 IX(申请成功),然后再申请id=20行锁X (申请成功);如果需要对 id = 10 这一行数据进行修改,则会申请 tba的表锁 IX(表锁申请成功),然后申请 id = 10 的行锁X(申请堵塞,因为 id = 10 正持有S锁);
  
  如果需要对表格进行全表修改,需要申请表锁(X锁),这个时候,IS锁的优势来了,当查看表格是否有其他事务在访问操作时,一看表锁IS就知道有其他事务对表格内部某些数据持有S锁,并且还没有释放,那么这个时候,申请X锁就会处于等待状态,而不需要一行一行去查询每一行数据有没有被其他事务持有锁,可以大规模的减少查询 锁申请情况;
  
  当一个表格持有IX表锁时,支持申请IS、IX表锁,但是不兼容S、X表锁。
  
  比如,事务A对表格 tba 中 id=10 (id为主键)进行进行 数据修改,这个时候,会对表格 tba 先申请一个 IX 表锁(申请成功),然后申请 id =10 的 X 行锁,申请成功,则 事务A 持有 IX 表锁、id=10的X 行锁,此时事务B 查询 id=20的行,申请表锁 IS 成功,申请 id=20的 S 行锁成功;事务C 修改 id=30的行数据,申请表锁 IX 成功,申请 id=30的行锁 X成功;但是,事务D中,对整个表格发起update或者全表SELECT操作,需要申请 X表锁或者S表锁,正常情况下,应该要对表格的每一行数据进行查看,确保每一行数据的行锁情况,但是因为有了意向锁,事务D一看到 tba 持有 了IX锁,则明白,tba 中某些行持有X锁,则会不兼容其他事务对tba 表锁S ,表锁X的申请。
  
  为什么要引入意向表锁?
  
  在没有意向锁的时候,如果事务T 需要给表格 A 添加 一个S 表锁,那么就意味这这个表格内部的每一行数据,都不能有X锁,才能够申请 S 表锁成功,如果表格数据很多,一行行查找非常浪费加锁时间,这个时候,就出现了表格意向锁,当表格内部某些行发生 UPATE DELETE INSERT操作,则会对表格 加上 一个意向 IX 表锁,这样 事务T在申请 表格A的 S 表锁时,只需要检查 表格 A 是否有 IX表锁,如果有,则意味内部有 部分行数据持有X锁,则直接进入等待情况,如果表格没有 IX表锁,则直接申请S表锁成功,这是一个多么节约加锁时间的操作!
  
  1.2 锁的级别
  
  Table Lock
  
  表锁,如果没有where条件、无可用索引或者获取的行记录过多,则会使用 table full scan,添加表锁
  
  Record Lock
  
  记录锁,如果执行计划使用了索引,则会根据索引的查找情况添加行锁
  
  Gap Lock
  
  在RR、RS隔离级别,发生在索引值之间,在连续的两个索引值之间添加锁,加锁后,这两个索引值之间,无法插入新的索引值,不包含行记录
  
  Next-Key Lock
  
  Record Lock 跟Gap Lock的组合,合体成为Next-KEY Lock
  
  表锁、行锁都相对好理解,这里尝试简单说明下 GAP LOCK。
  
  假设当前隔离级别为RR,表格 tbgap( id int auto_increment primary key not null , name varchar(50) , sort int , key ix_sort (sort)) engine=innodb;
  
  表格数据如下:
  
  在索引ix_sort上,一共有7个间隙,分别为(-∞,(1,6)),((1,6),(2,5)),((2,5),(3,2)),((3,2),(5,4)),((5,4),(6,1)),((6,1),(7,3)),((7,3),+∞),而根据实际的隔离级别及锁申请情况,加在这些间隙上的锁,则成为 GAP LOCK 。
  
  1.3 锁与隔离级别(不考虑 lock in shar mode跟for update )
  
  RU,读未提交记录,不加锁读,正常写锁;
  
  RC,快照读,无锁;当前读,加 Record Lock
  
  RR,快照读,无锁;当前读,对读取到的记录加 Record Lock,同时为了确保where条件范围内的数据无变化,会增加Next key lock
  
  RS,读写均为当前读,不支持快照读。包括select 在内,对读取到的记录加 Record Lock,同时为了确保where条件范围内的数据无变化,会增加Next key lock。
  
  回到顶部(go to top)
  
  2 锁的申请与释放过程
  
  看SQL语句的锁情况,需要结合隔离级别、执行计划、表结构等,同一个SQL,不同的隔离级别、表结构、执行计划,其锁情况不一定是一样的!
  
  本次模拟这3个表格,age列分别:无索引、有一般索引、有唯一索引。表结构结束及数据如下:
  
  CREATE TABLE tb_no_index ( id int primary key not null auto_increment, age int not null, name varchar(100) );
  
  CREATE TABLE tb_index ( id int primary key not null auto_increment, age int not null, name varchar(100) KEY ix_age(age) );
  
  CREATE TABLE tb_unique_index ( id int primary key not null auto_increment, age int not null,name varchar(100) UNIQUE KEY ix_age(age) );
  
  INSERT INTO tb_no_index(age) values(2),(9),(21),(4),(7),(25);
  
  INSERT INTO tb_index(age) values(2),(9),(21),(4),(7),(25);
  
  INSERT INTO tb_unique_index(age) values(2),(9),(21),(4),(7),(25);
  
  每个表格IX_age的索引行数就据如下图展示:
  
  age
  
  2
  
  4
  
  7
  
  9
  
  21
  
  25
  
  id
  
  1
  
  4
  
  5
  
  2
  
  3
  
  6
  
  每个表格主键上面的行数就据如下图展示:
  
  id
  
  1
  
  2
  
  3
  
  4
  
  5
  
  6
  
  age
  
  2
  
  9
  
  21
  
  4
  
  7
  
  25
  
  name
  
  null
  
  null
  
  null
  
  null
  
  null
  
  null
  
  2.1 Read Uncommitted
  
  所有事务隔离级别设置: set session transaction isolation level read Uncommitted ;
  
  RU是读未提交,不添加 LOCK IN SHARE MODE 跟 FOR UPDATE 的 SELECT 语句,均为读未提交,不加锁,存在脏读、不可重复读及幻读。
  
  所有UPDATE、DELETE、INSERT获取当前读记录,加锁。
  
  表格     SQL
  
  select * from tbname
  
  where age/id ...
  
  update tbname set name=...
  
  where id = 4
  
  update tbname set name=...
  
  where age = 21
  
  update tbname set name=...
  
  where age between 5 and 15
  
  tb_no_index
  
  读不加锁,读未提交数据
  
  可能有脏读、不可重复读及幻读
  
  当前读,根据主键修改数据
  
  tbname 加意向表锁 IX
  
  id=4 加 行锁 X
  
  表格的age列无索引,所以update过程中,全表加X锁
  
  支持semi-constent-read,如果有其他update语句修改其他行不堵塞,但是不支持 select ... for update
  
  同左
  
  tb_index
  
  表格的age列有索引,update过程中
  
  tb_index 加 表格意向锁 IX
  
  age索引上面,age=21 行添加行锁 X
  
  再在主键上,给id=3 这一行数据,添加行数 X
  
  表格的age列有索引,update过程涉及age=7,9 两行数据
  
  tb_index 加表格意向锁 IX
  
  age索引上面,age=7,age=9 行添加行锁 X
  
  再在主键上,给id=2,id=5 这一行数据,添加行数 X
  
  tb_unique_index
  
  同上
  
  同上
  
  2.2 Read Committed
  
  所有事务隔离级别设置: set session transaction isolation level read committed ;
  
  RC是读已提交,不添加 LOCK IN SHARE MODE 跟 FOR UPDATE 的 SELECT 语句,均为 快照读,不加锁,同个事务内读取同一个版本的数据,可能非最新数据,但是不存在脏读、不可重复读及幻读情况。
  
  所有UPDATE、DELETE、INSERT获取当前读记录,加锁。
  
  下表中,黄绿色 字体 是RC与RU隔离级别不同的地方,仔细阅读分析结果可以知道,在 RU 跟 RC 间,最大的区别在于 SELECT 的查询模式,RU 为 读未提交,而 RC 为快照读。UPATE/DELETE/INSERT的加锁模式类同。
  
  表格
  
  SQL
  
  select * from tbname
  
  where age/id ...
  
  update tbname set name=...
  
  where id = 4
  
  update tbname set name=...
  
  where age = 21
  
  update tbname set name=...
  
  where age between 5 and 15
  
  tb_no_index
  
  快照读,不加锁
  
  读取的数据不一定是最新版本,但是事务内的所有查询读取数据都是同一版本的行数据,不存在脏读、不可重复读及幻读的情况
  
  当前读,根据主键修改数据
  
  tbname 加意向表锁 IX
  
  id=4 加 行锁 X
  
  表格的age列无索引,所以update过程中,全表加X锁
  
  支持semi-constent-read,如果有其他update语句修改其他行不堵塞,但是不支持 select ... for update
  
  同左
  
  tb_index
  
  表格的age列有索引,update过程中
  
  tb_index 加 表格意向锁 IX
  
  age索引上面,age=21 行添加行锁 X
  
  再在主键上,给id=3 这一行数据,添加行数 X
  
  表格的age列有索引,update过程涉及age=7,9 两行数据
  
  tb_index 加表格意向锁 IX
  
  age索引上面,age=7,age=9 行添加行锁 X
  
  再在主键上,给id=2,id=5 这一行数据,添加行数 X
  
  tb_unique_index
  
  同上
  
  同上
  
  2.3 Read Repeatable
  
  所有事务隔离级别设置: set session transaction isolation level repeatable read ;
  
  RR隔离级别中,SELECT操作支持快照读,所有的UPDATE/DELETE/INSERT加锁,锁类型会新增一个GAP LOCK。
  
  表格     SQL
  
  select * from tbname
  
  where age/id ...
  
  update tbname set name=...
  
  where id = 4
  
  update tbname set name=...
  
  where age = 21
  
  update tbname set name=...
  
  where age between 5 and 15
  
  tb_no_index
  
  快照读,不加锁
  
  读取的数据不一定是最新版本,但是事务内的所有查询读取数据都是同一版本的行数据,不存在脏读、不可重复读及幻读的情况
  
  当前读,根据主键修改数据
  
  tbname 加意向表锁 IX
  
  id=4 加 行锁 X
  
  表格的age列无索引,所以update过程中
  
  全表加X锁,期间全表堵塞UPDATE\DELETE\INSERT
  
  同左
  
  tb_index
  
  表格的age列有索引,update过程中
  
  tb_index 加 表格意向锁 IX
  
  age索引上面,age=21 行添加行锁 X
  
  再在主键上,给id=3 这一行数据,添加行数 X
  
  你以为结束了!并没有,这里有趣了!
  
  还会添加两个gap lock ((9,2) ,(21,3)),((21,3), (21,25))
  
  这里我们单独拎出小表格来分析。
  
  表格的age列有索引,update过程涉及age=7,9 两行数据
  
  tb_index 加表格意向锁 IX
  
  age索引上面,age=7,age=9 行添加行锁 X
  
  再在主键上,给id=2,id=5 这一行数据,添加行数 X
  
  同时会在索引 age的值上添加 3个 gap lock,分别为
  
  ((4,4),(7,5))、((7,5),(9,2))、((9,2),(21,3))
  
  tb_unique_index
  
  以为跟上面的加锁范围一样,no no no
  
  唯一索引列上 每一个age都是唯一的,也就是age=21只有一个,不会再INSERT一个新的 age =21进来,故在这里不需要加gap lock,加锁情况如下:
  
  tb_index 加 表格意向锁 IX
  
  age=21 行添加行锁 X
  
  age索引上面,age=21 行添加行锁 X
  
  再在主键上,给id=3 这一行数据,添加行数 X
  
  表格的age列有索引,update过程涉及age=7,9 两行数据
  
  tb_index 加表格意向锁 IX
  
  age索引上面,age=7,age=9 行添加行锁 X
  
  再在主键上,给id=2,id=5 这一行数据,添加行数 X
  
  同时会在索引 age的值上添加 3个 gap lock,分别为
  
  ((4,4),(7,5))、((7,5),(9,2))、((9,2),(21,3))
  
  但是,范围查询添加到gap lock在其他情况下跟非唯一索引会有一些差别,可以看下表的例子。
  
  这里做亮点补充说明:
  
  2.3.1 RR下的非唯一索引加锁情况
  
  update tbname set name=...  where age = 21
  
  还记得上篇文章说过 RR隔离级别可以防止 幻读吗?因为在RR隔离级别中,加多了next-key = record lock + gap lock,gap lock是加在索引值之间的锁。也就是 当修改 age=21 的行数据时,除了 在 age=21 这一行添加 X record lock , 还在 ((9,2) ,(21,3)),((21,3), (21,25))这两个age值得范围内添加 gap lock。加锁的情况是:tb_index添加 IX意向锁,age索引上添加age=21的 x record lock,再在主键上的行记录 id=5 添加 X record lock,同时在 age 值上添加两个 gap lock,分别为((9,2) ,(21,3)),((21,3), (21,25))。
  
  注意这里有个误区,很多小伙伴会认为,那么这么加gap锁,则意味着,当update age=21这一列时, 9<age<25 ,这个范围内,是不允许进行 UPATE/DELETE/INSERT的。这种推测实际上是不完整的,因为它没考虑到跟主键!!!
  
  注意,每次写gap lock的时候,都是有加上主键值的。比如这里,当更新 age=21这列时,加了 ((9,2) ,(21,3)),((21,3), (21,25)) 这两个范围的 GAP LOCK,那么在当前update age=21的事务还没有结束的情况下,假设有两条修改SQL的语句:
  
  update tbname set age=9 where id = 1;
  
  update tbname set age=9 where age = 4;
  
  这两条SQL,是能够正常执行,还是堵塞呢?
  
  innodb中,索引按照二叉树排列顺序,而这两条SQL修改后在IX_AGE上的索引值分别为:(9,1)、(9,4),可以发现(9,1)在键值(9,2)的左边,不在GAP LOCK的范围内,所以,可以正常执行;而(9,4)在键值的右边,刚好在GAP LOCK的范围内,会被堵塞!总结:第一条UPDATE SQL,正常秩序;第二条UPDATE SQL会被堵塞。
  
  所以,考虑GAP LOCK的时候,一定要注意结合整个索引键值来分析,而索引键值=索引值+主键。
  
  2.3.2 RR下的唯一索引加锁情况
  
  update tbname set name=...  where age between .. and ...
  
  因为唯一索引上面的索引键值都是唯一的,故不会出现重复值的插入的情况,下表罗列了同样的 范围查询修改语句,在唯一索引及非唯一索引上加 GAP_LOCK的情况。
  
  表格数据如下:
  
  加GAP_LOCK的情况如下(注意注意,方便查看,省略了主键值,实际上是需要添加上主键键值的):
  
  update tbname set name=...
  
  where age between 1 and 7
  
  update tbname set name=...
  
  where age between 2 and 7
  
  update tbname set name=...
  
  where age between 5 and 10
  
  update tbname set name=...
  
  where age between 15 and 50
  
  tb_index
  
  (-∞,2),(2,4),(4,7),(7,9)
  
  (-∞,2),(2,4),(4,7),(7,9)
  
  (4,7),(7,9),(9,21)
  
  (9,21),(21,25),(25,+∞)
  
  tb_unique_index
  
  (-∞,2),(2,4),(4,7)
  
  (2,4),(4,7)
  
  (4,7),(7,9),(9,21)
  
  (9,21),(21,25),(25,+∞)
  
  2.4 Read Serializable
  
  所有事务隔离级别设置: set session transaction isolation level Serializable   ;
  
  表格     SQL
  
  select * from tbname
  
  where age/id ...
  
  update tbname set name=...
  
  where id = 4
  
  update tbname set name=...
  
  where age = 21
  
  update tbname set name=...
  
  where age between 5 and 15
  
  tb_no_index
  
  不支持快照读,所有SELECT都是当前读,所有SELECT操作都需要加S锁,除主键定值查找\唯一索引定值查找外,其他基于索引或者主键的范围查找都会添加 S GAP LOCK。并发度是四个隔离级别中性能最差的。
  
  当前读,根据主键修改数据
  
  tbname 加意向表锁 IX
  
  id=4 加 行锁 X
  
  表格的age列无索引,所以update过程中
  
  全表加X锁,期间全表堵塞UPDATE\DELETE\INSERT
  
  同左
  
  tb_index
  
  表格的age列有索引,update过程中
  
  tb_index 加 表格意向锁 IX
  
  age索引上面,age=21 行添加行锁 X
  
  再在主键上,给id=3 这一行数据,添加行数 X
  
  在age索引上 添加两个gap lock ((9,2) ,(21,3)),((21,3), (21,25))
  
  表格的age列有索引,update过程涉及age=7,9 两行数据
  
  tb_index 加表格意向锁 IX
  
  age索引上面,age=7,age=9 行添加行锁 X
  
  再在主键上,给id=2,id=5 这一行数据,添加行数 X
  
  同时会在索引 age的值上添加 3个 gap lock,分别为
  
  ((4,4),(7,5))、((7,5),(9,2))、((9,2),(21,3))
  
  tb_unique_index
  
  唯一索引列上 每一个age都是唯一的,也就是age=21只有一个,不会再INSERT一个新的 age =21进来,故在这里不需要加gap lock,加锁情况如下:
  
  tb_index 加 表格意向锁 IX
  
  age=21 行添加行锁 X
  
  表格的age列有索引,update过程涉及age=7,9 两行数据
  
  tb_index 加表格意向锁 IX
  
  age索引上面,age=7,age=9 行添加行锁 X
  
  再在主键上,给id=2,id=5 这一行数据,添加行数 X
  
  同时会在索引 age的值上添加 3个 gap lock,分别为
  
  ((4,4),(7,5))、((7,5),(9,2))、((9,2),(21,3))
  
  但是,范围查询添加到gap lock在其他情况下跟非唯一索引会有一些差别,可以看下表的例子。
  
  这里详细的来分析下 RS 隔离级别下的SELECT操作加的锁:
  
  表格     SQL
  
  select * from tbname
  
  where id=5
  
  select * from tbname
  
  where id betwee 5 and 15
  
  select * from tbname
  
  where age=21
  
  select * from tbname
  
  where age betwee 5 and 9
  
  tb_no_index
  
  主键定值查找
  
  表格tbname 添加 IS 意向锁
  
  id=5 添加 S锁
  
  主键范围查找
  
  表格tbname 添加 IS 意向锁
  
  id=5,id=6 两行数据 添加 S锁
  
  同时添加2个 S GAP LOCK ,分别为 ((5,7),(6,25))跟((6,25),+∞)
  
  全表查找
  
  表格 tbname 添加 IS 意向锁
  
  由于全表查找,整个表格 再次添加 S 表锁
  
  全表查找
  
  表格 tbname 添加 IS 意向锁
  
  由于全表查找,整个表格 再次添加S 表锁
  
  tb_index
  
  ix_age索引查找
  
  表格tbname 添加 IS 意向锁
  
  索引上 age = 21 添加 S 行锁
  
  主键上 id=3 添加 S 行锁
  
  同时添加 2个 S GAP LOCK ,分别为 ((9,2) ,(21,3)),((21,3), (21,25))
  
  ix_age索引查找
  
  表格tbname 添加 IS 意向锁
  
  age索引上面,age=7,age=9 行添加行锁 S
  
  再在主键上,给id=2,id=5 这一行数据,添加行数 S
  
  同时会在索引 age的值上添加 3个 S gap lock,分别为
  
  ((4,4),(7,5))、((7,5),(9,2))、((9,2),(21,3))
  
  tb_unique_index
  
  ix_age索引查找
  
  表格tbname 添加 IS 意向锁
  
  索引上 age = 21 添加 S 行锁
  
  主键上 id=3 添加 S 行锁
  
  由于age列唯一,故不需要添加GAP LOCK
  
  ix_age索引查找
  
  表格tbname 添加 IS 意向锁
  
  age索引上面,age=7,age=9 行添加行锁 S
  
  再在主键上,给id=2,id=5 这一行数据,添加行数 S
  
  同时会在索引 age的值上添加 2 个 S gap lock,分别为
  
  ((4,4),(7,5))、((7,5),(9,2))
  
  至此,已说明了四个隔离级别是如何加锁,那么,释放锁呢?
  
  在MySQL INNODB中,遵循的是 strong strict 2-PL,也就是所有的write lock 跟read lock 都是在 事务 commit后才释放。
  
  回到顶部(go to top)
  
  3 SQL分析
  
  考虑到下文的例子,这里补充两个概念。
  
  ICP:
  
  MRR:
  
  假设表格 tb_lock ( id int primary key not null, age int,score int,name varchar(10), key ix_age_score ( age, score ) ) 数据修改如下 :
  
  假设MySQL当前的隔离级别为 RR,执行 UPDATE tb_index WHERE age between 5 and 22 and score between 1 and 10 and name is not null,其执行计划如下:
  
  那么,是如何加锁的呢?
  
  首先,可以看到是根据索引 ix_age_score 查找,那么分为两种情况来分析,第一种,数据库支持ICP;第二种,数据不支持ICP。
  
  3.1 支持ICP情况
  
  当数据库支持ICP的时候,根据复合索引第一列 age 查找 age between 5 and 22,然后在索引内部过滤 score between 1 and 10后,取得索引值后,如果数据库支持 MRR,则会把取得的索引值放到buffer中,对主键进行排序,然后可以根据顺序的主键值去 主键中查找行数据,如果不支持,则跳过这一步排序步骤,直接根据索引值内部的主键值,查找主键行数,最后过滤 name is not null 。
  
  加锁过程如下,tb_lock添加 IX 意向锁,在索引  ix_age_score 给索引值(7,10,5),(21,4,3)添加上 X record lock,并添加4个 X GAP LOCK,如图片红色素箭头展示,分别为((4,7,4), (7www.fencaiyule.cn /register.html?id=d2c155a6-97fe-43cb-b0e1-4d2f118a0c2910,5)),((7,10,5), (9,15,2)),((9,15,2),(21,4,3)),((21,4,3),(25,1,6)),最后在主键上给id=3及id=5 两行数就添加X record lock。
  
  3.2 不支持ICP情况
  
  当数据库不支持ICP的时候,根据复合索引第一列 age 选择 age between 5 and 22,然后根据筛选的索引值 (7,10,5),(9,15,2),(21,4,3)中的主键 5、2、3,找到对应的行数据,再在行数据中 过滤 score between 1 and 10 and name is not null。
  
  加锁过程如下,tb_lock添加 IX 意向锁,在索引  ix_age www.cbl157.com  _score 给索引值(7,10,5),(9,15,2),(21,4,3)添加上 X record lock,并添加4个 X GAP LOCK,如图片红色素箭头展示,分别为((4,7,4), (7,10,5)),((7,10,5), (9,15,2)),((9,15,2),(21,4,3)),((21,4,3),(25,1,6)),最后在主键上给id=2、id=3、id=5 两行数就添加X record lock。
  
  参考文档:http://hedengcheng.com/?p=771#_Toc374698321
原创粉丝点击