MySQL允许客户端会话显式获取表锁,以防止其他会话在特定时间段内访问表。客户端会话只能为自己获取或释放表锁。它无法获取或释放其他会话的表锁。
在详细讨论之前,我们将创建一个名为示例的数据库sbtest,其中包含一个名为tbl实践表锁定语句的简单表。
1 2 3 4 5 6 |
CREATE DATABASE sbtest; CREATE TABLE tbl ( id int(11) NOT NULL AUTO_INCREMENT, col int(11) NOT NULL, PRIMARY KEY (id) ); |
一、LOCK和UNLOCK TABLES
LOCK TABLE的简单语法形式如下:
1 |
LOCK TABLES table_name [READ | WRITE] |
MySQL提供了两种锁类型:READ和WRITE,下面介绍。
要释放表的锁定,请使用以下语句:
1 |
UNLOCK TABLES; |
二、表锁为READ类型
READ锁具有以下功能:
- 一个表的READ锁可以同时被多个会话获取。另外,其他会话可以从表中读取数据而不需要获取锁。
- 保存READ锁的会话只能从表中读取数据,但不能写入。另外,其他会话在READ锁定释放之前不能将数据写入表中。来自另一个会话的写入操作将被置于等待状态,直到READ锁定被释放。
- 如果会话正常或异常终止,MySQL将隐式释放所有锁,WRITE锁也是如此。
让我们来看看READ锁在以下场景中的工作方式。
首先,连接到sbtest数据库。要找出当前的连接ID,可以使用CONNECTION_ID()函数:
1 2 3 4 5 6 7 |
mysql> SELECT CONNECTION_ID(); +-----------------+ | CONNECTION_ID() | +-----------------+ | 46083 | +-----------------+ 1 row in set (0.00 sec) |
然后,在tbl表中插入一个新行。
1 |
mysql> INSERT INTO tbl(col) VALUES(10); |
之后,要获得一个锁,你可以使用LOCK TABLE语句。
1 |
mysql> LOCK TABLE tbl READ; |
看一下此时表打开的情况。
1 2 3 4 5 6 7 |
mysql> show open tables where in_use >= 1; +----------+-------+--------+-------------+ | Database | Table | In_use | Name_locked | +----------+-------+--------+-------------+ | sbtest | tbl | 1 | 0 | +----------+-------+--------+-------------+ 1 row in set (0.00 sec) |
最后,在同一个会话中,如果你尝试向表中插入新行,你将收到错误消息。
1 2 |
mysql> INSERT INTO tbl(col) VALUES(11); ERROR 1099 (HY000): Table 'tbl' was locked with a READ lock and can't be updated |
再来看看同一个会话中查询是否正常。
1 2 3 4 5 6 7 |
mysql> SELECT * FROM tbl; +----+-----+ | id | col | +----+-----+ | 1 | 10 | +----+-----+ 1 row in set (0.00 sec) |
因此,获取一次READ锁定后,你无法在同一个会话中将数据写入表中。然后再来检查来自不同会话的READ锁。
首先,连接sbtest并检查连接ID:
1 2 3 4 5 6 7 |
mysql> SELECT CONNECTION_ID(); +-----------------+ | CONNECTION_ID() | +-----------------+ | 46092 | +-----------------+ 1 row in set (0.00 sec) |
然后,查询tbl表数据。
1 2 3 4 5 6 7 |
mysql> SELECT * FROM tbl; +----+-----+ | id | col | +----+-----+ | 1 | 10 | +----+-----+ 1 row in set (0.00 sec) |
接下来,tbl从第二个会话中插入一个新行到表中。
1 |
mysql> INSERT INTO tbl(col) VALUES(20); |
来自第二个会话的插入操作处于等待状态,因为第一个会话已经在tbl表上获取了一个READ锁并且它尚未释放。
你可以从SHOW PROCESSLIST中看到详细的元数据锁等待信息。
1 2 3 4 5 6 7 8 |
mysql> SHOW PROCESSLIST; +-------+------+-------------------+--------------------+-------------+--------+--------------------------------------+---------------------------------+----------+ | Id | User | Host | db | Command | Time | State | Info | Progress | +-------+------+-------------------+--------------------+-------------+--------+--------------------------------------+---------------------------------+----------+ | 46083 | root | localhost | sbtest | Query | 0 | init | SHOW PROCESSLIST | 0.000 | | 46092 | root | localhost | sbtest | Query | 20 | Waiting for table level lock | INSERT INTO tbl(col) VALUES(20) | 0.000 | +-------+------+-------------------+--------------------+-------------+--------+--------------------------------------+---------------------------------+----------+ 5 rows in set (0.00 sec) |
之后,回到第一个会话并使用UNLOCK TABLES语句释放锁(或者中断第一个会话也会释放锁)。从第一个会话释放READ锁后,第二个会话中的INSERT操作将会立即执行成功。
lock tables有一个local关键字,如lock tables tbl read local,意思就是只针对当前会话加read锁。其他会话可以正常写入数据的,但是只能针对非InnoDB存储引擎才有效,对于InnoDB存储引擎无效。
最后还有一个小问题,其它会话能否也能给该表加LOCK TABLES READ? 其它会话是否也能给该表加LOCK TABLES WRITE?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
mysql> LOCK TABLES tbl READ; Query OK, 0 rows affected (0.00 sec) mysql> show open tables where in_use >= 1; +----------+-------+--------+-------------+ | Database | Table | In_use | Name_locked | +----------+-------+--------+-------------+ | sbtest | tbl | 2 | 0 | +----------+-------+--------+-------------+ 1 row in set (0.00 sec) mysql> LOCK TABLES tbl WRITE; waiting... |
结果表明,其他会话可以加LOCK TABLES READ,但不能加LOCK TABLES WRITE。
三、表锁为WRITE类型
WRITE的表锁具有以下功能:
- 只有拥有表锁的会话才能读取和写入表中的数据,且可以删除表,但不能执行RENAME操作。
- 在写入锁定被释放之前,其他会话不能读取和写入数据到表。
我们来详细了解WRITE锁的工作原理。
首先,从第一次会话中获取一个WRITE锁。
1 |
mysql> LOCK TABLE tbl WRITE; |
然后,在tbl表中插入一个新行。
1 2 |
mysql> INSERT INTO tbl(col) VALUES(11); Query OK, 1 row affected (0.02 sec) |
虽然写入成功了,但是不支持RENAME操作。
1 2 |
mysql> rename table tbl to tbl_new; ERROR 1192 (HY000): Can't execute the given command because you have active locked tables or an active transaction |
接下来,从tbl表中读取数据。
1 |
mysql> SELECT * FROM tbl; |
之后,从第二会话尝试写入和读取数据:
1 |
mysql> SELECT * FROM tbl; |
MySQL将这些操作置于等待状态,你可以使用SHOW PROCESSLIST语句进行查看。
最后,释放第一次会话的锁定。
1 |
UNLOCK TABLES; |
你将看到第二个会话中的所有操作执行成功。
LOCK TABLES对于VIEW加锁,LOCK TABLES语句会为VIEW中使用的所有基表加锁。对触发器使用LOCK TABLE,那么就会锁定触发器中所包含的全部表(any tables used in triggers are also locked implicitly)。