Mysql 中的锁(行锁\表锁)

 有人说我思路有些乱没有条理,在这希望能用比较'安稳'的思路描述一下我理解的Mysql锁,欢迎大家指正。

前言:
    ACID:本来不想再说了,就是想强调一下:这四个属性(原子、一致、隔离、持久)是事物的属性,针对同一个事物对外是原子的,执行前后一致,相对其他事物不可见,完成后会持久存在。

一致性的问题:

  1. 更新覆盖:两个人同时修改同一条记录,相互修改的被覆盖。
  2. 脏读:不能确定会生效的数据被其他的事物读取。
  3. 不可重复读:同一个事物中两次读取同一个集合的数据不一致(相同数据集合被修改)。
  4. 幻读:事物内部读取的数据与已经持久化的数据不一致(相同条件又有新纪录产生)。

事物隔离级别:

  1. RU读未提交:未提交的数据对其他事物可见
  2. RC读提交:提交的数据对其他事物可见
  3. RR可重复读:同一个事物内部读取同一个集合会得到相同的结果
  4. SA串行:读写串行操作

事物隔离级别的出现是解决上面的问题:提交读->脏读;可重复读->不可重复读
(这里很多人都说“可重复读”不能避免“幻读”的问题么。MySQL又是如何解决的呢?
通过在“可重复读”的隔离级别中,显示声明可以读到最新的已经提交的数据。
共享锁: SELECT ... LOCK IN SHARE MODE    排它锁: SELECT ... FOR UPDATE 排它锁会导致写等待,不用索引会锁表)

MySQL中默认是用 “RR可重复读”,这种可重复读的实现有两种。1.当前环境的快照副本;2.多版本控制(mysql实现)。
MVCC(MultiVersion Concurrency Control)多版本控制:
        通过隐藏字段区分当前记录的版本;创建版本,删除版本控制数据有效性。
        更新相当于新增然后标记原来的删除版本;检索的时候会通过当前版本判断创建版本小于当前版本且没有或删除版本大于当前的事物。
        提交读、可重复读都使用的MVCC只不过查询条件不同。

MySQL锁

相对于其他的数据库而言,MySQL的锁机制比较简单,最显著的特点就是不同的存储引擎支持不同的锁机制。根据不同的存储引擎,MySQL中锁的特性可以大致归纳如下:

  行锁 表锁 页锁
MyISAM    
BDB  
InnoDB  

开销、加锁速度、死锁、粒度、并发性能

  • 表锁: 开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低
  • 行锁: 开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高
  • 页锁: 开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般

MyISAM的表锁:
        
读锁、写锁 互斥;写锁、写锁 互斥。
    并发控制:
        通过修改 concurrent_insert 这个属性 0,1,2;
        当concurrent_insert 为0时,不允许并发插入。
        当concurrent_insert 为1时,如果MyISAM允许在一个读表的同时,另一个进程从表尾插入记录(默认)。
        当concurrent_insert 为2时,无论MyISAM表中有没有空洞(中间记录被删除),都允许在表尾并发插入记录。
    等待优先级:
        在表的等待队列中默认写优先级高,也就意味着频繁写的时候,读是一直阻塞的
        通过以下方式调整优先级:
            show variables like "%low_priority%";
            set low_priority_updates = 1;
    死锁:
        MyISAM不会出现死锁是因为只有表锁,在 select 会自动加读锁,insert、update、delete会自动加写锁。
        产生死锁的原因:是因为逐步去获取锁而不是预先获取所要操作的所有锁,MyISAM只有表锁所以不会产生死锁。
        Lock table 相关语法这里不做解释,推荐一个地址把:https://www.cnblogs.com/kerrycode/p/6991502.html
        (需要说的一点 锁表跟事物并没有关系,事物提交锁也需要声明取消,获取锁期间同一回话只能操作响应的表;
        再次获取锁上次获取的锁将自动释放
,这也是不会产生死锁的原因。)

InnoDB的锁:
       
两种引擎对比:后者支持事物和行级锁
        检查行锁的争用情况:show status like 'innodb_row_lock%' 字段含义自己查一下吧。
    行锁:
       
共享锁:SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
        排他锁:SELECT * FROM table_name WHERE ... FOR UPDATE
        (意向锁不是我们人为控制的这里就不提了...)
        共享锁、排他锁 会在事物里一直存在一直保持到事物提交,或者死锁检查被回滚(排它锁占用记录少的事物将被回滚,死锁相关的下面在聊);
    行锁的实现:
        行锁是通过索引实现的,因此执行计划中没用到索引的SQL将会锁表,执行计划用到了索引不会锁表?
        不是的,执行计划只是模拟像其中 key_len 只是预估会用到索引的长度,实际执行还会选取最优执行方案(表的量级比较小锁表的效率比较高,也不会用索引的行锁)。
        1.由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
        2.当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用哪种索引InnoDB都会使用行锁。
        3.where 条件中比较的类型跟声明不一致的时候,会进行数据转换,这时候不会用索引。
    间隙锁:
        有叫“Gap锁”、“Next-Key锁”,通过查询索引的范围加锁:Select * from  emp where empid > 100 for update;
        间隙锁的目的:一方面是为了防止幻读,另外一方面,是为了满足其恢复和复制的需要(跟BinLog有关)。
        innodb_locks_unsafe_for_binlog设置为“on” 可以强制使用多版本数据一致性读,代价无法用binlog恢复复制数据
        这种加锁机制会阻塞符合条件范围内键值的并发插入,会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件
    锁优化:
       1、优化事务逻辑使用Read Commited隔离级别,对于需要更高隔离级别的事务,可以通过在程序中执行SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ或SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE动态改变隔离级别的方式满足需求。
         2、在合理的情况下,显示声明使用表锁。
         (1)需要更新大表的大量数据,使用行锁不仅执行效率低,而且可能造成锁等待和锁冲突;可以考虑使用表锁来提高速度。
         (2)事务涉及多个表,关系复杂,可能引起死锁,造成大量事务回滚;也可以考虑一次性锁定事务涉及的表,减小开销。
         3、在InnoDB下,使用表锁要注意以下两点:
         (1)使用LOCK TABLES虽然可以给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层──MySQL Server负责的,仅当autocommit=0、innodb_table_locks=1(默认设置)时,InnoDB层才能知道MySQL加的表锁,MySQL Server也才能感知InnoDB加的行锁,这种情况下,InnoDB才能自动识别涉及表级锁的死锁;否则,InnoDB将无法自动检测并处理这种死锁。
         (2)在用 LOCK TABLES对InnoDB表加锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁;事务结束前,不要用UNLOCK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务COMMIT或ROLLBACK并不能释放用LOCK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁。
    死锁:
        死锁产生的原因?任何系统都是一样的,分批次请求不同的锁,造成锁循环依赖;解决方法,一种是同一时间获取要操作的所有锁,不再后续申请。另一种是按照某种顺序访问数据减低锁冲突的几率。
        InnoDB一般能自动检测到死锁,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务;但在涉及外部锁或涉及表锁的情况下,不能自动检测到,需要设置锁等待超时 innodb_lock_wait_timeout来解决。这个参数是通过时间判断是否发生死锁,同样也会回滚执行慢的事物,所以需要设置合理的超时时间。
       如何诊断死锁:show engine innodb status \G  
                                show engine innodb mutex \G

不是我不举例子哦,真的是这个文档编辑太难搞了,但是每个这里描述的知识点都是我一个一个亲自测试过的。
这里鸣谢前人的博客:

       https://www.cnblogs.com/chenqionghe/p/4845693.html
       https://blog.csdn.net/mysteryhaohao/article/details/51669741(重点推荐)
       https://blog.csdn.net/JIESA/article/details/51317164
       https://blog.csdn.net/v123411739/article/details/39298127
       https://blog.csdn.net/ashic/article/details/53735537(这个意见保留)


这里说出我自己的一点疑惑,幻读跟不可重复读比较难区分,看了这么多博客和官网在含以上还是有点模糊。
“幻读是得到的结果和上一次统计的不一致” 其实是逻辑上的认为两次查询中数据没有变化的情况。
“幻读又提现在我明明刚才查询这个记录不存在,为什么我插入的时候又存在了?”因为隔离级别没有读到最新的数据。
解决这两个问题的方式:逻辑上的一致性的表同时加锁保证强一致性,通过共享和排他锁读取最新的记录再操作(也是加锁)。
mysql 是如何解决幻读的?有人跟我说通过MVCC啊,其实我认为不是,MVCC是提交读和可重复读两种隔离级别的实现, 是策略决定当前查询的需要最新的事物数据 还是 小于当前事物 的数据。为什么串行可以解决幻读?加锁啊~!(个人意见 欢迎交流

猜你喜欢

转载自blog.csdn.net/Joker_honey/article/details/81391309