• Mysql中的三类锁,你知道吗?


    导读

    • 正所谓有人(锁)的地方就有江湖(事务),人在江湖飘,怎能一无所知?

    • 今天来细说一下Mysql中的三类锁,分别是全局锁、表级锁、行级锁。

    • 文章首发于作者公众号【码猿技术专栏】,原创不易,喜欢的点个赞关注一下,谢谢!!!

    全局锁

    • 全局锁简单的说就是锁住整个数据库实例,命令是Flush tables with read lock。当你需要为整个数据库处于只读的状态的时候,可以使用这个命令。
    • 一旦使用全局锁,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。
    • 全局锁的使用场景大部分都是用来数据库备份

    为什么备份要加全局锁?

    • 用户买东西,首先会从余额里扣除金额,然后在订单里添加商品。如果备份数据库,不加锁,并且备份顺序为先备份用余额,再备份订单商品,有可能备份了用户余额后,用户下订单买东西提交事务,然后再备份订单商品表, 此时订单商品已存在。最后备份出来的数据为。最后用户余额为买东西前的余额,没有减少,但是订单商品却多了。演示如下图:

    • 这种情况可能用户会觉得赚了,但是如果备份顺序反过来,先备份商品表再备份余额表,用户就会发现我付了钱,但是商品没有加,这中结果就会更加的严重。
    • 因此保证备份数据的一致性很重要,必要的手段就是加锁。

    全局锁有什么坏处?

    • 全局锁是个啥?介绍完了读者心里已经有数了,让这个库只读?这是多么可怕的操作,简单列举几个危险之处:
      • 如果在主库备份,备份期间不能执行任何更新操作,会导致整个业务停摆,高并发情况下更甚。
      • 如果你在从库上备份,那么备份期间从库不能执行主库同步过来的binlog,会导致主从延迟。

    全局备份比较好的解决方案

    • 全局锁远瞅不错,近瞅吓一跳,陈某在此不推荐使用。
    • 其实 官方自带的逻辑备份工具是mysqldump。当mysqldump使用参数**–single-transaction**的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于MVCC的支持,这个过程中数据是可以正常更新的。
    • 一致性备份是好,但前提是存储引擎支持事务,这也是MyISAM被InnoDB取代的原因之一。

    表级锁

    • MySQL里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)。
    • 表锁一般是在数据库引擎不支持行锁的时候才会被用到的 。
    • MDL会直到事务提交才释放,在做表结构变更的时候,你一定要小心不要导致锁住线上查询和更新 。

    如何加表锁

    • 显式加表锁和解锁的语句很简单,如下:
        lock tables tb_name read/write;

    unlock tables;
    • 需要注意,lock tables语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。
    • 举个例子, 如果在某个线程A中执行lock tables t1 read, t2 write; 这个语句,则其他线程写t1、读写t2的语句都会被阻塞。同时,线程A在执行unlock tables之前,也只能执行读t1、读写t2的操作。连写t1都不允许,自然也不能访问其他表。

    MDL

    • MDL不需要显式使用,在访问一个表的时候会被自动加上。
    • 当对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加MDL写锁。
    • 读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。
    • 读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。

    查询表级锁争用

    • 查询表级锁的争用可以通过以下参数分析获得:
      • Table_locks_immediate:能够立即获得表级锁的次数
      • Table_locks_waited: 不能立即获取表级锁而需要等待的次数
    • 查询语句如下:
        show status like 'table_locks_waited'
    • 如果Table_locks_waited的值比较大,则说明存在着较严重的表级锁争用情况。

    行级锁

    • MySQL的行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如MyISAM引擎就不支持行锁。不支持行锁意味着并发控制只能使用表锁,对于这种引擎的表,同一张表上任何时刻只能有一个更新在执行,这就会影响到业务并发度。InnoDB是支持行锁的,这也是MyISAM被InnoDB替代的重要原因之一。
    • InnoDB的行锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从行锁升级为表锁
    • 在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。
    • 行级锁分为排它锁(写锁)、共享锁(读锁)、间隙锁

    排他锁

    • 排他锁,也称写锁,独占锁,当前写操作没有完成前,它会阻断其他写锁和读锁。
    • Mysql中的更新语句(update/delete/insert)会自动加上排它锁。

    • 如上图,事务B中的update语句被阻塞了,直到事务A提交才能执行更新操作。
    • 排他锁也可以手动添加,如下:
        select * from user where id=1 for update;
    • 注意以下两点:
      • 行锁是针对索引加锁的,上述例子中id是主键索引。
      • 加了排他锁并不是其他的事务不能读取这行的数据,而是不能再在这行上面加锁了。

    间隙锁

    • 当我们用范围条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做"间隙(GAP)"。InnoDB也会对这个"间隙"加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。

    • 如上图,给id>5中并不存在的数据加上了间隙锁,当插入id=6的数据时被阻塞了。
    • 这是一个坑:若执行的条件是范围过大,则InnoDB会将整个范围内所有的索引键值全部锁定,很容易对性能造成影响

    共享锁

    • 共享锁,也称读锁,多用于判断数据是否存在,多个读操作可以同时进行而不会互相影响。当如果事务对读锁进行修改操作,很可能会造成死锁。如下图所示。

    分析行锁定

    • 通过检查InnoDB_row_lock 状态变量分析系统上的行锁的争夺情况 。
        show status like 'innodb_row_lock%' 

    • innodb_row_lock_current_waits: 当前正在等待锁定的数量。
    • innodb_row_lock_time: 从系统启动到现在锁定总时间长度;非常重要的参数
    • innodb_row_lock_time_avg: 每次等待所花平均时间;非常重要的参数。
    • innodb_row_lock_time_max: 从系统启动到现在等待最常的一次所花的时间;
    • innodb_row_lock_waits: 系统启动后到现在总共等待的次数;非常重要的参数。直接决定优化的方向和策略。

    死锁解决方案

    1、直接进入等待,直到超时。这个超时时间可以通过参数innodb_lock_wait_timeout来设置,默认50秒。注意超时时间不能设置太短,如果仅仅是短暂的等待,一旦设置时间很短,很快便解锁了,会出现误伤。

    2、发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数innodb_deadlock_detect设置为on,表示开启这个逻辑,默认开启。 主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的。 当并发很高的时候,检测死锁将会消耗大量的资源,因此控制并发也是很重要的一种策略。

  • 相关阅读:
    Effective Java 第三版——26. 不要使用原始类型
    Effective Java 第三版——25. 将源文件限制为单个顶级类
    Effective Java 第三版——24. 优先考虑静态成员类
    Effective Java 第三版——23. 优先使用类层次而不是标签类
    Effective Java 第三版——22. 接口仅用来定义类型
    Effective Java 第三版——21. 为后代设计接口
    Effective Java 第三版——20. 接口优于抽象类
    Effective Java 第三版——19. 如果使用继承则设计,并文档说明,否则不该使用
    Effective Java 第三版——18. 组合优于继承
    Effective Java 第三版——17. 最小化可变性
  • 原文地址:https://www.cnblogs.com/Chenjiabing/p/12610822.html
Copyright © 2020-2023  润新知