考虑一个假设的日志系统,它定期将传入的日志分组到
LogIndex
中。一次,只有一个Active
索引可以接收新日志。
如果在过去 1 小时内没有新日志到达,则有一个周期性的工作从 LogIndex
转换 Active
-> Closed
状态。
DB schema如下:
1. LogIndex (Id, State [Active|Closed])
2. Log (Id, LogIndexId, LogLine, LogTimestamp)
可能会有 2 个数据库事务并行运行的场景:
Transaction 1 : To insert a new log line
Query1. Select Id from LogIndex where State='Active' LOCK IN SHARE MODE;
Query2. Insert into Log values(Id, LongIndex.Id, LogLine, LogTimestamp);
事务 2:定期检查器以确定是否需要关闭索引
Query1. Select Id from LogIndex where State='Active' LOCK IN SHARE MODE;
Query2. Select LogTimestamp from Log where LogIndexId='<someLogId>' order by LogTimestamp desc Limit 1;
<If the LogTimestamp is more than 1 hour old, then>
Query3. Update LogIndex set State='Closed' where Id='<someLogId>'
我有几个问题从非常基本的开始:
Q1:当
Select
以LOCK IN SHARE MODE
运行时,事务中的查询是否仍然以交错方式执行?
Q2: 假设交错执行发生: 如果我们想避免使用像
Select...LOCK FOR UPDATE
这样的独占锁,那么我们如何序列化这些交易以避免由于以下顺序导致的不一致状态:
t1:
Transaction1 - Query1
-> 选择 Active
状态下的 LogIndex。一次只有 1 个 LogIndex 处于Active
状态。
t2:
Transaction2 - Query1
-> 选择 Active
状态下的 LogIndex
t3:
Transaction2 - Query2
-> 列出 LogIndex 中的所有日志
t4:
Transaction2 - Query3
-> 将 LogIndex 更新为 Closed
t5:
Transaction1 - Query2
-> Inserts Log record for LogIndex.Id just set as Closed
或者另一种可能的情况:
t1:
Transaction1 - Query1
-> 选择 Active
状态下的 LogIndex。一次只有 1 个 LogIndex 处于Active
状态。
t2:
Transaction2 - Query1
-> 选择 Active
状态下的 LogIndex
t3:
Transaction2 - Query2
-> 列出最后一个日志超过 1 小时的 LogIndex 中的所有日志
t4:
Transaction1 - Query1
-> 为 LogIndex.Id 插入日志记录,它处于 Active
状态
t5:
Transaction2 - Query3
-> 将 LogIndex 更新为 Closed
,即使在 t4 时插入了新日志。
有问题的数据库是 MySQL InnoDB,默认隔离级别设置为可重复读取。 谢谢!
BEGIN
、FOR UPDATEs
和COMMIT
。这两个步骤就足够了。
更多讨论。
代码可以改成一次做多行吗?如果是这样,那将提供更大的可扩展性,并且发生冲突的可能性要小得多。
一次“交易”需要多长时间?希望不到一秒钟。
通常的模式是
BEGIN;
SELECT ... FOR UPDATE; -- any rows that you don't way changed
...
UPDATE / INSERT / DELETE -- whatever is needed, in whatever tables
COMMIT;
Of the
SELECT(s)
fetch more than one row (say 10), have an ORDER BY
so they always in the same order. (这避免了某种类型的死锁。)
我不明白“选择处于活动状态的 LogIndex”——那是一行吗?所有“活动”行?一个限制?
注意:如果“关闭”任务足够快,您可能只需要其中一个。
另见“乒乓”技术,其中数据被插入一个表而另一个表被清空。然后他们交换:http://mysql.rjweb.org/doc.php/staging_table
暗示使用默认的隔离级别。
“列出最后一个日志超过 1 小时的 LogIndex 中的所有日志”——可以有多少个?一打 = 好的。一百万 = 在交易中可能太慢了。
Update LogIndex set State='Closed' WHERE id = ...
您可以使用SELECT
而不是JOIN
来折叠前面的WHERE
。