暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MYSQL8 简单加锁测试

215

看了很多文章,感觉加锁是个很复杂的事情,最近遇到LTS库进程发生死锁. 还是RC隔离级别. 

只好自己做实验来完善加锁理论知识!

我们知道,谈及关系型数据库的基础知识是绕不开MVCC

的,而谈到MySQL,一般我们说的都是InnoDB引擎的锁相关内容。比如我们熟知的表锁、record locknext-key lock 以及意向锁等吧啦吧啦一堆。

今天主要围绕行锁相关,分析在日常的使用过程中RR
RC
下的加锁情况。因为平时使用中主要以RRRC为主,当然RC偏多一些。在这两种隔离级别下分别介绍主键、唯一索引、普通索引和无索引字段上操作的情况。

比如我们有一张 mis_pointer
 表,结构如以下:

复制
CREATE TABLE `mis_pointer` (
  `id` int NOT NULL,
  `p_name` varchar(64DEFAULT NULL,
  `p_addr` int DEFAULT NULL,
  `p_type` mediumint DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `p_addr` (`p_addr`),
  KEY `p_type` (`p_type`)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

复制
复制

这里没有自增键,AI表数据如下,后面介绍过程中数据可能发生变化哈。


测试SQL

set transaction_isolation='repeatable-read';
begin;
select * from mis_pointer mp where mp.id < 10 for update;

select THREAD_ID,OBJECT_NAME,INDEX_NAME,LOCK_TYPE,LOCK_MODE,LOCK_STATUS,LOCK_DATA 
from performance_schema.data_locks;

rollback;

复制

使用的工具如下: 它比较好看结果


先简单介绍下MYSQL的锁

15.7.1 InnoDB Locking


This section describes lock types used by InnoDB
.

  • Shared and Exclusive Locks   共享和独占锁 S,X

  • Intention Locks  意向锁

  • Record Locks  记录锁

  • Gap Locks    间隙锁

  • Next-Key Locks

  • Insert Intention Locks  插入意向锁

  • AUTO-INC Locks  自增锁

  • Predicate Locks for Spatial Indexes 空间索引锁

1 表锁, 有独占锁(X),共享锁(S) ,读意向锁(IS 锁)写意向锁(IX 锁)自增锁(AI)四种模式
手工给表 上 X 和S 锁 

    mysql> lock table user read(write);


    mysql> unlock tables;
    复制


    类似ORACLE 的TM, 目的就是告诉后来者目前该表正在干啥, 避免你每行去检查,提高性能.

    2 行锁 有四种类型:

    • Record Locks  记录锁

    • Gap Locks    间隙锁

    • Next-Key Locks 

    • Insert Intention Locks  插入意向锁  实际是插入间隙 IGAP

    加锁 分为 加锁对象,加锁的类型,加锁的模式,加锁的数据

    这里我们看到在MIS_POINTER表上加锁,

    INDEX_NAME :把锁加在 P_ADDR索引上 (加锁对象)

    LOCK_TYPE: 加锁的类型: 表和记录锁; 

    LOCK_MODE:加锁的模式 IX, X ;

    LOCK_DATA 加锁锁定的数据 1012值和对应的主键ID 9


    RR隔离级别下

    1主键索引加锁

    1.1范围条件

    使用以下语句进行范围更新时,LOCK_MODE显示加X锁,其实是加了Next key Lock(即锁住主键索引和前面的间隙),范围后面的11也加了XGAP

      set transaction_isolation='repeatable-read';
      begin;
      select * from mis_pointer mp where mp.id < 10 for update;


      复制

      这里能看出加了个表锁, IX意向更新锁, 然后对于小于10的记录,全加X锁, 9到11之间加GAP锁

        update mis_pointer mp set p_name='xxx' where mp.id < 10;
        复制

        这里 UPDATE 和FOR UPDATE 是一样的.

        1.2范围条件未命中


          set transaction_isolation='repeatable-read';
          begin;


          update mis_pointer mp
          set p_name='xxx'
          where mp.id > 6 and mp.id<9;


          复制

          会加GAP间隙锁

          1.3等值条件

            select * from mis_pointer mp where mp.id = 9 for update
            复制


            只加主键索引,记录锁,X

            1.4等值条件未命中

              set transaction_isolation='repeatable-read';
              begin;
              select * from mis_pointer mp where mp.id = 10 for update


              复制

              数据在中间就加间隙锁

                set transaction_isolation='repeatable-read';
                begin;
                select * from mis_pointer mp where mp.id = 13 for update
                复制

                数据在边上加SUPREMUM... 无穷+

                它不会阻碍其它会话更新

                  UPDATE mis_pointer mp


                  SET p_name='xxx'


                  WHERE mp.id = 9;
                  mysql> select * from mis_pointer;
                  +----+--------+--------+--------+
                  | id | p_name | p_addr | p_type |
                  +----+--------+--------+--------+
                  | 1 | a | 1000 | 1 |
                  | 2 | b | 1028 | 1 |
                  | 5 | a | 1004 | 1 |
                  | 6 | a | 1008 | 2 |
                  | 9 | xxx | 1012 | 3 |
                  | 11 | a | 1016 | 3 |
                  | 12 | c | 1088 | 1 |
                  +----+--------+--------+--------+
                  7 rows in set (0.00 sec)


                  复制

                  会阻塞后续边界上的插入

                    mysql> insert into mis_pointer(id,p_name,p_addr,p_type) values(15,'e','1099',4);
                    复制


                    形成阻塞效果

                    2 唯一索引加锁


                      set transaction_isolation='repeatable-read';
                      begin;
                      select * from mis_pointer mp where mp.p_addr < 1012 for update


                      复制

                      先在唯一索引加X锁,这里要多加一把锁,然后在主键索引加X锁

                      2.2范围条件未命中

                        set transaction_isolation='repeatable-read';
                        begin;
                        select * from mis_pointer mp
                        where mp.p_addr < 1011 and mp.p_addr > 1008 for update


                        复制

                        也要加一把 X 锁 这把锁显示加载 唯一索引上

                        2.3 等值条件

                          begin;
                          select * from mis_pointer mp where mp.p_addr = 1012 for update;
                          复制


                          2.4 等值条件未命中

                            set transaction_isolation='repeatable-read';
                            begin;
                            select * from mis_pointer mp where mp.p_addr = 1011 for update


                            复制

                            而 等值下未命中要在唯一索引加X 和GAP间隙锁. 也就是 NEXT-KEY

                            3 普通索引加锁

                            3.1范围条件

                              select * from mis_pointer mp where mp.p_type < 3 for update
                              复制



                              普通索引 IDX_P_TYPE{(1,1);(1,2) ; (1,5) ; (2,6) ; (3,9) ; (3,11) ; (1,12)}
                              LOCK_DATE选中的是 {(1,1);(1,2) ; (1,5) ; (2,6) ; (3,9) ; (1,12)}

                              也就是说二级索引的小于符合< 实际上是<= 要在边界也加把X锁. 到了主键索引上就没有边界的对应的锁

                              3.2范围条件未命中

                                set transaction_isolation='repeatable-read';
                                begin;
                                select * from mis_pointer mp
                                where mp.p_type >1 and mp.p_type< 3 for update;


                                select THREAD_ID,OBJECT_NAME,INDEX_NAME,LOCK_TYPE,
                                LOCK_MODE,LOCK_STATUS,LOCK_DATA
                                from performance_schema.data_locks;


                                复制


                                只加索引的X 


                                3.3等值条件

                                在等值查找条件下,RR隔离级别下,索引记录加Next Key Lock,对应主键加锁

                                  select * from mis_pointer mp where mp.p_type = 3 for update
                                  复制

                                  这里出现了SUPREMUM PSEUNDO-RECORD (上确界伪记录) 也是一种NEXT-KEY

                                  插入 P_TYPE=4 后的记录就出现GAP

                                    SHOW ENGINE INNODB STATUS;


                                    ---TRANSACTION 19418440, ACTIVE 0 sec


                                    4 lock struct(s), heap size 1128, 5 row lock(s)
                                    这里表示4个锁结构,锁定5行数据
                                    复制

                                    3.4 等值条件未命中

                                    这里把原来的2改成5, 然后再改回来

                                      set transaction_isolation='repeatable-read';
                                      begin;
                                      select * from mis_pointer mp where mp.p_type =2 for update;


                                      复制

                                        select * from mis_pointer mp where mp.p_type =5  for update;
                                        复制


                                        4 无索引字段


                                        4.1范围条件

                                        比如范围条件下


                                          set transaction_isolation='repeatable-read';
                                          begin;
                                          select * from mis_pointer mp where mp.p_name > 'b' for update


                                          复制


                                          这下就悲催了,所有主键索引加X,即相当于全主键索引加锁(全表)

                                          A 也不放过无索引真不行

                                          4.2范围条件下未命中

                                            select * from mis_pointer mp 
                                            where mp.p_name > 'd' and mp.p_name < 'e' for update;
                                            复制

                                            4.3等值条件下

                                              set transaction_isolation='repeatable-read';
                                              begin;
                                              select * from mis_pointer mp where mp.p_name = 'b' for update


                                              复制

                                              4.4等值条件下未命中

                                                set transaction_isolation='repeatable-read';
                                                begin;
                                                select * from mis_pointer mp where mp.p_name = 'E' for update


                                                复制


                                                这里无论怎么样都要锁全主键索引(全表)

                                                不过后期得到优化, 这里涉及到了MYSQL架构的问题. MYSQL分服务层和引擎层.  服务层要什么数据,引擎层就返回什么数据,然后由服务层完成过滤.

                                                这个图表示 引擎加锁后,服务层发现不是需要更新的,通知引擎解锁. 再高并发下是个问题.

                                                更新索引字段

                                                 从下面的语句无法发现被修改的索引的加锁信息

                                                  select THREAD_ID,OBJECT_NAME,INDEX_NAME,
                                                  LOCK_TYPE,LOCK_MODE,LOCK_STATUS,LOCK_DATA
                                                  from performance_schema.data_locks;
                                                  复制


                                                  查询加锁跟上面各种情况一致.

                                                  6 DELETE语句
                                                  跟UPDATE语句一样, 同时也要维护索引 前面1到4+5的情况

                                                  7 插入语句

                                                    insert into mis_pointer(id,p_name,p_addr,p_type) 
                                                    value(16,'d',1086,7);
                                                    复制

                                                    这个语句无法发现插入语句其它加锁的信息

                                                    理论上说 对唯一索引加GAP间隙锁或者是NEXT-KEY边界锁,对自增ID要加AI锁.

                                                    2 RC隔离级别下

                                                    1主键索引加锁

                                                    2.1.1范围条件

                                                      set transaction_isolation='read-committed';
                                                      begin;
                                                      select * from mis_pointer mp where mp.id < 11 for update;


                                                      rollback;


                                                      复制

                                                      只加主键记录锁,不会加GAP

                                                      2.1.2范围条件未命中

                                                        set transaction_isolation='read-committed';
                                                        begin;


                                                        update mis_pointer mp
                                                        set p_name='xxx'
                                                        where mp.id > 6 and mp.id<9;


                                                        复制


                                                        表意向X锁 不阻碍其它会话插入

                                                        2.1.3等值条件

                                                          set transaction_isolation='read-committed';
                                                          begin;
                                                          update mis_pointer mp
                                                          set p_name='xxx'
                                                          where mp.id =9;


                                                          复制


                                                          2.1.4等值条件未命中

                                                            set transaction_isolation='read-committed';
                                                            begin;
                                                            update mis_pointer mp
                                                            set p_name='xxx'
                                                            where mp.id =10;


                                                            复制


                                                            表意向X

                                                            2 唯一索引加锁

                                                            2.2.1范围条件

                                                              set transaction_isolation='read-committed';
                                                              begin;
                                                              select * from mis_pointer mp where mp.p_addr < 1012 for update


                                                              复制

                                                              可以看到不会加GAP,只是在对应唯一索引上加记录锁,相对应的主键索引加记录锁

                                                              这里明显 唯一索引加了4把锁, 而主键索引上只加了3把锁.

                                                              2.2.2范围条件未命中

                                                                set transaction_isolation='read-committed';
                                                                begin;
                                                                select * from mis_pointer mp where mp.p_addr < 1011 and mp.p_addr > 1008 for update


                                                                复制


                                                                也要在唯一索引加把锁


                                                                2.2.3等值条件

                                                                  set transaction_isolation='read-committed';
                                                                  begin;
                                                                  select * from mis_pointer mp where mp.p_addr = 1012 for update


                                                                  复制

                                                                  只加

                                                                  2.2.4等值条件未命中

                                                                    set transaction_isolation='read-committed';
                                                                    begin;
                                                                    select * from mis_pointer mp where mp.p_addr = 1011 for update;


                                                                    select THREAD_ID,OBJECT_NAME,INDEX_NAME,LOCK_TYPE,
                                                                    LOCK_MODE,LOCK_STATUS,LOCK_DATA
                                                                    from performance_schema.data_locks;


                                                                    复制

                                                                    只加表意向X锁

                                                                    3 普通索引加锁

                                                                    2.3.1范围条件

                                                                      begin;
                                                                      select * from mis_pointer mp where mp.p_type < 3 for update


                                                                      复制

                                                                      可[3,9] 跑不掉也要加锁

                                                                      2.3.2范围条件未命中

                                                                        set transaction_isolation='read-committed';
                                                                        begin;
                                                                        select * from mis_pointer mp
                                                                        where mp.p_type >1 and mp.p_type< 3 for update;


                                                                        复制


                                                                        2.3.3等值条件

                                                                        在等值查找条件下,RC隔离级别下,索引记录和对应主键加记录锁

                                                                          begin;


                                                                          select * from mis_pointer mp where mp.p_type = 3 for update
                                                                          复制

                                                                          2.3.4等值条件未命中

                                                                            select * from mis_pointer mp where mp.p_type = 2 for update;
                                                                            复制


                                                                            4 无索引字段

                                                                              set transaction_isolation='read-committed';
                                                                              begin;
                                                                              select * from mis_pointer mp where mp.p_name > 'b' for update


                                                                              复制


                                                                              范围条件下,不会锁全表,会锁定记录对应的主键索引,加记录锁,不会锁定间隙

                                                                              该字段值条件范围内对应主键和其他字段的操作也会被阻塞

                                                                              阻塞以该字段为条件的更新删除操作的会话

                                                                              由于没有GAP锁的存在,并不影响写入操作

                                                                              2.4.2范围条件下未命中


                                                                                select * from mis_pointer mp 
                                                                                where mp.p_name > 'd' and mp.p_name < 'e' for update;
                                                                                复制

                                                                                2.4.3等值条件下未命中

                                                                                  set transaction_isolation='read-committed';
                                                                                  begin;
                                                                                  select * from mis_pointer mp where mp.p_name = 'e' for update


                                                                                  复制


                                                                                  只加意向修改表锁


                                                                                  2.4.4 等值条件下

                                                                                    set transaction_isolation='read-committed';
                                                                                    begin;
                                                                                    select * from mis_pointer mp where mp.p_name = 'b' for update


                                                                                    复制


                                                                                    最后 我们发现RC隔离下 唯一索引和普通索引都要加一把锁,命中和没有命中情况下都要加.这也是没有办法索引上只有这个值! 也就是说维持范围的边界特性而多加一把记录锁; RR隔离下也有!

                                                                                    不过RR隔离下唯一和普通索引 未命中下要加X, GAP 也就是NEXT-KEY

                                                                                    隐式锁 简介

                                                                                    隐式锁主要用在插入场景中。在Insert语句执行过程中,必须检查两种情况,一种是如果记录之间加有间隙锁,为了避免幻读,此时是不能插入记录的,另一中情况如果Insert的记录和已有记录存在唯一键冲突,此时也不能插入记录。除此之外,insert语句的锁都是隐式锁,但跟踪代码发现,insert时并没有调用lock_rec_add_to_queue函数进行加锁, 其实所谓隐式锁就是在Insert过程中不加锁。

                                                                                    只有在特殊情况下,才会将隐式锁转换为显示锁。这个转换动作并不是加隐式锁的线程自发去做的,而是其他存在行数据冲突的线程去做的。例如事务1插入记录且未提交,此时事务2尝试对该记录加锁,那么事务2必须先判断记录上保存的事务id是否活跃,如果活跃则帮助事务1建立一个锁对象,而事务2自身进入等待事务1的状态

                                                                                    如何判断隐式锁是否存在

                                                                                    InnoDB的每条记录中都一个隐含的trx_id字段,这个字段存在于聚集索引的B+Tree中。假设只有主键索引,则在进行插入时,行数据的trx_id被设置为当前事务id;假设存在二级索引,则在对二级索引进行插入时,需要更新所在page的max_trx_id。

                                                                                    因此对于主键,只需要通过查看记录隐藏列trx_id是否是活跃事务就可以判断隐式锁是否存在。对于对于二级索引会相对比较麻烦,先通过二级索引页上的max_trx_id进行过滤,如果无法判断是否活跃则需要通过应用undo日志回溯老版本数据,才能进行准确的判断。

                                                                                    由于二级索引的记录不包含事务ID,如何判断二级索引记录上是否有隐式锁呢?前面提到二级索引页的PAGE_MAX_TRX_ID字段保存了一个最大事务ID,当二级索引页中的任何记录更新后,都会更新PAGE_MAX_TRX_ID的值。因此,我们先可以通过PAGE_MAX_TRX_ID进行判断,如果当前PAGE_MAX_TRX_ID的值小于当前活跃事务的最新ID,说明修改这条记录的事务已经提交,则不存在隐式锁,反之则可能存在隐式锁,需要通过聚集索引进行判断,其判断过程由函数row_vers_impl_x_locked_low完成,


                                                                                    不同类型行锁的兼容矩阵


                                                                                    MYSQL 加锁影响因素


                                                                                    MYSQL

                                                                                    隔离机制

                                                                                    版本

                                                                                    SQL语句

                                                                                    查询范围

                                                                                    索引状态

                                                                                    服务层

                                                                                    RC

                                                                                    5.7

                                                                                    INSERT

                                                                                    =

                                                                                    主键

                                                                                    引擎层

                                                                                    RR

                                                                                    8.0

                                                                                    DELETE

                                                                                    > 

                                                                                    唯一索引




                                                                                    UPDATE

                                                                                    !=

                                                                                    普通索引






                                                                                    无索引






                                                                                    更新索引列


                                                                                    MYSQL Performance 内存控制

                                                                                    MYSQL 产生大量数据的过程


                                                                                    文章转载自海鲨数据库架构师,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                                                                    评论