我有一个包含 2000 行的资源表。 引擎是innodb。有一个“free_at”字段(已索引)。 对于每个请求,我需要锁定表,获取空闲资源(按“free_at”列排序),将该行更新为非空闲,然后释放锁定。
这是一个基本的池实现,我一直在使用,并且在 100-200 个连接和少于 1000 行(池中的资源)的情况下运行良好。
现在大约有 800 个进程不断地从表中请求资源(每个进程每 10-15 秒一次,因此平均可达 80\s)。
我的瓶颈是锁等待时间,每个请求的等待时间在 30 到 60 秒 (!) 之间。我确信我应该更改一些配置以使其锁定和释放更快。
我尝试将引擎类型更改为 MEMORY,但这并没有改善锁定等待时间。
我是否应该寻找另一个不基于 MySQL 且可以按优先级分配资源的池解决方案(在我的例子中是“free_at”字段)?
编辑:
我使用的锁定
LOCK TABLES table_name WRITE
然后选择
SELECT * FROM table_name WHERE (free_at < NOW() OR free_at is null) ORDER BY free_at ASC
更新“free_at”字段
UPDATE table_name SET free_at = NOW() + INTERVAL 5 MINUTE WHERE id= 1234
终于解锁了
UNLOCK TABLES
表架构
`resources` ( `id` int(11) NOT NULL AUTO_INCREMENT, `free_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `free_at` (`free_at`), ) ENGINE=InnoDB
最佳答案
您需要锁定表中的行,而不是整个表。正如您所发现的,锁定整个表不会扩展。
坚持使用 InnoDB。 MEMORY 和 MyISAM 访问方法不会执行此操作。
假设您的资源
表具有以下列(它可以有其他列):
resource_id int not null primary key
free int not null 1 means free, 0 means in use
free_at timestamp not null
假设您想要一个事务来获取最旧的可用资源行 (free
= 1),那么您需要对每个需要资源的客户端执行以下操作。
START TRANSACTION;
SELECT resource_id
FROM resource
WHERE free = 1
ORDER BY free_at ASC
LIMIT 1
FOR UPDATE;
此时,您的应用程序将获得一个您可以分配的resource_id,或者什么也得不到。如果没有得到,则意味着它必须等待并重试。
如果您有resource_id,请更新它以表明它正在使用中。
UPDATE resource
SET free = 0,
free_at = NOW()
WHERE resource_id = 'the resource ID you just got';
然后,尽快做
COMMIT;
完成您启动的事务并释放该行上的锁。这种分配资源的方式比锁定表效果更好,因为每个连接只需要锁定自己的行。
如果您没有从SELECT ... FOR UPDATE
查询中获取资源ID,您需要立即执行
ROLLBACK;
取消您在该行上启动的事务。然后你需要等待并重试。等待一段有意义的时间:至少是应用程序使用您的资源之一并释放它所花费的时间。如果您等待的时间较短,您的许多连接将占用资源表并减慢速度。如果您需要更多资源,请添加一些。
当您的软件使用完分配的资源后,执行此操作将其释放回池中。您可以在自动提交模式下执行此操作;这里不需要显式事务。但请确保您的客户端为此打开了自动提交模式。
UPDATE resource
SET free = 1
WHERE resource_id = 'the resource ID you have been using';
请注意;我不太明白你是如何使用 free_at
所以我可能有这部分逻辑错误。
通过在
上添加复合索引(覆盖索引),查找空闲资源的SELECT
操作很可能会加速。
(free, free_at, resource_id)
关于mysql - 调整 MySQL 配置以支持锁定同一表的数百个连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21390030/