是否可以锁定一个表,使持有者可以读写,而其他会话只能读取?
文档似乎建议读锁只允许每个人读,写锁只允许持有者读写,其他会话无权访问。似乎让持有者能够读写而其他会话只能阅读将是一种非常经常需要的行为——也许是最经常需要的行为。
也许在实施这种情况下对性能的影响会太高?
是否可以锁定一个表,使持有者可以读写,而其他会话只能读取?
文档似乎建议读锁只允许每个人读,写锁只允许持有者读写,其他会话无权访问。似乎让持有者能够读写而其他会话只能阅读将是一种非常经常需要的行为——也许是最经常需要的行为。
也许在实施这种情况下对性能的影响会太高?
现有答案中有很多正确的词,但似乎没有人给出明确的答案。我会尝试。
正如您在LOCK TABLES的文档中已经看到的那样,它不能用于此目的,因为用于READ
锁:
持有锁的会话可以读取表(但不能写入)。
和WRITE
锁:
只有持有锁的会话才能访问该表。在释放锁之前,没有其他会话可以访问它。
这就是使用任意引擎表几乎无法实现的效果,但可以使用事务引擎,即 InnoDB 来实现。
让我们考虑一下,单个会话对表保持恒定的写锁,而其他表可以根据事务从表中读取数据是什么意思。这意味着我们有一个开放的长期存在的事务(让它成为W
事务),它锁定一个表以进行修改,并且其他事务(在其他会话中)可以读取已经修改但尚未提交的数据。就隔离级别而言,这意味着我们应该将默认隔离级别设置为READ-UNCOMMITTED
,这样我们就不必为每个新会话更改隔离级别:
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
但是我们的事务W
,应该使用更强的隔离级别,否则我们不能对我们的表应用任何锁定。READ-COMMITTED
不够强大,但REPEATABLE-READ
正是我们想要的。那是在开始W
事务之前,我们应该为当前会话设置事务级别:
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
现在,如何锁定整个表。让我们创建一个表:
CREATE TABLE t (
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
val VARCHAR(45) NOT NULL,
PRIMARY KEY (id)
) ENGINE = InnoDB;
LOCK IN SHARE MODE不是我们想要的:
如果[读取的]这些行中的任何一个被另一个尚未提交的事务更改,则您的查询将等待该事务结束,然后使用最新值。
LOCK FOR UPDATE似乎可以满足我们的需要:
SELECT ... FOR UPDATE 锁定行和任何关联的索引条目。
现在我们只需要锁定行。我们能做的最简单的事情就是锁定主键。COUNT(*)
对 InnoDB 进行全索引扫描(因为 InnoDB 不知道确切的行数)。
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT COUNT(*) FROM t FOR UPDATE;
INSERT INTO t VALUES (NULL, '');
现在您可以打开其他会话并尝试从表中读取数据并尝试添加或修改这些会话中的现有数据。
但是问题是,您应该在 中提交修改W
,并且一旦提交事务,锁就会被释放,并且所有等待的插入或更新也会被应用,即使您使用以下方式提交它:
COMMIT AND CHAIN; SELECT COUNT(*) FROM ti FOR UPDATE;
这个故事的寓意是拥有两个 MySQL 帐户要容易得多:a) 具有 INSERT、UPDATE 和 DELETE GRANT 权限的写入帐户,以及 b) 没有的读取帐户。
有SELECT ... FOR UPDATE
,它将为其他调用者锁定行SELECT ... FOR UPDATE
,但不会为任何人锁定它SELECT
。UPDATE
s 也会等待锁。
当您想要获取一个值然后将更新推回而没有任何人更改该值并且您没有注意到时,这很有用。小心,添加太多会让你陷入僵局。
您可能会发现 InnoDB 引擎默认执行您需要的操作:写入不会阻塞读取。您需要注意事务隔离级别,以便在需要时可以进行写入。