MySQL学习笔记四:锁机制

锁种类(Innodb)

通过锁粒度划分:行锁间隙锁表锁
  • 行级锁
    • 行锁是作用在索引上的
      • InnoDB行锁是通过给索引上的索引项加锁来实现的, 这也就意味着,只有通过索引条件检索的数据,Innodb才使用行锁,否则,innodb将使用表锁
        • 也不是说表锁,innodb在RR的隔离级别下,会加间隙锁。如果没有索引,会锁住所有行 + 所有间隙,相当于锁住了整个表
      • 行锁的产生和释放(两阶段锁协议
        • 行锁是在需要时被加上(比如事务中执行update语句,那么从这条update语句后加上行写锁)
        • 事务结束后才释放(并非执行完update就释放了)
        • 两阶段锁协议测试
          • session A
            • begin; select * from tb1 where id = 1 for update(加排他锁)
            • commit;
          • session B
            • select * from tb1 where id = 1,正常读,不阻塞
            • select * from tb1 where id = 1 lock in share mode, 加共享锁,阻塞
            • select ... for update/ update/delete 排他锁,阻塞
            • 阻塞状态下,session A commit后释放锁,session B正常执行
      • 锁相关配置参数
        • Innodb_row_lock_current_waits
        • Innodb_row_lock_time: 锁等待总时长
        • Innodb_row_lock_time_avg: 锁等待平均时长
        • Innodb_row_lock_time_max
        • Innodb_row_lock_waits: 锁等待总次数
  • 间隙锁Gap
    • 间隙锁的出现是为了解决RR隔离级别,非唯一索引下的幻读问题,至于Innodb隔离级别和幻读,在事务章节中有详解
      • 幻读,简单点说,在可重复读的隔离级别下,一个事务A中update....a = "xxx"了一行数据,在事务未提交的情况下,另一个事务Binsert a = "xxx"的一条数据,那么事务A将产生幻读,造成数据不一致(binlog恢复异常)和语义不明确(事务A中“我要锁住a = "xxx"行的声明失效”)
      • 间隙锁阻塞了在gap中的插入行为(gap是基于所使用索引的gap)
    • gap锁发生的场景
      • RR隔离级别下,在一个事务中加排它锁(update/delete/select...for update)会触发间隙锁,但是会根据索引类型和查询条件有所退化,下面详细说明
    • gap如何理解
      • 首先要确定这条加锁语句,触发了哪个索引
      • 命中所有索引的间隙,和所有索引前后边界,都形成了一些间隙,这些间隙会加锁,举例
        +----+-----------+--------+-----+-----+-------+---------------------+---------------------+
        | id | client_id | api_id | qpm | qpd | state | ctime               | mtime               |
        +----+-----------+--------+-----+-----+-------+---------------------+---------------------+
        |  3 | 123       |      1 |  10 |   0 |     0 | 2021-03-26 11:23:21 | 2021-03-26 11:23:21 |
        |  4 | 123       |      2 |  15 |   0 |     0 | 2021-03-26 11:23:21 | 2021-03-26 11:23:21 |
        |  5 | 126       |      4 |  10 |   1 |     0 | 2021-04-21 13:19:04 | 2021-04-22 11:17:05 |
        |  6 | 130       |      4 |  10 |   1 |     0 | 2021-04-21 13:19:04 | 2021-04-22 11:17:43 |
        
        这个表里,client_id是非唯一索引,那么这个表目前就有这些间隙
        (-∞, 123], (123, 126], (126, 130], (130, +supremum]   (这里使用了左开右闭的形式,即next key锁。next key = gap + 行)
        
        执行语句 `select * from t where client_id = 126 for update`
        将锁住(123, 126], (126, 130]的间隙,此区间的insert行为将阻塞,知道上面语句的事务提交
        
    • gap锁不是加在记录上,而是加在行行之间的间隙
  • Next key 锁
    • Next key = 间隙锁 + 行锁, 左开右闭区间
    • Next key 锁是加锁的基本单位,查找过程中访问到的对象才会加锁
    • 等值查询,命中唯一索引,Next Key锁退化为行锁。此时不会锁住间隙
    • 等值查询,如果扫描行的右边界值不等于该等值,Next key锁退化为间隙锁。此时会锁住所有间隙,但是右边界值不会被锁住
  • 表级锁
    • 锁全表的情况(这时其实不是表锁,二是间隙锁+行锁):
      • 全表扫描: 没有索引,执行当前读操作: delete users where uid = 55uid字段没有索引
        • RC隔离级别下: uid没有索引,innodb会做全表扫描,但是他不知道改锁谁(没有索引),这个时候会全表锁住。并把查询的结果给mysql server, 让mysql server过滤查询结果。
        • RR隔离级别下: 为了解决幻读问题,会加间隙锁,那么所有的记录,以及记录之间的间隙,全部被锁住了。
    • 表级锁的种类
      • 一是表锁,语法lock tables tb1 read, tb2 write可以强制加表锁
        • 表锁情况下,其他线程对tb1只读,写阻塞,tb2读写阻塞
      • 二是元数据锁MDL: metadata lock
通过类型划分:共享锁,排它锁
  • 共享锁(S)
    • 读锁: 加读锁后,可以再加读锁,不能加写锁(手动加)
      • select .... lock in share mode
  • 排他锁(X)
    • 写锁: 加锁后不能加读锁,也不能再加写锁(mysql自动加)
      • insert、update、delete 自动加写锁
      • select .... for update
通过情感划分:乐观锁,悲观锁

乐观锁和悲观锁不是锁实体,而是在处理业务逻辑时对于加锁行为的态度。

  • 乐观锁:我觉得在我处理数据期间,该数据不会被更改。所以我不对该数据加锁
    • 这种情况需要维护版本概念,当我提交数据时,服务端数据版本和我查询时的版本不同,需要重新加载后处理
  • 悲观锁:我觉得在我处理数据期间,该数据肯定会被更改。所以我在处理数据前要排它锁

锁相关查询语句

  1. 查看正在锁的事务: SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

    • The INNODB_LOCKS table provides information about each lock that an InnoDB transaction has requested but not yet acquired, and each lock that a transaction holds that is blocking another transaction.
    • INNODB_LOCKS表记录这样的锁信息,每个锁都是被一个INNODB事务“请求锁但未得”;并且每个锁都是有一个事务持有,并阻塞其他事务
      • 总结下来就是,这个表数据展示所有有阻塞的锁信息。官网详情
  2. 查看正在等待锁的事务: SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

    • The INNODB_LOCK_WAITS table contains one or more rows for each blocked InnoDB transaction, indicating the lock it has requested and any locks that are blocking that request.
    • INNODB_LOCK_WAITS表包含所有阻塞的innodb事务,这些事务是由于请求锁等待
      • 总结下来就是,这个表记录所有处于锁等待中的事务信息。官网详情
  3. 查看正在等待的行锁

    • SHOW STATUS LIKE 'innodb_row_lock%';

补充知识点

自增锁

为了维护自增主键的唯一性和递增性,Mysql提供了自增锁概念:

插入语句在获取主键值时,会先申请自增锁,然后获取主键值

MySQL5.1.22版本后新增参数innodb_autoinc_lock_mode,默认值为1

0: 表示采用MySQL5.0版本的自增锁策略,一个insert语句执行前获取锁,执行完成后才释放锁(肉眼可见,影响并发插入的性能)
1: 普通insert语句,自增锁申请后马上释放(获取主键值后,不等待语句执行);类似insert ... select...这种批量插入语句,自增锁还是等语句执行后释放
2:所有插入语句都是申请主键锁获取主键后立即释放

思考

  1. 行锁是加在索引上的,那么对于同一行记录,我通过二级索引字段加锁(锁是加在二级索引上),另一个事务通过主键加锁(锁是加载主键索引上的),这是也会阻塞,是为什么?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值