美文网首页
简介mysql之锁

简介mysql之锁

作者: 温岭夹糕 | 来源:发表于2021-03-07 20:51 被阅读0次

    前言

    在上篇中我们介绍了mysql的(innodb引擎)事务
    了解到事务是并发控制的基本单位(一致性),加锁是实现数据库并发控制的一个非常重要的技术。
    mysql的锁按级别可分为全局锁/表级锁/行锁

    全局锁

    顾名思义加锁使整个实例处于只读状态,此时写业务停摆(危害可想而知),典型使用场景为全库备份,锁定所有表,从而获取一致性视图

    //sessionA执行命令
    flush tables with read lock;
    //开启sessionB执行写操作,发现被阻塞
    update T set age=age+1 where id=1;
    //sessionA释放锁,sessionB得以继续执行
    unlock tables;
    

    建议使用Flush tables with read lock (FTWRL) 不使用 set global readonly=true 1.1.在有些系统中,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改 global 变量的方式影响面更大 1.2在异常处理机制上有差异。如果执行 FTWRL 命令之后由于客户端发生异常断开,那么 MySQL 会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高

    按锁的粒度分类

    全局锁的危害

    主库加锁,写业务停摆
    从库加锁,binlog无法同步写入,主从延迟
    优化:
    全局锁常用于备份,备份的目的是获取当前时间的一致性视图,那么有什么办法能帮助我们获取一致性视图呢?
    在事务的原理中我们提到过,可重复读和读已提交会在产生视图,那么我们在备份时开启事务不久行了,mysqldump(mysql自带)的参数–single-transaction就帮助

    mysqldump -h 127.0.0.1 
    -u root -p  
    --single-transaction 
    --databases learn  >/var/log/mysql/T.sql
    

    导入

    create database learn;
    use learn;
    source /var/log/mysql/T.sql
    

    事务是innodb引擎独有的,这也是为什么innodb越来多被使用的原因之一

    表级锁

    表级锁分为
    1.表锁;

    lock tables T read/write  只读/只写
    unlock tabels T
    

    在还没出现行锁前,表锁是常见的处理并发的方式
    2.元数据锁;(meta data lock MDL)
    MDL主要为解决DDL(data manipulation language)和DML(data definition languge)的冲突
    MDL不需要显示调用,访问时自动被添加,锁的是元数据(如表结构)

    //sessionA 
    begin;
    select * from T; //事务的特点是结束后再释放锁,因此MDL读锁没被释放
    //sessionB 表被加读锁(只读)且没被释放,此时读锁和写锁互相冲突(无法再加写锁),被阻塞
    alter table t add f int;
    //sessionA 
    commit;
    

    元数据表锁的危害

    事务不提交,就一直占用锁,无法修改表结构,因此尽量避免长事务(这里又提到了长事务的危害)或者查看infomation_schema库中的innodb_trx表中记录的事务并kill,

    行锁(innodb引擎特有)

    顾名思义,进行对应行操作时对行加对应的锁(写锁间会冲突)

    例如: 来自极客时间mysql45讲
    执行到事务的第二条语句时事务a持有行id=1和2的行锁,此时事务B被阻塞直到事务A提交释放锁。

    知道了这个设定我们也就知道,如果事务中锁多个行,要尽量把最可能造成锁冲突,最可能影响并发的锁尽量后放(减少锁住的时间)

    其实像这种思路,不但在这个地方需要注意,在并发编程中,我们在编写相应的并发程序时也要这么去考虑,就是尽量将容易引起并发度的操作就在合适的位置从而减少一次操作中锁住共享数据的时间,进而提升效率

    如以下场景:
    1从顾客 A 账户余额中扣除电影票价;
    2给影院 B 的账户余额增加这张电影票价;
    3记录一条交易日志。
    很明显 给影院添B加余额记录的不会只有一个顾客A,即2最可能冲突,要在事务中放到最后

    死锁

    死锁就是两个线程之间互相依赖对方释放要操作的行锁才能继续执行的情况 image.png

    事务A和B分别持有id=1和2的行锁,之后事务B依赖A释放id=1的行锁,事务A依赖B释放2的行锁,两线程互相等待

    死锁处理策略

    1.等待直到时间超过innodb_lock_wait_timeout的值(默认50s)
    2.发起死锁检测主动回滚其中一个事务使另一个得以进行

    innodb_deadlock_detect on

    死锁检测的额外负担 :每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,时间复杂度为O(n),随着并发量的提升消耗的CPU资源可想而知(因此控制并发有多重要)

    行锁是基于索引实现的

    上面操作都是基于主键id来完成行的确定的,
    没用到索引的行更新会锁住整个表
    实验

    create table B(
         id int(10) not null auto_increment,
         name varchar(10),
         age int(10),
         primary key(id),
        key age(age))engine=innodb;
    
    insert into B (name,age) values ('z3',1)  ('z2',1)  ('z1',1);
    
    
    //sessionA
    begin;
    update B set age=age+1 where name='z3';
    
    //sessionB 被阻塞
    update B set age=age+1 where name='z2';
    

    没用到索引时,innodb不知道要给哪个行加锁,要进行全表扫描,即加表锁

    共享锁和排他锁

    了解了上面的行锁机制我们再来看这些锁的分类
    共享锁:我读一行的时候你可以读改行,但是不能写

    select * from table_name where .....lock in share mode
    

    排他锁:我写的时候,你连读都不行

    select * from table_name where .....for update
    

    相关文章

      网友评论

          本文标题:简介mysql之锁

          本文链接:https://www.haomeiwen.com/subject/zmxsqltx.html