• MySQL 5.7 InnoDB锁


    简介
    参考https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-gap-locks。
    InnoDB引擎实现了标准的行级别锁(S和X)。InnoDB引擎加锁原则遵循二段锁协议,即事务分为两个阶段,事务开始后进入加锁阶段,事务commit或者rollback就进入解锁阶段。InnoDB引擎下锁的影响因素很多,隔离级别不同,是否使用索引等都会产生不同的锁结果。
     
    查看锁和事务
    当出现ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction,要解决是一件麻烦的事情。特别是当一个SQL执行完了,但未COMMIT,后面的SQL想要执行就是被锁,超时后结束,DBA光从数据库无法着手找出源头是哪个SQL锁住了。有时候看看 show engine innodb status, 并结合 show full processlist 能暂时解决问题,但一直不能精确定位。在5.5中,information_schema 库中增加了三个关于锁的表(MEMORY引擎)。
     
    INNODB_LOCKS
    提供有关InnoDB事务已请求但尚未获得的以及事务正在阻塞另一个事务的锁的信息。
    lock_id
    InnoDB内部的唯一锁ID
    lock_trx_id
    拥有这个锁的事务ID
    lock_mode
    请求的锁,S, X, IS, IX等
    lock_type
    锁类型,RECORD或者TABLE 
    lock_table
    被锁的表或包含被锁记录的表
    lock_index
    被锁的索引,不是行级锁时为NULL
    lock_space
    被锁的表空间号,不是行级锁时为NULL
    lock_page
    被锁的页号,不是行级锁时为NULL
    lock_rec
    被锁的Heap号,不是行级锁时为NULL
    lock_data
    被锁的记录的主键,不是行级锁时为NULL
     
    INNODB_LOCK_WAITS
    当前等待的锁。
    requesting_trx_id
    正在请求的、受阻的事务ID
    requested_lock_id
    事务正在等待的锁ID
    blocking_trx_id
    阻塞其他事务的事务ID
    blocking_lock_id 
    阻塞其他事务的事务持有的锁ID
     
    INNODB_TRX
    当前事务。
    trx_id 
    InnoDB内部的唯一事务ID
    trx_state
    事务状态,RUNNING, LOCK WAIT等
    trx_started
    事务开始时间
    trx_requested_lock_id
    事务正在等待的锁ID
    trx_wait_started
    事务开始等待的时间
    trx_weight
    事务的权重,当发生死锁回滚的时候,优先选择该值最小的进行回滚
    trx_mysql_thread_id
    事务线程ID,即show full processlist中的ID
    trx_query
    执行的SQL语句
    trx_operation_state
    事务当前操作状态
    trx_tables_in_use
    执行当前SQL时有多少个表被使用
    trx_tables_locked
    执行当前SQL时有多少个表有行锁
    trx_lock_structs
    事务保留的锁的数量
    trx_lock_memory_bytes
    事务锁占据的内存大小(B)
    trx_rows_locked
    事务锁定的大概行数
    trx_rows_modified
    事务修改和插入的行数
    trx_concurrency_tickets
    即innodb_concurrency_tickets系统变量
    trx_isolation_level
    事务隔离级别
    trx_unique_checks
    是否唯一性检查
    trx_foreign_key_checks
    是否外键检查
    trx_last_foreign_key_error
    最后的外键错误详细信息
    trx_adaptive_hash_latched
     
    trx_adaptive_hash_timeout
     
    trx_is_read_only
    1表示事务是只读的
    trx_autocommit_non_locking
    1表示事务是不包含FOR UPDATE或者LOCK IN SHARE MODE语句,并且autocommit是enable的
     
    强行解锁
    1、
    SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
    获取到blocking_trx_id
     
    2、
    SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
    查找trx_id和上面获取到的blocking_trx_id一样的记录,获取这条记录的trx_mysql_thread_id
     
    3、
    kill 上面获取到的trx_mysql_thread_id,这样就把阻塞其他事务的事务线程杀掉了。
     
    共享锁和排它锁
    共享锁(S lock):允许持有锁的事务读取一行,select语句后加lock in share mode。
    排它锁(X lock):允许持有锁的事务更新或删除一行,select语句后加for update。
    共享锁和排他锁都是锁的行记录。当一个事务获取了行r的共享锁,那么另外一个事务也可以立即获取行r的共享锁,因为读取并未改变行r的数据,这种情况就是锁兼容。
    但是如果有事务想获得行r的排它锁,则它必须等待其他事务释放行r上的共享锁,这种情况就是锁不兼容。二者兼容性如下表格所示:
     
    X
    S
    X
    冲突
    冲突
    S
    冲突
    兼容
    对于select 语句,InnoDB不会加任何锁,也就是可以多个并发去进行select的操作,不会有任何的锁冲突,因为根本没有锁。
    对于insert,update,delete操作,InnoDB会自动给涉及到的数据加排他锁,只有查询select需要我们手动设置排他锁。
     
    意向锁
    InnoDB支持多粒度锁定,允许行锁和表锁共存。例如 LOCK TABLES ... WRITE 这样的语句会获取指定表的排它锁。为了在多个粒度级别上实现锁定,InnoDB使用了意向锁。意向锁是表级锁,指示事务稍后事务对表的行需要的锁的类型(共享锁或排它锁)。意向锁有两种:
    意向共享锁(IS):指示事务打算在表中的单个行上设置共享锁。如果需要对记录A加共享锁,那么此时InnoDB会先找到这张表,对该表加意向共享锁之后,再对记录A添加共享锁。
    意向排它锁(IX):指示事务打算在表中的单个行上设置排他锁。如果需要对记录A加排他锁,那么此时InnoDB会先找到这张表,对该表加意向排他锁之后,再对记录A添加排他锁。
    例如 SELECT ... LOCK IN SHARE MODE 设置IS,SELECT ... FOR UPDATE 设置IX。这两种意向锁都是表锁,都是系统自动添加和自动释放的,整个过程无需人工干预。
    意图锁定协议如下:
    在事务可以获取表中某一行上的共享锁之前,它必须首先获取表上的IS锁或比IS锁更强的锁;
    在事务可以获取表中某一行上的排他锁之前,它必须首先获取表上的IX锁。
    表级锁类型兼容性总结如下:
     
    X
    IX
    S
    IS
    X
    冲突
    冲突
    冲突
    冲突
    IX
    冲突
    兼容
    冲突
    兼容
    S
    冲突
    冲突
    兼容
    兼容
    IS
    冲突
    兼容
    兼容
    兼容
    如果请求事务与现有锁兼容,则授予该事务锁,但如果与现有锁冲突,则不授予该事务锁。事务等待冲突的现有锁被释放。如果锁请求与现有锁冲突,并且由于会导致死锁而无法被授予,则会发生错误。意向锁不会阻塞除全表锁请求(例如 LOCK TABLES ... WRITE)之外的任何东西,意向锁定的主要目的是显示某人锁定了一行,或者准备锁定表中的一行。
    因为表锁覆盖了行锁的数据,所以表锁和行锁会产生冲突。比如A事务申请表锁,B事务申请行级锁,或者A事务申请行级锁,B事务申请表锁。这时候B事务的申请是需要被阻塞的。那么怎么判断B事务该阻塞呢?遍历表的每一行看看是否有行级锁吗?这样效率非常差。这时候就引入了意向锁。在申请行锁前,数据库自动为我们申请了对应的意向锁,因为意向锁是表锁,这时候如果再申请表锁,就自然会阻塞了。意向锁之间互相兼容,表锁(ALTER TABLE, DROP TABLE, LOCK TABLES等)会和意向锁冲突。
    执行 SHOW ENGINE INNODB STATUSG,如果有意向锁,就可以看到类似下面的信息:
    TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
     
    记录锁
    记录锁是索引记录上的锁,记录锁总是锁定索引记录,即使表没有定义索引。对于这种情况,InnoDB创建一个隐藏的聚簇索引,并使用这个索引来锁定记录。
    对主键加锁,加在聚簇索引上;
    对二级索引加锁,加在二级索引+聚簇索引上;
    对无索引列加锁,加在聚簇索引上。
    执行 SHOW ENGINE INNODB STATUSG,如果有记录锁,就可以看到类似下面的信息:
    RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
    trx id 10078 lock_mode X locks rec but not gap
    Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
    0: len 4; hex 8000000a; asc ;;
    1: len 6; hex 00000000274f; asc 'O;;
    2: len 7; hex b60000019d0110; asc ;;
     
    取消自动提交
    InnoDB下事务一旦提交或者回滚,就会自动释放事务中的锁。变量autocommit=1开启自动提交,autocommit=0关闭自动提交,默认autocommit=1。在自动提交模式下,每执行一句sql,就自动提交事务,锁也会立即释放。这种情况下无法手动控制事务的提交以及锁的释放时间。
    因此,我们会在进行锁的相关操作之前,先执行(set autocommit=0)或者start transaction以关闭自动提交模式,开启手动提交模式,这样事务中的每一行sql执行完成后,锁一直不会释放,直到我们手动提交或者回滚事务,锁才会释放。
     
    间隙锁(GAP)
    间隙锁是对索引记录之间的间隙的锁,或对第一个索引记录之前或最后一个索引记录之后的间隙的锁,不包含索引记录本身。间隙锁是性能和并发性之间权衡的一部分,用于某些事务隔离级别,而不是其他事务隔离级别。
    对于使用惟一索引锁定行以搜索惟一行的语句,不需要间隙锁(这并不包括搜索条件只包含一个复合多列惟一索引的某些列的情况,在这种情况下,确实会发生间隙锁)。如果列没有索引,或者索引不是惟一的,那么语句将产生间隙锁。
    必须在REPEATABLE-READ级别才可以使用间隙锁。可以显式禁用间隙锁,如果将事务隔离级别更改为READ COMMITTED或启用innodb_locks_unsafe_for_binlog系统变量(现在已经不推荐使用该变量),就会发生这种情况。在这种情况下,对搜索和索引扫描禁用间隙锁,只用于外键约束检查和重复键检查。
    如何确定间隙锁的区间?根据检索索引记录C向左寻找最靠近C的索引记录值A,作为左区间,向右寻找最靠近C的索引记录值B作为右区间,即锁定的间隙为(A,B),A<C<B,锁定的间隙除了索引记录C之间还包括第一个索引记录A之前和最后一个索引记录B之后。索引记录相同的值是根据主键升序排序的。
    间隙锁的目的是为了防止幻读,其主要通过两个方面实现这个目的:
    1、防止两个索引区间内有新数据被插入。
    2、防止现有数据更新成两个索引区间内的数据。
     
    Next-Key锁
    Next-Key锁是索引记录上的记录锁(S或X)和索引记录前的间隙上的间隙上的间隙锁的组合。一般来说MySQL用的都是Next-Key锁。Next-Key锁是左开右闭的区间,例如索引包含10、11、13、20,那么可能的Next-Key锁如下:
    (负无穷大, 10]
    (10, 11]
    (11, 13]
    (13, 20]
    (20, 正无穷大)
     
    测试环境和数据
    MySQL版本为5.7.21,部署于CentOS X86_64上,采用默认配置。
     
    创建表
    CREATE TABLE `test` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `age` int(11) NOT NULL,
    PRIMARY KEY (`id`),
    KEY `idx_age` (`age`)
    ) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
     
     
    数据
    INSERT INTO `test` (`id`,`age`) VALUES (6,1);
    INSERT INTO `test` (`id`,`age`) VALUES (8,5);
    INSERT INTO `test` (`id`,`age`) VALUES (10,8);
    INSERT INTO `test` (`id`,`age`) VALUES (12,12);
    INSERT INTO `test` (`id`,`age`) VALUES (14,14);
    INSERT INTO `test` (`id`,`age`) VALUES (16,14);
    INSERT INTO `test` (`id`,`age`) VALUES (18,18);
    INSERT INTO `test` (`id`,`age`) VALUES (20,20);
     
    +----+-----+
    | id | age |
    +----+-----+
    | 6 | 1 |
    | 8 | 5 |
    | 10 | 8 |
    | 12 | 12 |
    | 14 | 14 |
    | 16 | 14 |
    | 18 | 18 |
    | 20 | 20 |
    +----+-----+
     
    示例
    启动三个会话,会话1和会话2均取消自动提交,会话3查看锁状态。
    SELECT * FROM test where age=14 for update 语句的间隙锁范围(id,age)包括(12,12)到(18,18)之间的间隙,同时还会对记录age=14加排他锁。
     
    一、
    会话1:
    SELECT * FROM test where age=14 for update;
    会话2:
    INSERT INTO `test` (`id`, `age`) VALUES (13, 12);
    结果:
    会话2阻塞。
     
    二、
    会话1:
    SELECT * FROM test where age=14 for update;
    会话2:
    INSERT INTO `test` (`id`, `age`) VALUES (17, 18);
    结果:
    会话2阻塞。
     
    三、
    会话1:
    SELECT * FROM test where age=14 for update;
    会话2:
    INSERT INTO `test` (`id`, `age`) VALUES (11, 12);
    结果:
    会话2执行成功。
     
    四、
    会话1:
    SELECT * FROM test where age=14 for update;
    会话2:
    INSERT INTO `test` (`id`, `age`) VALUES (11, 14);
    结果:
    会话2阻塞。
     
    五、
    会话1:
    SELECT * FROM test where age=14 for update;
    会话2:
    UPDATE `test` SET `age`=14 WHERE `id`=18;;
    结果:
    会话2阻塞。
     
    五、
    会话1:
    SELECT * FROM test where age=14 for update;
    会话2:
    UPDATE `test` SET `age`=19 WHERE `id`=18;;
    结果:
    会话2执行成功。
  • 相关阅读:
    pgsql 时间转时间戳
    java SHA-1加密
    java 输出json文件
    按照指定字符串截取文本
    java 科学计数法转换
    【原创】几个常用机场的对比
    【原创】Google Cloud服务器路由分析
    批量转换文件Unicode到中文
    博客搬家
    关于zookeeper
  • 原文地址:https://www.cnblogs.com/gjb724332682/p/11005471.html
Copyright © 2020-2023  润新知