我有一个名为seat的表,它有一个像这样的模式
ID,取
对于每个用户,我会随机取一个id并分配给该用户,这里为了简单起见,我将其设为= 1。我正在使用的查询
update seats u inner join (
SELECT id from seats
where taken is null limit 1) s
on s.id = u.id set taken = 1;
此查询采用随机的座位,取得标志为null,并为该座位生成标志1.虽然此查询工作正常,但此线程是否安全?
考虑这种情况,我有两个并行用户。对于user1,我选择行X,并且在更新查询运行之前,user2签入,对于该用户,select查询返回与user1相同的行。所以我将结束两次更新同一行。
此查询可以使用此方案吗?
答案 0 :(得分:2)
使用mysql,有一个简单,几乎令人难以置信的解决方案:
update seats set taken = 1
where taken is null
limit 1
此语法将受更新过程影响的行数限制为1,使用mysql对更新语法的特殊扩展来实现您的意图。
当然,作为官方支持的扩展,这是一个原子动作,完全是线程安全的。
虽然您的代码和问题都没有表明能够捕获哪个 ID是更新,但您可以通过用户定义变量捕获它:
update seats set
taken = 1,
id = (@id := id)
where taken is null
limit 1;
select @id id;
select返回的值将是更新的id,如果没有更新行,则返回null。
答案 1 :(得分:1)
只是为了记录,您可以在任何RDBMS中运行的所有SQL命令都是线程安全的,只要它是我们正在谈论的服务器并且每个线程都使用自己的连接。实际上,数据库服务器是为此目的而创建的。
两个或多个线程/进程同时访问数据库服务器可能遇到的唯一问题是死锁。这是一个你可以在程序逻辑中阻止的问题。当一个连接持有资源(数据库中的记录或表)并且要求另一个连接而另一个连接将以相反的顺序获取相同的两个资源时,发生死锁。这两个连接将等待另一个连接释放其理论上可以永久延伸的资源。 MySQL通过在一段有限的时间内授予锁定来解决此问题。
如果要防止死锁,最简单的解决方案是考虑获取锁的订单。这样两个连接就不可能面对面了。
回到你的SQL语句,我的回答与波希米亚语完全相同。如果你坚持认为它是随机的,你也可以:
UPDATE seats
SET
taken = 1
WHERE
taken IS NULL
ORDER BY RAND()
LIMIT 1
只是你需要知道WHERE taken IS NULL
是一件非常糟糕的事情。如果要搜索字段,则应始终为NOT NULL
,特别是如果涉及锁定。也许你可以指定0
代替NULL
,它会大大提升性能!