likes
comments
collection
share

说一说 MySQL的锁机制(行锁、表锁、间隙锁、Next-Key Lock)

作者站长头像
站长
· 阅读数 56
锁的操作类型分类
  • 读锁:共享锁,多个读操作可以对同一份数据同时进行而不会互相影响。

  • 写锁:排他锁,在写操作未完成之前,会阻止其他的写锁与读锁。

锁的操作粒度分类
  • 表锁: 偏向于读,MyiSAM
  • 行锁:偏向于写,InnoDB

MyiSAM

  • 在进行SELECT 操作前,MyiSAM会给涉及到的表加读锁。这个时候其他Session可以正常对未加锁的表进行操作。但是对加了读锁的表,只能对其进行查询(共享锁),对其修改则会阻塞,等待至表解锁后,才会生效。

  • Session1 Session2
    给TABLE_A加读锁 无操作
    可以对TABLE_A进行查询,不能对TABLE_A进行修改 可以对TABLE_A进行查询,不能对TABLE_A进行修改
    查询修改 TABLE_B 报错 查询修改 TABLE_B正常
    修改TABLE_A报错 修改TABLE_A阻塞
    Unlock tables;解锁 TABLE_A被修改
  • 在进行写操作前,MyiSAM会给涉及到的表加写锁。这个时候其他Session可以正常对未加锁的表进行操作。但是对加了写锁的表,对其进行读或写,都会阻塞,直至写锁释放后,才会进行相应操作。

    • Session1 Session2
      给TABLE_A加写锁 无操作
      可以对TABLE_A进行修改,不能对TABLE_A进行查询 不能对TABLE_A进行查询,修改
      不能对TABLE_B进行操作 可以对TABLE_B进行操作
      查询TABLE_A报错 查询或者修改TABLE_A阻塞
      Unlock tables;解锁 TABLE_A被查询出结果或被修改
  • MyiSAM的读写锁调度是写优先,这也是MyiSAM不适合作为以写为主的引擎。因为写锁后,其他线程不能进行任何操作,大量的写入操作会使得查询很难得到锁,从而造成永久堵塞。

IMPORTANT

通俗地讲:

  • 读锁就是:我要备份,后面来的先别乱动(修改)东西;

  • 写锁就是:我要修改,后面来的别急,排队(不管你是要读还是要改,都得等我这次改完)。

InnoDB

InnoDB的锁机制为行锁,行锁支持事务。

事务的ACID属性:
  • A 原子性(Atomic):指一个事务为最小单位不可再往下划分,要么全执行,要么都不执行。
  • C 一致性(Consistency):指数据在事务执行之前是一致的,执行之后也是一致的,即:不会破坏数据库的完整性(完整性:逻辑与业务上的符合逻辑即为完整性)。
  • I 隔离性(Isolation):指事务在执行的过程不会被外界的其他事务或数据库操作干扰,数事务执行过程中的中间态对外不可见。
  • D 持久性(Durability):事务执行完成之后对数据库的影响是持久的,不会回滚。
并发事务带来的问题:
  • 更新丢失:多个事务同时更新同一行的数据,并且不知道其他事务的存在,导致最后有的更新失效(丢失)了。

  • 脏读:事务在更新数据的过程中(已经修改了数据尚未提交),去读取数据,读到了中间态的数据,这些数据不符合一致性要求,即为脏读。

  • 不可重复读:在一次的事务操作中,对某一数据进行了两次(及以上)的读操作,此时有另一个事务在两次读操作的中间修改了数据,造成了两次读取的数据不同,即不可以重复读(不然数据会不一样)。

  • 幻读:在一次的事务操作中,先读取了几行数据后,另一个事务又增加或删除了数据,在此之后,此事务又去读取数据,发现数据凭空生成或消失,跟幻觉一样,即幻读。

总结:更新丢失为多个事务几乎同时修改数据出现的问题;脏读为一个事务在修改数据,另一个事务读取(SELECT)事务出现的问题;不可重复读为一个事务在查询数据,中间有另一个事务修改(UPDATE)了数据的问题;幻读为一个事务在查询数据,另一个事务在插入或删除(INSERT or DELETE)数据的问题。

事务的隔离级别:
  • 未提交读Read Uncommitted:最低级别,只能避免不读到物理修改数据过程的数据,数据的逻辑修改的中间态依然存在,破坏了数据一致性,上述问题同时存在。
  • 已提交读Read Committed:语句级,保证了语句的原子性,只能读到已经提交的内容,但是在修改数据过程中并没有加锁,为什么只会读到已经提交的数据内容呢?,这是使用了“快照读”的方式优化了,使得我们在修改数据的同时,查询不会被阻塞,可以完成高并发的查询,大大提高了效率;但是因为修改的过程中没有加锁,则会出现两次查询数据的过程中,数据中间被其他事务修改或者增添数据了,造成不可重复读和幻读。
  • 可重复读Repeated Read:事务级,MySQL默认隔离级别,从名字看就知道了,避免了不可重复读的问题。普通的查询同样是通过“快照读”的方式,来避免脏读的出现,在此基础上又加入了一个在事务开启的同时,不能对涉及到的数据行进行修改,从而避免了“同一次事务中读到的数据不一致”的不可重复读的问题,但是没有避免幻读(在InnoDB中解决了幻读「间隙锁加行锁)。
  • 可序列号Serializable:最高级别,事务级,串行执行事务,即一个一个排队执行事务,这种级别下,所有的并发事务问题都会被避免,但是由于从并行操作变成了串行排队操作,效率大大降低。
  • Read Commited的做法是在事务的每一条SQL语句执行前生成一个快照,此时其他并发事务去读取这个数据时,避免了脏读的出现。
  • Repeated Read的做法是在事务的第一次查询前生成一个快照,之后在这一次事务的读取过程中,都去读取这一次快照,从而避免了脏读和不可重复读。

总结锁与隔离级别与并发问题的关系:

在默认的隔离级别下,我们在对某几行数据进行修改或者查询的时候,只会锁住这几行数据不被修改,从而保证避免了不可重复读的出现;而我们即使对整张表所有行都进行操作了,那也是锁住了这张表的所有行,而不是锁住这张表,不能阻止表插入新的行,从而依然会出现幻读的情况(间隙锁+行锁的Next-Key Lock解决了此问题),而最高的隔离级别则是通过将事务串行化,我们在执行查询事务的同时是不可能有其他事务来插入数据的,从而避免了幻读的出现。

间隙锁(Gap Lock)

当我们在查询语句时,条件为范围查询时,InnoDB不管这个区间是否有数据,都会将其锁住,向这个区间的“间隙”(不存在的行)插入或删除数据都会阻塞。

Next-Key Lock

Next-Key Lock = Record Lock + Gap Lock InnoDB在默认隔离级别(Repeated Read)下,使用Next-Key Lock的方案解决了幻读的问题。

即在进行范围性的SELECT时,我们先对已经存在的Records加上Record Lock,再对此区间的间隙加上Gap Lock,从而解决了幻读的问题。

索引失效会使得行锁变成表锁

原因:索引失效导致的全表扫描,使得从行锁->表锁。

如何锁定一行

select … for update 语句

优化建议

  • 尽可能让所有数据的检索都通过索引完成,避免无索引行锁升级为表锁。
  • 合理设计索引,尽量缩小锁的访问。
  • 尽可能减少检索条件 避免间隙锁的危害。
  • 尽量控制事务大小,减少锁定资源量和时间长度。
  • 尽可能低级别事务隔离。
InnoDB与MyiSAM最大的不同点:是InnoDB支持事务、行锁、外键,MyiSAM不支持。具体文章
转载自:https://juejin.cn/post/6847902217081618440
评论
请登录