我正在研究计费系统(C#代码,MySQL Galera集群后端,InnoDB存储引擎),并对如何为发票生成真正唯一的序列号产生疑问。
通常在其他系统中,我创建了一个唯一的服务来获取发票号码,每当生成的发票我向该服务询问一个号码,并且该服务保证对持有该柜台的表的独占访问就不会问题一点。
但是这个新系统是为了高可用性而集群化的,所以这种方法是不可接受的,因为需要运行这些服务中的多个。
所以我在这里提到的逻辑就是:
如果我没有错,如果某个其他事务在当前事务完成之前更新了计数器,则提交将抛出异常,然后我可以重试该操作,这将确保发票号的顺序性。
所以我的问题是,哪一个是正确的隔离级别来实现这一目标?是READ_COMMITED还是可能产生重复?或者有一个更好的方法吗?
答案 0 :(得分:1)
实际上,如果你不小心,两种隔离级别都会给你带来麻烦。
除了技术差异(例如,他们锁定的行数),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
通常是一个安全的好选择(默认是有原因的);如果你降低它,你可能必须更加思考潜在的问题,但可能会获得一些性能/更少的阻塞。
您没有指定如何设置群集,显然您必须确保它们都使用相同的表或在主服务器上使用不同的偏移量。
对于您的问题,当另一个事务尝试更改值时会发生什么:第二个事务将在需要锁定资源的点等待,直到第一个事务释放它(通常在它准备就绪时),并且您将只获得一个达到超时(或检测到死锁)时发生异常。