mysql innodb 注意事项_MySQL InnoDB 锁事

本文首发于微信公众号 《andyqian》, 关注第一时间查看好文

前言

在数据库中,通常通过锁来解决并发下数据一致性问题,从而避免数据产生脏乱。在保证数据一致性问题的前提下,通过锁范围又分为不同种类,在 MySQL 中,存储引擎就支持不同类型锁。如: MyISAM 只支持表锁。InnoDB 支持:行锁,表锁,Gap 锁等等。今天就来聊聊 MySQL InnoDB 的 " 锁事" 。

锁类型

既然提到了锁,锁类型是逃不开的话题,在 InnoDB 中,行锁分为共享锁(Share) 和 排他 (Exclusive) 锁。其详细概念如下:共享(Share)锁 :简称 (S) 锁, 持有该锁的事务允许读取行数据。

排他(Exclusive)锁:简称 (X)锁 持有该锁的事务允许 update 或 delete 行数据。

备注:此处的行锁不能简单的理解为锁定一行数据,而是 行数据。行数据包括:单行,多行,理解这区别,对后面理解(Record 锁,Gap 锁)会有莫大的帮助。

兼容关系:

S 锁X 锁S 锁兼容不兼容X 锁不兼容不兼容

文字解析:

当事务 T1 在行 t 上持有 S (共享) 锁时,事务 T2 对行 t 上 请求持有锁的结果如下:当事务 T2 请求获取 S (共享) 锁时,将立即授予,此时 事务 T1,T2 同时持有 S (共享) 锁。

当事务 T2 请求获取 X (排他) 锁时,此时T2 将会处于锁等待状态,等待事务T1 释放 S (共享)锁后再进行获取,如果T2 锁

例子: 事务1持有S 锁, 事务2 请求持有S 锁。事务1

begin;

mysql> select * from t_base_info where oid = 1 lock in share mode;

+-----+----------+---------------------+---------------------+

| oid | name | create_time | updated_time |

+-----+----------+---------------------+---------------------+

| 1 | andyqian | 2020-03-21 14:34:08 | 2020-03-21 14:34:08 |

+-----+----------+---------------------+---------------------+

1 row in set (0.00 sec)事务2

begin;

mysql> select * from t_base_info where oid = 1 lock in share mode;

+-----+----------+---------------------+---------------------+

| oid | name | create_time | updated_time |

+-----+----------+---------------------+---------------------+

| 1 | andyqian | 2020-03-21 14:34:08 | 2020-03-21 14:34:08 |

+-----+----------+---------------------+---------------------+

1 row in set (0.00 sec)

例子: 事务1持有S 锁, 事务2 请求持有X 锁。事务1

begin;

mysql> select * from t_base_info where oid = 1 lock in share mode;

+-----+----------+---------------------+---------------------+

| oid | name | create_time | updated_time |

+-----+----------+---------------------+---------------------+

| 1 | andyqian | 2020-03-21 14:34:08 | 2020-03-21 14:34:08 |

+-----+----------+---------------------+---------------------+

1 row in set (0.00 sec)事务2

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from t_base_info where oid = 1 for update;

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

以上例子也佐证了上面的理论。

常见锁

在 InnoDB 存储引擎, REPEATABLE READ (可重复读) 隔离级别下,为我们提供了多种锁,接下来我们一探究竟。(下述所有例子:在 Innodb,REPEATABLE READ (可重复读)隔离级别 运行,请确保一致)

1. 表锁

锁定范围:整表。

特点:获取锁效率高,有效避免死锁 (破坏了死锁的竞争条件)。但严重影响性能,并发性低,在生产系统上,表锁简直属于灾难。

事务1

begin;

lock table t_base_info read;

事务2:

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql> insert into t_base_info(name,create_time,updated_time)values("name",now(),now());

结果:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

备注:表锁的超时时间不受:innodb_lock_wait_timeout 限制,而是受:lock_wait_timeout 限制。

mysql> show variables like "lock_wait_timeout";

+-------------------+----------+

| Variable_name | Value |

+-------------------+----------+

| lock_wait_timeout | 31536000 |

+-------------------+----------+

1 row in set (0.00 sec)

单位为秒,默认为31536000 秒,365 天。

可通过下述命令进行修改:

set global lock_wait_timeout = 10;

2. Record 锁

锁定范围:索引记录,如果表中没有索引记录,则会自动创建一个隐藏的聚簇索引。

特点:只锁定单条索引记录,获取锁效率稍低,可能会产生死锁, 但并发高,性能好。

适用隔离级别: REPEATABLE READ (可重复读),READ COMMITTED (提交读),READ UNCOMMITTED (未提交读)。

3. Gap 锁

锁定范围:顾名思义,锁定的是一个范围。

特点:Gap 锁: 锁定一个范围,锁定后该范围不支持新增,其他事物在该范围中insert,delete 需要lock wait 直至释放。

适用隔离级别: REPEATABLE READ (可重复读)。

备注:在 READ UNCOMMITTED (未提交读),READ COMMITTED (提交读) 隔离级别下无效。

例子:

事务1

begin;

select * from t_base_info where oid < 8 for update;

事务2:

begin;

mysql> insert into t_base_info(oid,name,create_time,updated_time)

values(4,"name4",now(),now());

结果:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

4. Next-Key 锁

锁定范围:顾名思义,锁定的是一个范围。

特点:Next-Lock:Recod 锁 + Gap 锁的集合。其中锁定的范围是:当前记录和范围。

适用隔离级别:REPEATABLE READ (可重复读)

注意事项:该锁在 READ UNCOMMITTED (未提交读),READ COMMITTED (提交读) 隔离级别下无效。

事务1

begin;

select * from t_base_info where oid < 8 for update;

事务2:

begin;

mysql> update t_base_info set name = "andyqian008" where oid = 8;

结果:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

锁超时

在高并发下,热点数据的锁竞争是非常常见的。在 MySQL 中采用超时机制,当获取锁时间超过 innodb_lock_wait_timeout 后,会提示获取锁失败。如下所示:锁获取超时后,提示信息:

> 1205 - Lock wait timeout exceeded; try restarting transaction

> Time: 30.89s查看锁超时时间, (默认为 50 秒)。

show variables like "innodb_lock_wait_timeout";修改全局锁超时时间。

set global innodb_lock_wait_timeout = 30;修改当前会话锁超时时间

set session innodb_lock_wait_timeout = 20;

备注:修改 global 超时时,当前 session 超时时间不变,其他 session 改变。

修改 session 超时时,当前 session 超时时间改变,其他 session 不改变。

在 Navicat 等客户端工具中,一个 Query 为一个 session 。

小结

上述是我对 MySQL InnoDB 锁以及常见锁的理解,如有误之处,还请多多指出。谈到这里,我觉得这和 Java 中 JUC 包提供的职责是相同的,其设计思路也有相通之处。此时有人要问了:Java 中不是还有CAS 无锁方案吗?数据库中有类似的概念及实现吗?你还别不信, 在 InnoDB 存储引擎中的 MVCC 与 Java 中 CAS 的就像极了,我们下篇就来详细讲讲。

相关阅读:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值