【发布时间】:2014-07-19 02:30:44
【问题描述】:
(请尽可能笼统地回答。但我在 MS SQL Server 和 MySql 工作,所以如果没有通用答案,请继续回答其中一个或两个。)
考虑在 SQL 数据库中实现的预订系统。我想确保在许多多个用户中,只有一个用户获得预订,而没有其他用户“认为”他们得到了预订。这是数据库工作中的经典并发问题,但我不确定最佳答案是什么。
具体说明:
假设每个用户都有一个 UserID。我们可以想象目前有几个用户正在尝试使用 UserID 值 1004、1005、1009 和 1011 进行预订。
假设资源和预订存储在表 SEATS 中。 我们可以想象 SEATS 表在某一时刻包含:
----- SEATS -----------------------------
SeatID UserID ResvTime
1 1017 2014.07.15 04:17:18.000
2 NULL NULL
3 NULL NULL
4 1012 2014.07.15 04:19:35.000
5 1003 2014.07.15 04:20:46.000
-----------------------------------------
现在假设“同时”,用户 1004 和 1005 尝试获取 SeatID 3。 我想知道什么 SQL 将正确地确保他们中只有一个获得席位,而另一个获得拒绝。在 T-SQL 中,我能想到的最简单的代码版本是:
PROC GRABSEAT @seatid INT, @userid INT, @obtained BIT OUTPUT
BEGIN
DECLARE @avail INT
SET @avail = (SELECT UserID FROM SEATS WHERE (SeatID = @seatid))
IF (@avail IS NULL)
BEGIN
UPDATE SEATS SET UserID = @userid, ResvTime = GETDATE() WHERE (SeatID = @seatid)
SET @obtained = 1
END
ELSE
SET @obtained = 0
END
但问题是如何防止这种情况允许多个并发用户都执行此 PROC,在同一个座位上获得 TRUE 返回(例如 SeatID = 3)。
例如,如果用户 1004 和 1005 几乎同时执行此 PROC,则他们可以在其中任何一个尝试设置 UserID 列之前执行 SELECT 并获取 @avail = NULL。然后他们都将运行 UPDATE 语句。假设没有更糟的结果,那么其中一个会覆盖另一个的集合,两者都会认为他们获得了席位,但实际上只有最后运行 UPDATE 语句的那个才会将他们的数据存储在 SEATS 表中。另一个将覆盖他们的数据。这被称为“丢失输入”问题。但是在 SQL 数据库中防止它的方法是什么?我一直假设每个单独的 SQL 语句都作为 TRANSACTION 执行。一个事务具有四个所谓的“ACID”属性。这些属性是我需要的。所以,我认为在 SQL 数据库中的答案是:
BEGIN TRANSACTION
EXCEUTE GRABSEAT @seatid= <id1>, @userid = <id2>, @obtained
COMMIT
通过这样做,我需要的主要属性(隔离)将保证不会发生我担心的交错执行。
但我看到文章说它根本没有那么简单。我认为各种文章指出的一个大问题是,并非每个 TRANSACTION 都真正以完全原子性和孤立性运行。因此,也许上述在 TRANSACTION 中的包装将无法达到预期的结果。如果没有,那需要什么?
【问题讨论】:
标签: mysql sql sql-server database acid