锁定和解锁 MySQL 中的表
在本文中,您将了解如何使用 MySQL LOCK TABLES 和 UNLOCK TABLES 语句获取表锁和释放表锁。
假设在一个银行系统中有这样的逻辑:
当用户 A 从自己的银行账户取出 500 元时,用户 A 的余额为 原余额 减去 500。当另一个用户 B 给用户转入 500 元时,用户 A 的余额为 原余额 加上 500。
如果这两个操作同时发生,则可能导致用户 A 的余额是错误的。
MySQL 的锁就是为了解决这种并发问题的。MySQL 支持三种类型的锁:表级锁、行级锁和页面所。
MySQL 允许您在会话中显式地获取表锁,以防止其他会话在您需要独占访问表的期间修改表。
锁的操作是在当前会话中进行的。一个会话只能为自己获取锁,并只能释放自己的锁。
MySQL 提供了 LOCK TABLES 和 UNLOCK TABLES 语句用于显式地的获取表锁和释放表锁。
MySQL 表锁语法
LOCK TABLES 语法
要为当前会话显式地获取表锁,请按照以下语法使用 LOCK TABLES 语句:
LOCK TABLES
table_name [READ | WRITE]
[, table_name [READ | WRITE], ...];
这里:
table_name是您要获取锁的表名。READ和WRITE是锁类型。READ锁用于共享读取表,WRITE锁用于排斥的读写表。- 您可以在一个语句中获取多个表的锁。多个表锁之间使用逗号分隔。
LOCK TABLES语句在获取新的表锁之前会隐式的释放当前会话持有的所有的表锁。- 您可以使用
LOCK TABLE代替LOCK TABLES。
UNLOCK TABLES 语法
要释放当前会话获取的所有的表锁,请使用以下语句:
UNLOCK TABLES;
锁类型
表锁支持 READ 和 WRITE 两种类型的锁。 READ 锁用于共享读取表, WRITE 锁用于排斥的读写表,他们的特点如下:
READ 锁
-
持有表锁的会话只能读取表,但不能写入表。
-
多个会话可以同时获取一个表的
READ锁。 -
其他会话无需显式获取
READ锁即可读取该表,但是不能写入表。其他会话的写操作会一直等待知道读锁被释放。
WRITE 锁
-
持有锁的会话可以读写表。
-
只有持有锁的会话才能访问该表。在释放锁之前,没有其他会话可以访问它。
-
持有
WRITE锁时,其他会话对表的锁请求会阻塞。
如果您没有显式地的释放表锁,当会话结束后,无论是 READ 锁还是 WRITE 锁,都会被 MySQL 释放掉。
MySQL 表锁实例
这个实例演示了如何在 MySQL 中获取表锁( READ 和 WRITE 锁)和释放表锁。
我们使用以下语句在 testdb 数据库中 创建一个 test_lock 表 以实践我们的实例:
CREATE TABLE `test_lock` (
`id` int NOT NULL AUTO_INCREMENT,
`txt` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
如果您没有 testdb 数据库,请先使用如下语句创建数据库并选择数据库:
CREATE DATABASE testdb;
use testdb;
READ 锁实例
READ 锁是共享的读锁。让我们通过以下实例了解 READ 锁的特性。
-
先开启一个会话,并使用
SHOW PROCESSLIST语句查看当前会话的 ID:SHOW PROCESSLIST;+----+-----------------+-----------+--------+---------+--------+---------------------------------+--------------------------------------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+-----------------+-----------+--------+---------+--------+---------------------------------+--------------------------------------------------+ | 5 | event_scheduler | localhost | NULL | Daemon | 201156 | Waiting on empty queue | NULL | | 8 | root | localhost | testdb | Query | 0 | init | show PROCESSLIST | +----+-----------------+-----------+--------+---------+--------+---------------------------------+--------------------------------------------------+这里,当前会话的 ID 为 8。
-
在当前会话没有获取
READ锁的情况下向表中插入一个新行,INSERT INTO test_lock(txt) VALUES('Hello');这里能正常插入。
-
使用下面的
LOCK TABLES获取test_lock表的读锁:LOCK TABLES test_lock READ; -
让我们在拥有
READ锁的情况下向表中插入一个新行,INSERT INTO test_lock(txt) VALUES('Hello World');此时,MySQL 给出一个错误: “ERROR 1099 (HY000): Table ’test_lock’ was locked with a READ lock and can’t be updated”。
-
您可以重新打开一个会话,并在新会话测试读表:
SELECT * FROM test_lock;+----+-------------+ | id | txt | +----+-------------+ | 1 | Hello | +----+-------------+这说明,拥有表的读锁的会话之外的其他会话也可以读表。
-
在新的会话中测试写表。请执执行上面的的
INSERT语句。你会发现,该操作将被挂起,直到表的读锁被释放。 -
您可以使用
SHOW PROCESSLIST语句查看回话是否处于等待的状态:SHOW PROCESSLIST;+----+-----------------+-----------+--------+---------+--------+---------------------------------+--------------------------------------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+-----------------+-----------+--------+---------+--------+---------------------------------+--------------------------------------------------+ | 5 | event_scheduler | localhost | NULL | Daemon | 201156 | Waiting on empty queue | NULL | | 8 | root | localhost | testdb | Query | 0 | init | show PROCESSLIST | | 9 | root | localhost | testdb | Query | 16 | Waiting for table metadata lock | INSERT INTO test_lock(txt) VALUES('Hello World') | +----+-----------------+-----------+--------+---------+--------+---------------------------------+--------------------------------------------------+您可以看到, Id 为 9 的会话正在等待锁释放,它的状态为:
Waiting for table metadata lock。
WRITE 锁实例
WRITE 锁是排他锁,只有持有锁的会话才能访问表,其他会话的对表的操作都会等待锁的释放。让我们通过以下示例了解 WRITE 锁的特性。
-
先开启一个会话,并从此会话中获取
test_lock的WRITE锁。LOCK TABLES test_lock WRITE; -
使用以下语句在
test_lock表中插入一个新行。INSERT INTO test_lock(txt) VALUES('Hi');插入成功。这说明持有表的写锁的会话可以写表。
-
使用以下语句从
test_lock表中查询数据:SELECT * FROM test_lock;+----+-------------+ | id | txt | +----+-------------+ | 1 | Hello | | 2 | Hello World | | 3 | Hi | +----+-------------+查询成功。这说明持有表的写锁的会话可以读表。
-
开启另一个会话,并尝试读取数据:
SELECT * FROM test_lock;您会发现,该操作处于等待的状体,并且没有返回。
-
您可以使用
SHOW PROCESSLIST语句查看会话的状态SHOW PROCESSLIST;+----+-----------------+-----------+--------+---------+--------+---------------------------------+-------------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+-----------------+-----------+--------+---------+--------+---------------------------------+-------------------------+ | 5 | event_scheduler | localhost | NULL | Daemon | 202266 | Waiting on empty queue | NULL | | 8 | root | localhost | testdb | Query | 0 | init | SHOW PROCESSLIST | | 9 | root | localhost | testdb | Query | 81 | Waiting for table metadata lock | SELECT * FROM test_lock | +----+-----------------+-----------+--------+---------+--------+---------------------------------+-------------------------+ -
在第一个会话中使用下面的语句释放掉持有的锁:
UNLOCK TABLES;您将看到第二个会话中的
SELECT语句的执行结果如下:+----+-------------+ | id | txt | +----+-------------+ | 1 | Hello | | 2 | Hello World | | 3 | Hi | +----+-------------+
结论
MySQL 允许您在会话中使用 LOCK TABLES 语句显式地获取表锁,以防止其他会话在您需要独占访问表的期间修改表。
MySQL 允许您在会话中使用 UNLOCK TABLES 语句显式地释放表锁。
MySQL 表锁有两种类型:共享的读锁和排他的写锁。