13.3.5 LOCK TABLES 和 UNLOCK TABLES 语句

LOCK TABLES
    tbl_name [[AS] alias] lock_type
    [, tbl_name [[AS] alias] lock_type] ...

lock_type: {
    READ [LOCAL]
  | [LOW_PRIORITY] WRITE
}

UNLOCK TABLES

MySQL 使 Client 端会话能够显式地获取 table 锁,以便与其他会话协作访问 table,或者防止其他会话在会话需要互斥访问期间修改 table。会话只能为其自身获取或释放锁。一个会话无法获取另一会话的锁,也不能释放另一会话持有的锁。

在更新 table 时,锁可用于模拟事务或提高速度。 table 锁定限制和条件中对此进行了更详细的说明。

LOCK TABLES明确获取当前 Client 端会话的 table 锁。可以为基本 table 或视图获取 table 锁。您必须具有LOCK TABLES特权和SELECT特权才能锁定每个对象。

对于视图锁定,LOCK TABLES将视图中使用的所有基本 table 添加到要锁定的 table 集中,并自动锁定它们。从 MySQL 5.7.32 开始,LOCK TABLES检查视图定义器是否对基于视图的 table 具有适当的特权。

如果使用LOCK TABLES显式锁定 table,则触发器中使用的任何 table 也会隐式锁定,如锁定 table 和触发器中所述。

UNLOCK TABLES明确释放当前会话持有的所有 table 锁。 LOCK TABLES在获取新锁之前隐式释放当前会话持有的所有 table 锁。

UNLOCK TABLES的另一个用途是释放通过带读取锁的table语句获取的全局读取锁定,这使您可以锁定所有数据库中的所有 table。参见第 13.7.6.3 节“ FLUSH 语句”。 (如果您拥有诸如 Veritas 之类的可以及时拍摄快照的文件系统,这是获取备份的一种非常方便的方法.)

table 锁只能防止其他会话进行不适当的读取或写入。持有WRITE锁的会话可以执行 table 级操作,例如DROP TABLETRUNCATE TABLE。对于持有READ锁的会话,不允许DROP TABLETRUNCATE TABLE操作。

以下讨论仅适用于非TEMPORARYtable。 TEMPORARYtable 允许LOCK TABLES(但被忽略)。可以通过创建 table 的会话自由访问该 table,而不考虑可能执行了哪些其他锁定。无需锁定,因为没有其他会话可以看到该 table。

table 锁获取

要获取当前会话中的 table 锁,请使用LOCK TABLES语句,该语句获取元数据锁(请参阅第 8.11.4 节“元数据锁定”)。

可以使用以下锁定类型:

READ [LOCAL]锁定:

  • 持有锁的会话可以读取 table(但不能写入 table)。

  • 多个会话可以同时获取该 table 的READ锁。

  • 其他会话可以在不显式获取READ锁的情况下读取 table。

  • LOCAL修饰符使其他会话可以在保持锁的同时执行不冲突的INSERT语句(并发插入)。 (请参阅第 8.11.3 节“并发插入”。)但是,如果您要在持有锁的同时使用服务器外部的进程来操作数据库,则不能使用READ LOCAL。对于InnoDB个 table,READ LOCALREAD相同。

[LOW_PRIORITY] WRITE锁定:

  • 持有锁的会话可以读写 table。

  • 只有持有锁的会话才能访问该 table。在释放锁之前,没有其他会话可以访问它。

  • 保持WRITE锁定时,其他会话对 table 的锁定请求将阻止。

  • LOW_PRIORITY修饰符无效。在以前的 MySQL 版本中,它影响了锁定行为,但现在不再如此。现在已弃用它,并且使用它会产生警告。使用WRITE而不使用LOW_PRIORITY

WRITE锁通常具有比READ锁更高的优先级,以确保尽快处理更新。这意味着,如果一个会话获得READ锁,然后另一个会话请求WRITE锁,则随后的READ锁请求将 await,直到请求WRITE锁的会话已获得并释放了该锁。 (对于较小的max_write_lock_count系统变量,可能会发生此策略的异常;请参阅第 8.11.4 节“元数据锁定”。)

如果LOCK TABLES语句由于在任何 table 上其他会话持有的锁而必须 await,则它将阻塞直到可以获取所有锁为止。

需要锁的会话必须在一个LOCK TABLES语句中获取它需要的所有锁。在保留由此获得的锁的同时,会话只能访问锁定的 table。例如,在下面的语句序列中,尝试访问t2时发生错误,因为它未锁定在LOCK TABLES语句中:

mysql> LOCK TABLES t1 READ;
mysql> SELECT COUNT(*) FROM t1;
+----------+
| COUNT(*) |
+----------+
|        3 |
+----------+
mysql> SELECT COUNT(*) FROM t2;
ERROR 1100 (HY000): Table 't2' was not locked with LOCK TABLES

INFORMATION_SCHEMA数据库中的 table 是一个 exception。即使会话持有通过LOCK TABLES获得的 table 锁,也可以在不显式锁定它们的情况下访问它们。

您不能在使用相同名称的单个查询中多次引用锁定的 table。请改用别名,并为 table 和每个别名获取单独的锁:

mysql> LOCK TABLE t WRITE, t AS t1 READ;
mysql> INSERT INTO t SELECT * FROM t;
ERROR 1100: Table 't' was not locked with LOCK TABLES
mysql> INSERT INTO t SELECT * FROM t AS t1;

对于第一个INSERT,会发生错误,因为锁定 table 有两个对相同名称的引用。第二个INSERT成功,因为对该 table 的引用使用了不同的名称。

如果您的语句通过别名引用 table,则必须使用相同的别名锁定 table。如果不指定别名,则无法锁定 table:

mysql> LOCK TABLE t READ;
mysql> SELECT * FROM t AS myalias;
ERROR 1100: Table 'myalias' was not locked with LOCK TABLES

相反,如果使用别名锁定 table,则必须在该语句中使用该别名引用该 table:

mysql> LOCK TABLE t AS myalias READ;
mysql> SELECT * FROM t;
ERROR 1100: Table 't' was not locked with LOCK TABLES
mysql> SELECT * FROM t AS myalias;

Note

LOCK TABLESUNLOCK TABLES应用于分区 table 时,始终锁定或解锁整个 table。这些语句不支持分区锁修剪。参见第 22.6.4 节“分区和锁定”

table 锁释放

释放会话持有的 table 锁时,它们都将同时释放。会话可以显式释放其锁,也可以在某些条件下隐式释放锁。

如果 Client 端会话的连接终止,无论是正常连接还是异常连接,服务器都会隐式释放该会话持有的所有 table 锁(事务性和非事务性)。如果 Client 端重新连接,则锁定将不再有效。此外,如果 Client 端有活动的事务,则服务器在断开连接时会回滚事务,如果发生重新连接,则新会话将从启用自动提交开始。因此,Client 端可能希望禁用自动重新连接。启用自动重新连接后,如果发生重新连接,则不会通知 Client 端,但是任何 table 锁或当前事务都将丢失。在禁用自动重新连接的情况下,如果连接断开,则下一条发出的语句将发生错误。Client 端可以检测到错误并采取适当的措施,例如重新获得锁或重做事务。参见第 27.7.19 节“ C API 自动重新连接控制”

Note

如果在锁定 table 上使用ALTER TABLE,则它可能会被解锁。例如,如果您尝试第二次ALTER TABLE操作,则结果可能是错误Table 'tbl_name' was not locked with LOCK TABLES。要处理此问题,请在第二次更改之前再次锁定 table。另请参见第 B.4.6.1 节“ ALTER TABLE 的问题”

table 锁定和事务的交互

LOCK TABLESUNLOCK TABLES与事务的交互作用如下:

  • LOCK TABLES不是事务安全的,它在尝试锁定 table 之前隐式提交任何活动的事务。

  • UNLOCK TABLES隐式提交任何活动事务,但前提是已使用LOCK TABLES来获取 table 锁。例如,在以下语句集中,UNLOCK TABLES释放全局读取锁,但由于没有有效的 table 锁而不提交事务:

FLUSH TABLES WITH READ LOCK;
START TRANSACTION;
SELECT ... ;
UNLOCK TABLES;
SET autocommit=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
... do something with tables t1 and t2 here ...
COMMIT;
UNLOCK TABLES;

当您调用LOCK TABLES时,InnoDB在内部采用自己的 table 锁,而 MySQL 则采用自己的 table 锁。 InnoDB在下一次提交时释放其内部 table 锁,但是要让 MySQL 释放其 table 锁,您必须调用UNLOCK TABLES。您不应该具有自动提交= 1,因为InnoDB会在调用LOCK TABLES之后立即释放其内部 table 锁,并且死锁很容易发生。如果自动提交= 1InnoDB根本不获取内部 table 锁,以帮助旧应用程序避免不必要的死锁。

锁定 table 和触发器

如果使用LOCK TABLES显式锁定 table,则触发器中使用的任何 table 也将隐式锁定:

  • 锁定与通过LOCK TABLES语句显式获取的锁定时间相同。

  • 触发器中使用的 table 上的锁取决于该 table 是否仅用于读取。如果是这样,则读锁就足够了。否则,将使用写锁。

  • 如果使用LOCK TABLES显式锁定了 table 以进行读取,但是由于可能在触发器中对其进行了修改,则需要将其锁定以进行写入,则将采用写锁定,而不是读锁定。 (也就是说,由于 table 在触发器中的出现而需要的隐式写锁定导致将 table 的显式读锁定请求转换为写锁定请求.)

假设您使用以下语句锁定两个 tablet1t2

LOCK TABLES t1 WRITE, t2 READ;

如果t1t2有任何触发器,则触发器中使用的 table 也将被锁定。假设t1的触发器定义如下:

CREATE TRIGGER t1_a_ins AFTER INSERT ON t1 FOR EACH ROW
BEGIN
  UPDATE t4 SET count = count+1
      WHERE id = NEW.id AND EXISTS (SELECT a FROM t3);
  INSERT INTO t2 VALUES(1, 2);
END;

LOCK TABLES语句的结果是t1t2被锁定,因为它们出现在该语句中;而t3t4被锁定,因为它们在触发器中使用:

  • 根据WRITE锁定请求,t1被锁定以进行写入。

  • t2被锁定以进行写入,即使请求是READ锁定。发生这种情况是因为在触发器中插入了t2,因此READ请求被转换为WRITE请求。

  • t3被锁定以进行读取,因为它只能从触发器内读取。

  • t4已被锁定,无法写入,因为它可能会在触发器中更新。

table 锁定的限制和条件

您可以安全地使用KILL终止正在 awaittable 锁的会话。参见第 13.7.6.4 节“ KILL 语句”

LOCK TABLESUNLOCK TABLES不能在存储的程序中使用。

performance_schema数据库中的 table 不能用LOCK TABLES锁定,但setup_xxxtable 除外。

LOCK TABLES语句生效时,以下语句被禁止:CREATE TABLE创建 table...喜欢CREATE VIEWDROP VIEW以及有关存储的函数,过程和事件的 DDL 语句。

对于某些操作,必须访问mysql数据库中的系统 table。例如,HELP语句需要服务器端帮助 table 的内容,而CONVERT_TZ()则可能需要读取时区 table。服务器隐式锁定系统 table 以在需要时进行读取,因此您无需显式锁定它们。这些 table 如前所述:

mysql.help_category
mysql.help_keyword
mysql.help_relation
mysql.help_topic
mysql.proc
mysql.time_zone
mysql.time_zone_leap_second
mysql.time_zone_name
mysql.time_zone_transition
mysql.time_zone_transition_type

如果要使用LOCK TABLES语句在所有这些 table 上显式放置WRITE锁,则该 table 必须是唯一的一个锁;没有其他 table 可以使用同一语句锁定。

通常,您不需要锁定 table,因为所有单个UPDATE语句都是原子的;没有其他会话可以干扰任何其他当前正在执行的 SQL 语句。但是,在某些情况下,锁定 table 可能会带来好处:

  • 如果要在一组MyISAMtable 上运行许多操作,则锁定要使用的 table 要快得多。锁定MyISAMtable 可以加快对其的插入,更新或删除,因为 MySQL 直到调用UNLOCK TABLES时才会刷新锁定 table 的键高速缓存。通常,在每个 SQL 语句之后刷新键缓存。

锁定 table 的不利之处在于,没有会话可以更新READ锁定的 table(包括持有锁的 table),没有会话可以访问WRITE锁定的 table,除了持有该锁的 table 之外。

  • 如果要将 table 用于非事务性存储引擎,则要确保没有其他会话修改SELECTUPDATE之间的 table,则必须使用LOCK TABLES。此处显示的示例要求LOCK TABLES安全执行:
LOCK TABLES trans READ, customer WRITE;
SELECT SUM(value) FROM trans WHERE customer_id=some_id;
UPDATE customer
  SET total_value=sum_from_previous_statement
  WHERE customer_id=some_id;
UNLOCK TABLES;

如果没有LOCK TABLES,则另一个会话可能会在执行SELECTUPDATE语句之间在transtable 中插入新行。

在许多情况下,可以通过使用相对更新(UPDATE customer SET value=value+new_value)或LAST_INSERT_ID()函数来避免使用LOCK TABLES

在某些情况下,您还可以通过使用用户级咨询锁定功能GET_LOCK()RELEASE_LOCK()来避免锁定 table。这些锁保存在服务器的哈希 table 中,并以pthread_mutex_lock()pthread_mutex_unlock()高速实现。参见第 12.14 节“锁定功能”

有关锁定策略的更多信息,请参见第 8.11.1 节“内部锁定方法”