zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

Mysql锁详解

2023-03-14 22:44:53 时间

 前言

MySQL中不同的存储引擎支持不同的锁机制。比如

  • MyISAMMEMORY存储引擎采用的是表级锁(table-level locking);
  • BDB 存储引擎采用的是页面锁(page-level locking),但也支持表级锁;
  • InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。

从对数据操作的类型来看,可以分为:

  1. 读锁(共享锁):事务T对数据对象A加上读锁,则事务T只可以读A不能修改,其他事务也只能对数据A加读锁。
  2. 写锁(排它锁)事务T对数据对象A加上写锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。

一、表级锁、行级锁、页面锁的区别

    • 表级锁:偏向MyISM存储引擎,开销小,加锁块;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
    • 行级锁:偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率最低,并发度最高。 InnoDB与MyISAM最大不不同有两点,一是支持事务,                         二 是采用了行级锁
    • 页面锁开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

    表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用; 而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。

    二、MyISAM表锁

    2.1 查看表锁争用情况

      • Table_locks_immediate 表示立即释放表锁数
      • Table_locks_waited 表示需要等待的表锁数

      如果waited值比较大,说明存在严重的表锁争用情况。

      20210226160526448.png

      2.2 如何加表锁

      MyISAM 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATEDELETEINSERT 等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,如果需要显式加锁,必须同时取得所有涉及到表的锁。例如:

      Lock tables 表1 read local, 表2 read local;
      Select sum(total) from 表1;
      Select sum(total) from 表2;
      Unlock tables;

      image.gif2.3 MyISAM锁调度

      MyISAM 存储引擎的读锁和写锁是互斥的,读写操作是串行的。那么,一个进程请求某个 MyISAM 表的读锁,同时另一个进程也请求同一表的写锁,MySQL 如何处理呢?

      答案是写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插 到读锁请求之前!这是因为 MySQL 认为写请求一般比读请求要重要。这也正是 MyISAM 表 不太适合于有大量更新操作和查询操作应用的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。

      三、InnoDB锁问题

      InnoDB默认使用的是行锁:行锁开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度最高。

      可以通过检查 InnoDB_row_lock 状态变量来分析系统上的行锁的争夺情况

      20210227103438935.png

      除了读锁与写锁之外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

        • 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。
        • 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。

        示例SQL准备:

        CREATE TABLE demo_innodb_lock(
           id INT(11),
             name VARCHAR(16),
             sex VARCHAR(1)
        )ENGINE = INNODB DEFAULT CHARSET = utf8
        INSERT INTO demo_innodb_lock VALUES (1,'100','1');
        INSERT INTO demo_innodb_lock VALUES (1,'200','0');
        INSERT INTO demo_innodb_lock VALUES (2,'90','0');
        INSERT INTO demo_innodb_lock VALUES (3,'400','1');
        INSERT INTO demo_innodb_lock VALUES (4,'300','0');
        INSERT INTO demo_innodb_lock VALUES (5,'500','1');
        INSERT INTO demo_innodb_lock VALUES (6,'600','0');
        INSERT INTO demo_innodb_lock VALUES (7,'700','1');
        INSERT INTO demo_innodb_lock VALUES (8,'800','0');
        CREATE INDEX idx_id ON demo_innodb_lock(id);
        CREATE INDEX idx_name ON demo_innodb_lock(name);

        image.gif

        把自动提交关闭

        SET autocommit = 0;

        image.gif

        示例1:对于UPDATE、DELETE、INSERT语句,InnoDB会自动给涉及数据集加排他锁(X)   

        20210227140059274.png

        此时由于事务没有提交,再开一个窗口对同一行进行更新操作,会发现处于阻塞状态

        20210227140149371.png

        当第一个窗口中的事务执行commit;后,释放排它锁,如果第二个窗口中锁等待没有超时,则会立即执行

        示例2:行锁升级为表锁

        在示例准备中我们给id和name创建了索引,但是此时索引失效了,执行更新操作后不提交

        20210227143759549.png

        在第二个窗口执行更新并提交

        20210227144236185.png

        此时在第一个窗口中查询发现,id=4的数据并没有发生改变,问题是操作的不是同一行数据,说明此时已经升级为表锁了,原因就是索引失效。

        20210227144343887.png

        提交窗口一中的事务,释放表锁,再次查询

        20210227144534687.png

        3.1 InnoDB行锁的实现方式

          1. InnoDB行锁是通过给索引上的索引项加锁来实现的,InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
          2. 由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
          3. 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。
          4. 即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同执行计划的代价来决定的,如果 MySQL 认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。

          3.2 间隙锁Next-Key 锁)

          当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB 会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙

          (GAP)”,InnoDB 也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key 锁)。

          示例:

          20210227145149662.png

          此时去执行这样一条更新语句时,由于数据库中没有id=2的数据,那么就意味着存在间隙。

          20210227145327306.png

          窗口1中事务没提交的情况下,在窗口2插入一条ID=2的数据,发现插入不进去,证明了是存在间隙锁的;提交窗口1中的事务后,窗口2中SQL执行成功。

          20210227145555529.png

          InnoDB 使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,另外一方面,是为了满足其恢复和复制的需要。