美文网首页
MySql InnoDB 锁(lock)

MySql InnoDB 锁(lock)

作者: jyhnp | 来源:发表于2020-06-01 16:07 被阅读0次

    文章是通过《Mysql技术内幕 InnoDB存储引擎》这本书概括的,主要是锁的这一章,包括共享锁、排它锁、意向锁、一致性非锁定读、一致性锁定读、自增长与锁这些知识点,稍微有点长,耐心看下来,定会有收获。

    测试表结构:

    DROP TABLE IF EXISTS t;
    CREATE TABLE t (
    id int(11) DEFAULT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

    BEGIN;
    INSERT INTO t VALUES (1);
    INSERT INTO t VALUES (2);
    COMMIT;

    开发多用户、数据库驱动应用时,最难的一个点就是:一方面要最大程度利用数据库的并发访问,另一方面还要确保每个用户能以一致的方式读取和修改数据,为此就有了锁机制。锁是为了支持资源进行并发访问,提供数据的完整性和一致性,Lock的对象是事务,用来锁定数据库中的对象,如表、页、行,事务仅在commit或rollback后释放。

    InnoDB存储引擎实现两种标准的行级锁:

    共享锁(S Lock):允许事务读一行数据
    排它锁(X Lock):允许事务删除或更新一行数据

    如果事务T1获取了行row1的共享锁,另一个事物T2同时也能获取row1的共享锁,这种情况成为锁兼容,但此时事务T3想获取row1的排它锁,则必须等事务T1、T2释放了row1的共享锁,这种称为锁不兼容。如图:


    Mysql技术内幕.png

    此外,InnoDB存储引擎支持多粒度(granular)锁定,这种锁允许事务在行级别和表级别上的锁同时存在,称之为意向锁。

    意向共享锁(IS Lock): 事务获取一张表中某几行的共享锁
    意向排它锁(IX Lock): 事务获取一张表中某几行的排它锁
    Mysql技术内幕.png

    举例来说:如果已经有事务对表1加了IS表锁,之后事务对记录row1操作在表1上加IX锁,由于锁不兼容,需要等表1的IS锁操作完成后。


    Mysql技术内幕.png

    一致性非锁定读:

    如果读取的行正在执行DELETE或UPDATE操作(X锁),这时读操作不会因此去等待行上的锁释放, Innodb存储引擎会去读取一个快照数据。快照数据是指该行之前版本的数据,读快照数据不需要上锁,因为没有事务要对历史的数据进行修改。
    如图:


    Mysql技术内幕.png

    在事务隔离级别READ COMMITED(读已提交)和REPEATBLE READ(可重复读,InnoDB默认)下使用一致性非锁定读;READ COMMIT事务隔离级别下,非一致性锁读总是读取最新的快照;REPEATABLE READ隔离级别会去读取锁定行的上一次快照。

    来看一个例子说明两种隔离级别不同的一致性非锁读的效果,打开两个数据库命令行(A和B):
    Session A:

    mysql> use test;
    Database changed

    mysql> set session transaction isolation level repeatable read;
    Query OK, 0 rows affected (0.00 sec)

    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)

    mysql> select * from t where id = 1;
    +----+
    | id |
    +----+
    | 1 |
    +----+
    1 row in set (0.00 sec)

    会话A设置事务隔离级别为REPEATABLE READ,并且执行命令Begin开启了一个事务,查询了t表id等于1的记录,但事务并没有结束,与此同时在会话B继续操作

    session B:

    mysql> begin;
    Query OK, 0 rows affected (0.01 sec)

    mysql> update t set id = 10 where id = 1;
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1 Changed: 1 Warnings: 0

    mysql>
    mysql> commit;
    Query OK, 0 rows affected (0.00 sec)

    会话B开启一个事物,修改id等于1的记录,commit提交会话B事务,再去会话A查看

    Session A:

    mysql> select * from t where id = 1;
    +----+
    | id |
    +----+
    | 1 |
    +----+
    1 row in set (0.00 sec)
    REPEATABLE READ隔离级别下,一致性非锁定读查询的是当前事务锁定行的上一次快照,并不是最新的快照,我们再来看看READ COMMIT事务隔离级别的一致性非锁读效果

    Session A:

    mysql> set session transaction isolation level read committed;
    Query OK, 0 rows affected (0.00 sec)

    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)

    mysql> select * from t where id = 2;
    +----+
    | id |
    +----+
    | 2 |
    +----+
    1 row in set (0.00 sec)

    会话A设置事务隔离级别为COMMIT READ,查询id为2的记录,切到会话B

    Session B:

    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)

    mysql> update t set id = 20 where id = 2;
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1 Changed: 1 Warnings: 0

    mysql> commit;
    Query OK, 0 rows affected (0.00 sec)

    会话B开启一个事务修改id为2的值为20,并提交事务,再去会话A

    Session A:

    mysql> select * from t where id = 2;
    Empty set

    mysql> select * from t where id = 20;
    +----+
    | id |
    +----+
    | 20 |
    +----+
    1 row in set (0.00 sec)

    效果已经出来了,COMMIT READ隔离级别下,一致性非锁定读获取的是最新的快照,之前id为2的快照不是最新的了,所以查询不到了,id为20的为最新快照。从数据库理论来看,其违反了事务ACID的I特性,及隔离性。

    一致性锁定读

    Innodb默认配置下,使用REPEATABLE READ隔离级别,Select操作会使用一致性非锁定读。但是在某些情况下,需要加锁保证数据逻辑的一致性,就要用到一致性锁定读。

    InnoDB存储引擎对于SELECT语句支持两种一致性锁定读(locking read)操作:
    1.SELECT *** FOR UPDATE(排它锁)
    2.SELECT *** LOCK IN SHARE MODE(共享锁)

    注意:使用FOR UPDATE时查询条件有索引列(主键也行,主键列默认有索引)的话,锁的是当前行,否则锁全表,update语句的where条件也是一个道理

    两种操作必须在事务中,当事务提交了,锁也就释放了,因此在使用完两种锁定SELECT语句时,要使用BEGIN,START TRANSACTION或者AUTOCOMMIT=0来开启事务。

    自增长与锁

    InnoDB存储引擎中,每个含有自增的表进行插入操作时都会有一个自增长计数器(auto-increment counter),当对含有自增长计数器表进行插入时,计数器会被初始化,执行如下语句得到计数器的值:

    SELECT MAX(auto_inc_col) from t FOR UPDATE

    这种自增实际采用表锁机制,通过FOR UPDATE(X锁)排它锁来保证唯一,为了提高性能,锁不是在一个事务完成后释放,而是在完成自增长插入后的SQL后立即释放,这种实现方式称为AUTO-INC Locking。

    由于AUTO-INC Locking的并发性能较差,事务必须等待前一个插入的完成(虽然不用等待事务的完成),对于大量数据插入会影响插入性能。从MySQL5.1.22版本开始,InnoDB存储引擎提供一种轻量级互斥量(mutex)的自增长机制,对内存中的计数器进行累加操作,大大提升列并发插入的性能。此版本开始,InnoDB提供了一个参数innodb_autoinc_lock_mode来控制自增长模式,该参数默认值是1。如图所示插入类型和innodb_autoinc_lock_mode值说明:

    Mysql技术内幕.png Mysql技术内.png

    死锁

    死锁的概念:死锁是指两个或两个一上的事务在执行过程中,因争夺资源而造成
    的一种互相等待的现象。

    解决死锁最简单的就是超时机制,当两个事务互相等待时,当一个等待时间超过了阈值,此事务就进行回滚,另一个事务就能继续进行了,超时机制虽然简单,若超时的事务占比较大,比如更新了很多行,占用了较多的undo log,这个事务回滚的时间相对另一个事务所占的时间可能会很多。当前数据库普遍采用wait-for graph(等待图)的方式 进行死锁检测。

    Mysql技术内幕.png

    wait-for graph是一种主动的死锁监测机制,在每个事务请求锁并发等待时都会判断是否存在回路,若存在回路则有死锁(如图t1和t2事务存在回路),InnoDB 存储引擎选择回滚undo log量最小的事务。

    死锁示例(注意查询条件列要带有索引,不然会锁全表):


    Mysql技术内幕.png

    结束语:如果有写的不好或者不太懂得地方可以在下方评论,感觉不错可以点个赞哦。

    相关文章

      网友评论

          本文标题:MySql InnoDB 锁(lock)

          本文链接:https://www.haomeiwen.com/subject/bjvjzhtx.html