正确顺序计数器的隔离级别
本文关键字:隔离级 计数器 顺序 | 更新日期: 2023-09-27 18:18:32
我正在开发一个计费系统(c#代码,MySQL Galera集群后端,InnoDB存储引擎),并且对如何为发票生成真正唯一的序列号有疑问。
通常在其他系统中,我创建了一个唯一的服务来获取发票号码,每当生成发票时,我就向该服务请求一个号码,并且由于该服务保证对保存计数器的表的独占访问,因此根本没有问题。
但是这个新系统是为了高可用性而集群的,所以这种方法是不可接受的,因为需要多个服务同时运行。
我在这里应用的逻辑是这样的:
- <
- 开始事务/gh>
- 创建没有序列号的发票
- 检索串行计数器
- 向表 写入新的计数器<
- 更新发票/gh>
- 提交
如果我没有错,如果其他事务在当前事务完成之前更新了计数器,那么提交将抛出异常,然后我可以重试操作,这将确保发票号码的顺序性。
所以我的问题是,哪一个是正确的隔离级别来实现这一点?read_committed是否足够或可能产生重复?或者有更好的方法?
实际上,如果你不小心的话,这两种隔离级别都会给你带来麻烦。
除了技术上的差异(例如他们锁定了多少行),READ COMMITTED
和REPEATABLE READ
在处理以下情况方面的差异:
start transaction;
select no from counters where type = 'INVOICE';
-- some other session changes the value and commits it
select no from counters where type = 'INVOICE';
READ COMMITTED
会给你两个不同的结果,REPEATABLE READ
会给你两个select
的旧值。但是,这两种隔离级别都不能阻止任何人更改该值,所以您不希望出现这两种情况。
重要的是要锁定你要修改的行,这样就没有人可以修改它了:
start transaction;
select no from counters where type = 'INVOICE' for update;
update counters set no = @newvalue where type = 'INVOICE';
或者如果计算简单,则先执行实际更新:
start transaction;
update counters set no = no + 1 where type = 'INVOICE';
select no from counters where type = 'INVOICE';
假设您的表看起来像这样(并且您不查询例如select max(no) from invoices
以获得最后一个数字),两个隔离级别都将工作。它们的主要区别在于锁的行数。如果你在type
上有一个索引(在我的例子中),它们的行为将完全相同。
这个决定将取决于你剩下的查询。repeatable read
通常是一个好的和安全的选择(默认是有原因的);如果您降低它,您可能不得不更努力地考虑潜在的问题,但可能会获得一些性能/更少的阻塞。
你没有指定如何设置集群,显然你必须确保它们都使用相同的表或在你的主表上使用不同的偏移量。
对于你的问题,当另一个事务试图更改值时会发生什么:第二个事务将在需要锁定资源的点等待,直到第一个事务释放它(通常是在它准备好时),并且只有在达到超时(或检测到死锁)时才会得到异常。