MySQL中锁机制实现原理

  JMM之Java中锁概念的分类总结 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中介绍了JMM中锁的分类,MySQL中锁又有很多不同的分类:

  1、从操作的粒度可分为表级锁、行级锁和页级锁。
    表级锁:每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAMInnoDBBDB 等存储引擎中。
    行级锁:每次操作锁住一行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB 存储引擎中。
    页级锁:每次锁定相邻的一组记录,锁定粒度界于表锁和行锁之间,开销和加锁时间界于表锁和行锁之间,并发度一般。应用在BDB 存储引擎中。
        

  2、从操作的类型可分为读锁和写锁。

    读锁(S锁):共享锁,针对同一份数据,多个读操作可以同时进行而不会互相影响。

    写锁(X锁):排他锁,当前写操作没有完成前,它会阻断其他写锁和读锁。

    IS锁、IX锁:意向读锁、意向写锁,属于表级锁,SX主要针对行级锁。在对表记录添加SX锁之前,会先对表添加ISIX锁。

  S锁:事务A对记录添加了S锁,可以对记录进行读操作,不能做修改,其他事务可以对该记录追加S锁,但是不能追加X锁,需要追加X锁,需要等记录的S锁全部释放。X锁:事务A对记录添加了X锁,可以对记录进行读和修改操作,其他事务不能对记录做读和修改操作。

  3、从操作的性能可分为乐观锁和悲观锁。

    乐观锁:一般的实现方式是对记录数据版本进行比对,在数据更新提交的时候才会进行冲突检测,如果发现冲突了,则提示错误信息。

    悲观锁:在对一条数据修改的时候,为了避免同时被其他人修改,在修改数据之前先锁定,再修改的控制方式。共享锁和排他锁是悲观锁的不同实现,但都属于悲观锁范畴。

  这些锁如何实现?下面介绍分别介绍

  一、行锁

    InnoDB引擎中,我们可以使用行锁和表锁,其中行锁又分为共享锁和排他锁。InnoDB行锁是通过对索引数据页上的记录加锁实现的,主要实现算法有 3 种:Record LockGap Lock Next-key Lock

      RecordLock锁:锁定单个行记录的锁。(记录锁,RCRR隔离级别都支持)

      GapLock锁:间隙锁,锁定索引记录间隙,确保索引记录的间隙不变。(范围锁,RR隔离级别支持)

      Next-key Lock 锁:记录锁和间隙锁组合,同时锁住数据,并且锁住数据前后范围。(记录锁+围锁,RR隔离级别支持)

     RR隔离级别,InnoDB对于记录加锁行为都是先采用Next-Key Lock,但是当SQL操作含有唯一索引时,Innodb会对Next-Key Lock进行优化,降级为RecordLock,仅锁住索引本身而非范围。

      1select ... from 语句:InnoDB引擎采用MVCC机制实现非阻塞读,所以对于普通的select语句,InnoDB不加锁。

      2select ... from lock in share mode语句:追加了共享锁,InnoDB会使用Next-Key Lock锁进行处理,如果扫描发现唯一索引,可以降级为RecordLock锁。

      3select ... from for update语句:追加了排他锁,InnoDB会使用Next-Key Lock锁进行处理,如果扫描发现唯一索引,可以降级为RecordLock锁。

      4update ... where 语句:InnoDB会使用Next-Key Lock锁进行处理,如果扫描发现唯一索引,可以降级为RecordLock锁。

      5delete ... where 语句:InnoDB会使用Next-Key Lock锁进行处理,如果扫描发现唯一索引,可以降级为RecordLock锁。

      6insert语句:InnoDB会在将要插入的那一行设置一个排他的RecordLock锁。

    举例说明:“update t1 set name=‘XX’ where id=10”操作为例,分析 InnoDB 对不同索引的加锁行为,以RR隔离级别为例。
    1、主键加锁:仅在id=10的主键索引记录上加X锁。

        

    2、唯一键加锁:现在唯一索引id上加X锁,然后在id=10的主键索引记录上加X锁。

        

    3、非唯一键加锁:对满足id=10的记录和主键分别加X锁,然后在(6,c)-(10,b)(10,b)-(10,d)(10,d)-(11,f)范围分别加Gap Lock

        

    4、无索引加锁:表里所有行和间隙都会加X锁。(当没有索引时,会导致全表锁定,因为InnoDB引擎锁机制是基于索引实现的记录锁定)。

        

   二、悲观锁

    悲观锁(Pessimistic Locking),是指在数据处理过程,将数据处于锁定状态,一般使用数据库的锁机制实现。从广义上来讲,前面提到的行锁、表锁、读锁、写锁、共享锁、排他锁等,这些都属于悲观锁范畴(与JMM中悲观锁定义表述上区别还是比较大的,本质上还是一致的——排他)。

     1)表级锁

      表级锁每次操作都锁住整张表,并发度最低。常用命令如下:

        手动增加表锁:  lock table 表名称 read|write,表名称2 read|write;

        

        查看表上加过的锁:show open tables;

        

        删除表锁:    unlock tables;

      表级读锁:当前表追加read锁,当前连接和其他的连接都可以读操作;但是当前连接增删改操作会报错,其他连接增删改会被阻塞。

      表级写锁:当前表追加write锁,当前连接可以对表做增删改查操作,其他连接对该表所有操作都被阻塞(包括查询)。

    即:表级读锁会阻塞写操作,但是不会阻塞读操作。而写锁则会把读和写操作都阻塞

    2)共享锁(行级锁-读锁)

      共享锁又称为读锁,简称S锁。共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。使用共享锁的方法是在select ... lock in share mode,只适用查询语句。

    即:事务使用了共享锁(读锁),只能读取,不能修改,修改操作被阻塞。

    3)排他锁(行级锁-写锁)

      排他锁又称为写锁,简称X锁。排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能对该行记录做其他操作,也不能获取该行的锁。使用排他锁的方法是在SQL末尾加上for updateinnodb引擎默认会在updatedelete语句加上for update。行级锁的实现其实是依靠其对应的索引,所以如果操作没用到索引的查询,那么会锁住全表记录。

    即:事务使用了排他锁(写锁),当前事务可以读取和修改,其他事务不能修改,也不能获取记录锁(select... for update)。如果查询没有使用到索引,将会锁住整个表记录。

  三、乐观锁

    乐观锁是相对于悲观锁而言的,它不是数据库提供的功能,需要开发者自己去实现。在数据库操作时,想法很乐观,认为这次的操作不会导致冲突,因此在数据库操作时并不做任何的特殊处理,即不加锁,而是在进行事务提交时再去判断是否有冲突了。

    乐观锁实现的关键点:冲突的检测。

    悲观锁和乐观锁都可以解决事务写写并发,在应用中可以根据并发处理能力选择区分,比如对并发率要求高的选择乐观锁;对于并发率要求低的可以选择悲观锁。

    乐观锁实现原理:

    1)使用版本字段(version

      先给数据表增加一个版本(version) 字段,每操作一次,将那条记录的版本号加 1version是用来查看被读的记录有无变化,作用是防止记录在业务处理期间被其他事务修改。

         

    2)使用时间戳(Timestamp

      与使用version版本字段相似,同样需要给在数据表增加一个字段,字段类型使用timestamp时间戳。也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则提交更新,否则就是版本冲突,取消操作。

     乐观锁利用版本号实现的一个案例:

        

     除了自己手动实现乐观锁之外,许多数据库访问框架也封装了乐观锁的实现,比如hibernate框架。MyBatis框架大家可以使用OptimisticLocker插件来扩展。

 

 

posted on 2022-02-23 10:04  池塘里洗澡的鸭子  阅读(1059)  评论(0编辑  收藏  举报