我有一个简单的过程,我需要处理一个表的记录,理想情况下运行该过程的多个实例而不处理相同的记录。我使用MySQL完成此操作的方式相当普遍(尽管我认为令牌字段更像是黑客):
在表格中添加几个字段:
CREATE TABLE records (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
...actual fields...
processed_at DATETIME DEFAULT NULL,
process_token TEXT DEFAULT NULL
);
然后是一个简单的处理脚本:
process_salt = md5(rand()) # or something like a process id
def get_record():
token = md5(microtime + process_salt)
db.exec("UPDATE records SET process_token = ?
WHERE processed_at IS NULL LIMIT 1", token)
return db.exec("SELECT * FROM records WHERE token = ?", token)
while (row = get_record()) is valid:
# ...do processing on row...
db.exec("UPDATE records SET processed_at = NOW(), token = NULL
WHERE id = ?", row.id)
我正在使用PostgreSQL数据库的系统中实现这样的过程。由于MVCC,我知道Pg可以被认为比锁定更成熟 - 我可以在Pg而不是令牌字段中使用行锁定或其他功能吗?
答案 0 :(得分:1)
这种方法适用于PostgreSQL,但是当你每次更新两行时,它往往效率很低 - 每次更新需要两个事务,两次提交。使用commit_delay
并可能禁用synchronous_commit
可以稍微降低成本,但除非您的存储子系统上有非易失性回写缓存,否则它仍然不会很快。 / p>
更重要的是,因为你提交了第一个更新,所以无法区分仍在工作的工人和已经崩溃的工人之间的区别。如果所有工作人员都在本地计算机上,然后偶尔扫描丢失的PID,那么您可以将令牌设置为工作人员的进程ID,但这很麻烦且容易出现竞争条件,更不用说重复使用pid的问题了。
我建议您采用旨在解决这些问题的真实排队解决方案,如ActiveMQ,RabbitMQ,ZeroMQ等。PGQ也可能具有重要意义。< / p>
在事务性关系数据库中进行队列处理应该很容易,但实际上很难做得好并且做得对。在详细检查时,大多数看起来合理的“解决方案”实际上都会将所有工作序列化(因此只有一个排队工作人员在任何给定时间做任何事情)。
答案 1 :(得分:0)
您可以使用SELECT ... FOR UPDATE NOWAIT
获取行上的独占锁定,如果已锁定则报告错误。