MySQL 锁

本文深入探讨了MySQL中对MyISAM、MEMORY表的表级锁定与InnoDB表的行级锁定机制。重点分析了锁定类型的选择、锁定操作的执行流程以及如何通过查询表锁定信息来优化系统性能。同时,通过实例展示了读锁定和死锁现象的产生与解决过程,旨在帮助开发者更好地理解和应用MySQL的锁定机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MySQL 支持对 MyISAM 和 MEMORY 表进行表级锁定,对 InnoDB 表进行行级锁定。

在许多情况下,可以根据猜测应用程序使用哪类锁定类型最好,但一般很难说出某个给出的锁类型就比另一个好。一切取决于应用程序,应用程序的不同部分可能需要不同的锁类型。

为了确定是否想要使用行级锁定的存储引擎,应看看应用程序做什么并且混合使用什么样的选择和更新语句。例如,大多数Web应用程序执行许多选择,而很少进行删除,只对关键字的值进行更新,并且只插入少量具体的表。基本MySQL MyISAM 设置已经调节得很好。

在 MySQL 中对于使用表级锁定的存储引擎,表锁定时不会死锁的。这通过总是在一个查询开始时立即请求所有必要的锁定并且总是以同样的顺序锁定表来管理。

对 WRITE,MySQL使用的表锁定方法原理如下:
 如果在表上没有锁,在它上面放一个写锁。
 否则,把锁定请求放在写锁定队列中。

对 READ,MySQL使用的锁定方法原理如下:
 如果在表上没有写锁定,把一个读锁定放在它上面。
 否则,把锁请求放在读锁定队列中。

当一个锁定被释放时,锁定可被写锁定队列中的线程得到,然后是读锁定队列中的线程。

这意味着,如果你在一个表上有许多更新,SELECT语句将等待直到没有更多的更新。

可以通过检查 table_locks_waited 和 table_locks_immediate 状态变量来分析系统上的表锁定争夺:

mysql> show global status like "table_locks%";
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Table_locks_immediate | 36    |
| Table_locks_waited    | 0     |
+-----------------------+-------+
2 rows in set (0.00 sec)

Table_locks_immediate 发生表锁定操作, 但表锁定后马上释放
Table_locks_waited  发生表锁定, 并因此具有锁等待

对于 SQL 执行过程中, 需要对 SQL 进行硬解析, 计算 SQL 的运行成本后得到执行计划, 在执行 SQL 语句的时候需要查询当前资源是否具有锁机制, 如果当前资源被锁定中, 必须等待资源释放后才能够继续执行 SQL.


利用 SCHEMA sbtest.new 作为测试对象. 参考下面表结构。

mysql> desc sbtest.new;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id    | int(11) | YES  |     | NULL    |       |
+-------+---------+------+-----+---------+-------+
1 row in set (0.01 sec)

 

下面两个例子举例说明锁定的发生。

例子 1 读锁定
当前具有两个 session 登录到 MySQL 服务器中, 简称 sessionA 与 sessionB。


sessionA 中执行下面操作。

sessionA> show global status like "table_locks%";
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Table_locks_immediate | 36    |
| Table_locks_waited    | 0     |
+-----------------------+-------+
2 rows in set (0.00 sec)

解释:查询当前 MySQL 中表锁定信息。


sessionA> lock table sbtest.new write;
Query OK, 0 rows affected (0.00 sec)

解释:对测试表 sbtest.new 锁定,该操作只会影响其他会话对 sbtest.new 表执行 DDL 及 DML 操作。


sessionA> show global status like "table_locks%";
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Table_locks_immediate | 37    |
| Table_locks_waited    | 0     |
+-----------------------+-------+
2 rows in set (0.00 sec)

解释:当执行 lock table 操作之后,系统会对 sbtest.new 表执行一次锁定操作,当完成在表中数据库头部标记锁定资源操作后,释放锁。

在当前 sessionA 执行锁定操作状态下,不影响 sessionA 对表 sbtest.new 进行增删改操作,参考例子。

sessionA> insert into sbtest.new values (4),(5),(6);
Query OK, 3 rows affected (0.04 sec)
Records: 3  Duplicates: 0  Warnings: 0

sessionA> select * from sbtest.new;
+------+
| id   |
+------+
|    1 |
|    2 |
|    3 |
|    4 |
|    5 |
|    6 |
+------+
6 rows in set (0.00 sec)

sessionA> delete from sbtest.new where id > 3;
Query OK, 3 rows affected (0.06 sec)


完成上述操作后, 切换到 sessionB 会话中, 执行下面操作。

sessionB> select * from sbtest.new;

解释:当 sessionB 进行表查询时,由于 sessionA 执行锁定操作,导致查询等待,直到锁定结束为止。

利用管理员创建 sessionC 登录到 MySQL, 利用 show processlist 命令显示当前登录到 MySQL 终端的所有状态状态信息。

sessionC> show processlist;
+----+------+---------+------+---------------------------------+--------------------------+
| Id | db   | Command | Time | State                           | Info                     |
+----+------+---------+------+---------------------------------+--------------------------+
|  1 | NULL | Sleep   |  120 |                                 | NULL                     |
|  2 | NULL | Query   |    0 | NULL                            | show processlist         |
|  3 | NULL | Query   |  112 | Waiting for table metadata lock | select * from sbtest.new |
+----+------+---------+------+---------------------------------+--------------------------+
3 rows in set (0.00 sec)

要使用 show processlist 必须具有 PROCESS 权限,由于排版关系,返回信息进行部分折断,从 State 状态栏中可以清楚看到, ID 3 的会话当前正在处于查询等待状态, Waiting for table metadata lock 显示当前等待状态信息。

 

只要 sessionA 对表执行解锁操作,sessionB 就能够重新获得资源,继续之前 SQL 操作。

sessionA> unlock tables;
Query OK, 0 rows affected (0.00 sec)

sessionB> select * from sbtest.new;
+------+
| id   |
+------+
|    1 |
|    2 |
|    3 |
+------+
3 rows in set (29 min 52.91 sec)


例子 2:死锁
MySQL 在使用 InnoDB 引擎时,默认使用自动提交机制,该机制能够自动帮我们完成事务,自动提交机制并不能够为我们提高事务性能,我们稍后进行描述。

认识一下死锁的发生。我们需要两个会话,分别是 sessionA 与 sessionB。分别创建两个表,t_lock.a 与 t_lock.b, t_lock.a 与 t_lock.b 结构一致,如下描述。

mysql> desc t_lock.a;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   |     | NULL    |       |
| name  | varchar(10) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> desc t_lock.b;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   |     | NULL    |       |
| name  | varchar(10) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

测试过程中,sessionA 与 sessionB 需要关闭自动提交功能。
1. sessionA 登录到数据库,分别在两个表中插入测试数据。

sessionA> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

sessionA> insert into t_lock.a values (1,'from sessionA');
Query OK, 1 row affected, 1 warning (0.00 sec)

sessionA> insert into t_lock.b values (1,'from sessionA');
Query OK, 1 row affected, 1 warning (0.00 sec)

sessionA> commit;
Query OK, 0 rows affected (0.04 sec)

2. sessionA 更新 A 表中数据,不提交事务。

sessionA> update t_lock.a set name='new data' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

sessionA> select * from t_lock.a;
+----+----------+
| id | name     |
+----+----------+
|  1 | new data |
+----+----------+
1 row in set (0.00 sec)

3. sessionB 更新 B 表数据, 再更新 A 表数据,由于当前 A 表数据被 sessionA 修改中,数据处于保护状态,导致 sessionB 尝试修改 A 表数据时候出现锁定等待。锁定过程中,系统显示进程为更新中。

sessionB> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> update t_lock.b set name='update data' where id=1;
Query OK, 1 row affected, 1 warning (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 1

sessionB> select * from t_lock.b;
+----+------------+
| id | name       |
+----+------------+
|  1 | update dat |
+----+------------+
1 row in set (0.00 sec)

sessionB> update t_lock.a set name='update data' where id=1;

sessionA> show full processlist;
+----+------+---------+------+----------+---------------------------------------------------+
| Id | db   | Command | Time | State    | Info                                              |
+----+------+---------+------+----------+---------------------------------------------------+
|  2 | NULL | Query   |    8 | Updating | update t_lock.a set name='update data' where id=1 |
|  3 | NULL | Query   |    0 | NULL     | show full processlist                             |
+----+------+---------+------+----------+---------------------------------------------------+
2 rows in set (0.00 sec)


4. 这个时候当 sessionA 尝试修改 B 表数据,因为 sessionB 当前为锁定状态,而且 sessionB 对 B 表中数据具有锁定状态中,则出现死锁。sessionB 会自动终止尝试修改 A 表数据事务, 并返回下面错误信息。而且下面两个事务操作都被终止。

sessionA> update t_lock.b set name='new data' where id=1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

sessionB> update t_lock.a set name='update data' where id=1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

sessionA> select * from t_lock.a;
+----+----------+
| id | name     |
+----+----------+
|  1 | new data |
+----+----------+
1 row in set (0.00 sec)

sessionB> select * from t_lock.b;
+----+------------+
| id | name       |
+----+------------+
|  1 | update dat |
+----+------------+
1 row in set (0.00 sec)

### MySQL 机制详解 MySQL 中的机制是为了保障数据库在高并发环境下的数据一致性和稳定性而设计的重要功能之一。以下是关于 MySQL 机制的具体解析: #### 1. 的分类 MySQL 主要分为两种类型的:表级和行级。 - **表级 (Table-Level Lock)** 表级是最简单的锁定策略,适用于 MyISAM 存储引擎。它会在整个表上施加,无论是读操作还是写操作都会影响到整张表。对于 `SELECT` 操作,MyISAM 会自动给涉及的所有表加上读;而对于 `UPDATE`, `INSERT`, 和 `DELETE` 则会自动加上写[^3]。 - **行级 (Row-Level Lock)** 行级由 InnoDB 存储引擎实现,提供更高的并发能力。只有涉及到具体记录的操作才会触发行级。例如,在执行 `SELECT ... FOR UPDATE` 或者 `SELECT ... LOCK IN SHARE MODE` 时,InnoDB 只会对符合条件的特定行加而不是封整个表[^2]。 #### 2. 不同存储引擎的特性 不同的存储引擎有不同的行为: - **MyISAM**: 默认使用的是表级别的,这意味着即使是一个小范围内的更新也会阻塞其他线程对该表任何部分的访问[^3]。 - **InnoDB**: 支持事务以及更细粒度的行级,这使得它可以更好地处理复杂的多用户场景下的并发请求[^4]。 #### 3. 常见类型及其作用 除了基本的表级与行级区分外,还有几种具体的形式用于满足不同需求: - **共享 (Shared Lock, S-Lock)** 当一个客户端通过命令如 `LOCK TABLES table_name READ` 获取了一个表上的共享之后,其它客户也可以获得该表上的共享,但不能再获取排他直到当前持有共享的所有连接释放它们为止[^2]。 - **排他 (Exclusive Lock, X-Lock)** 排他允许独占式的修改权限。如果某个事务已经获得了某条记录或者某些列上的排他,则在此期间不允许别的事务再对此同一组资源申请任何形式的新——既包括另外的排他也包括新的共享[^2]。 - **GAP ** 这种特殊的用来阻止新纪录插入到现有两条连续记录之间的空隙(gap)里去。比如当我们运行下面这条SQL语句的时候就会用到gap lock:`SELECT * FROM table_name WHERE id > 10 FOR UPDATE;` - **Next-Key Lock** 是一种组合了索引记录本身(record)和其前面那个区间(gap)两者一起保护起来的一种复合型模式。主要用于解决可重复读(repeatable read)隔离级别下可能出现的幻影问题(phantom problem)[^4]。 #### 4. 死现象及预防措施 死是指两个或更多事务相互等待对方持有的资源从而进入僵局的状态。为避免这种情况发生可以采取以下方法: - 尽量按照固定的顺序访问资源; - 减少单次事务持续时间; - 设置合理的超时参数让系统能够及时发现并终止潜在的死循环状况等等[^4]。 ```sql -- 示例:如何手动解表 UNLOCK TABLES; ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Terry_Tsang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值