阅读 2193

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

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

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

锁的操作粒度分类
  • 表锁: 偏向于读,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:最高级别,事务级,串行执行事务,即一个一个排队执行事务,这种级别下,所有的并发事务问题都会被避免,但是由于从并行操作变成了串行排队操作,效率大大降低。


已提交读与可重复读是实际开发中最经常使用到的两种事务隔离级别,这两者主要是通过MVCC(Multi Version Concurrency Control对并发事务问题的解决。

  • 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不支持。具体文章
文章分类
后端
文章标签