假设我在表t中有一行ID等于1。
如果运行以下sql,我得到的结果等于1(当列c为0时):
SELECT NEXT_ID(1)
但是,如果运行此命令,我得到的结果是1,而不是0(因为表t中没有ID = 2的行):
SELECT NEXT_ID(2)
NEXT_ID函数:
CREATE FUNCTION NEXT_ID(id INT)
RETURNS VARCHAR(15)
BEGIN
DECLARE counter BIGINT DEFAULT 0;
UPDATE t SET c = (@counter := c +1) WHERE ID = id;
return @counter;
END;
我的目的是创建一个计数器,该计数器以原子操作的方式递增一个值。 那么,为什么在NEXT_ID(2)上得到的值大于0?似乎计数器变量已存储在会话中...
在多线程应用程序中使用安全吗?
答案 0 :(得分:1)
如果您DECLARE counter
,则不应使用@counter
来引用变量。在MySQL存储函数中,声明的变量没有@
符号。标记为@
的变量为user-defined variables。 @counter
是与counter
不同的变量,即使它们具有相同的拼写。
在多线程应用中这样做安全吗?当然,如果线程通过使用不同的id
值来递增不同的行,它们将不会冲突(假设id
是t
表的唯一键)。
即使多个线程使用相同的id
值,因此需要增加t
表中的同一行,也会发生的事情是首先到达那里,并锁定行,然后增加它。第二个线程将等待获得自己的锁,直到第一个线程提交其事务。然后,第二个线程将继续,然后将看到c
的增量值。
所以它很安全,但是它不允许很高的吞吐率。争用同一id
的线程将必须排队,并等待锁的当前持有者提交其事务。如果期望多个线程并行执行它们的工作,则会发现这是一个瓶颈。
这就是存在AUTO_INCREMENT
的原因,因为它短暂地锁定了计数器以生成新值,但是却立即释放了锁定,而不是等待调用者的事务完成。这样一来,并发线程就可以继续工作,而不必等待对方。
您无法通过AUTO_INCREMENT
操作来模拟UPDATE
的行为,这些操作必不可少。
发表您的评论
对不起,我忘记了:=
不适用于局部声明的变量。我对其进行了测试,发现了两种选择:
替代方法1:不必费心声明局部变量,只需使用用户定义的变量即可。
CREATE FUNCTION NEXT_ID(id INT)
RETURNS VARCHAR(15)
READS SQL DATA
BEGIN
UPDATE t SET c = (@counter := c +1) WHERE ID = id;
RETURN @counter;
END
替代2:使用局部变量,但将LAST_INSERT_ID()
设置为增量值。然后SET
将计数器局部变量设为该值。
CREATE FUNCTION NEXT_ID(id INT)
RETURNS VARCHAR(15)
READS SQL DATA
BEGIN
DECLARE counter BIGINT DEFAULT 0;
UPDATE t SET c = LAST_INSERT_ID(c +1) WHERE ID = id;
SET counter = LAST_INSERT_ID();
RETURN counter;
END;
我测试了这两种选择,它们都起作用。