更新前的原子聚合检查
Atomic aggregate check before update
我有一个 table,其中包含许多记录、一个枚举列和一个高度多线程的环境。
CREATE TYPE status AS ENUM
('UNCONFIRMED', 'REGISTERED', 'VALIDATED', 'PAID');
我们的系统准确地允许 6000 validated
个状态。这个值是动态的,以后会增加但永远不会下降。
我们必须在允许更新之前检查验证记录的数量:
SELECT count(*) FROM table WHERE status='VALIDATED';
- 如果结果低于
< 6000
(一开始),我们将状态更改为
validated
.
- 如果结果是
>= 6000
,我们不改变状态,并且
抛出异常。
- 我们绝不能超过这个数字
我想:
- 隔离级别
- table锁定
- select 更新
- 更新 where + 子查询(见下文)
- 另一个?
UPDATE table
SET status='VALIDATED'
WHERE (
SELECT count(*)<6000 FROM table WHERE status='VALIDATED'
) AND id=:id;
执行此操作并保证数百个线程之间的原子性的最佳方法是什么?
我的建议:
(a) 创建一个专用的 status_counter
table,对 counter
列进行约束,检查其值是否低于限制 (6000) :
CREATE TABLE status_counter (status status primary key, counter integer CHECK (counter < 6000)) ;
(b) 设置计数器:
INSERT INTO status_counter
SELECT 'VALIDATED', count(*) FROM my_table WHERE status='VALIDATED' ;
(c) 在插入或更新新行时通过 table
上的触发器更新 status_counter
table :
CREATE OR REPLACE FUNCTION table_insert ()
RETURNS trigger LANGUAGE plpgsql AS
$$
BEGIN
IF NEW.status = 'VALIDATED'
THEN
UPDATE status_counter
SET counter = counter + 1
WHERE status='VALIDATED' ;
END IF ;
RETURN NEW ;
END ;
$$ ;
CREATE OR REPLACE TRIGGER table_insert BEFORE INSERT ON table
FOR EACH ROW EXECUTE FUNCTION table_insert() ;
CREATE OR REPLACE FUNCTION table_update ()
RETURNS trigger LANGUAGE plpgsql AS
$$
BEGIN
IF NEW.status IS NOT DISTINCT FROM 'VALIDATED'
AND OLD.status IS DISTINCT FROM 'VALIDATED'
THEN
UPDATE status_counter
SET counter = counter + 1
WHERE status='VALIDATED' ;
ELSEIF NEW.status IS DISTINCT FROM 'VALIDATED'
AND OLD.status IS NOT DISTINCT FROM 'VALIDATED'
THEN
UPDATE status_counter
SET counter = counter - 1
WHERE status='VALIDATED' ;
END IF ;
RETURN NEW ;
END ;
$$ ;
CREATE OR REPLACE TRIGGER table_update BEFORE UPDATE OF status ON table
FOR EACH ROW EXECUTE FUNCTION table_update() ;
(d) 从现在开始,每次用户在table
中插入或更新一行时,counter
都会触发更新,当counter
更新失败达到引发异常并阻止插入或更新行的限制。
数百个线程之间的原子性应该得到保证,因为它们都试图更新 table status_counter
.
中相同的唯一行
演示在 dbfiddle.
"update where + a subquery" 是不够的,您需要将其与 table 锁、咨询锁或更高的隔离级别相结合。否则它会允许多个更新同时完成并超过计数。
select 更新”似乎是个糟糕的主意。每次尝试都必须锁定 table 中的每一行,这会产生大量流失。而且它仍然可能会遗漏一些,具体取决于是否曾经使用 status='VALIDATED'
插入行
我有一个 table,其中包含许多记录、一个枚举列和一个高度多线程的环境。
CREATE TYPE status AS ENUM
('UNCONFIRMED', 'REGISTERED', 'VALIDATED', 'PAID');
我们的系统准确地允许 6000 validated
个状态。这个值是动态的,以后会增加但永远不会下降。
我们必须在允许更新之前检查验证记录的数量:
SELECT count(*) FROM table WHERE status='VALIDATED';
- 如果结果低于
< 6000
(一开始),我们将状态更改为validated
. - 如果结果是
>= 6000
,我们不改变状态,并且 抛出异常。 - 我们绝不能超过这个数字
我想:
- 隔离级别
- table锁定
- select 更新
- 更新 where + 子查询(见下文)
- 另一个?
UPDATE table
SET status='VALIDATED'
WHERE (
SELECT count(*)<6000 FROM table WHERE status='VALIDATED'
) AND id=:id;
执行此操作并保证数百个线程之间的原子性的最佳方法是什么?
我的建议:
(a) 创建一个专用的 status_counter
table,对 counter
列进行约束,检查其值是否低于限制 (6000) :
CREATE TABLE status_counter (status status primary key, counter integer CHECK (counter < 6000)) ;
(b) 设置计数器:
INSERT INTO status_counter
SELECT 'VALIDATED', count(*) FROM my_table WHERE status='VALIDATED' ;
(c) 在插入或更新新行时通过 table
上的触发器更新 status_counter
table :
CREATE OR REPLACE FUNCTION table_insert ()
RETURNS trigger LANGUAGE plpgsql AS
$$
BEGIN
IF NEW.status = 'VALIDATED'
THEN
UPDATE status_counter
SET counter = counter + 1
WHERE status='VALIDATED' ;
END IF ;
RETURN NEW ;
END ;
$$ ;
CREATE OR REPLACE TRIGGER table_insert BEFORE INSERT ON table
FOR EACH ROW EXECUTE FUNCTION table_insert() ;
CREATE OR REPLACE FUNCTION table_update ()
RETURNS trigger LANGUAGE plpgsql AS
$$
BEGIN
IF NEW.status IS NOT DISTINCT FROM 'VALIDATED'
AND OLD.status IS DISTINCT FROM 'VALIDATED'
THEN
UPDATE status_counter
SET counter = counter + 1
WHERE status='VALIDATED' ;
ELSEIF NEW.status IS DISTINCT FROM 'VALIDATED'
AND OLD.status IS NOT DISTINCT FROM 'VALIDATED'
THEN
UPDATE status_counter
SET counter = counter - 1
WHERE status='VALIDATED' ;
END IF ;
RETURN NEW ;
END ;
$$ ;
CREATE OR REPLACE TRIGGER table_update BEFORE UPDATE OF status ON table
FOR EACH ROW EXECUTE FUNCTION table_update() ;
(d) 从现在开始,每次用户在table
中插入或更新一行时,counter
都会触发更新,当counter
更新失败达到引发异常并阻止插入或更新行的限制。
数百个线程之间的原子性应该得到保证,因为它们都试图更新 table status_counter
.
演示在 dbfiddle.
"update where + a subquery" 是不够的,您需要将其与 table 锁、咨询锁或更高的隔离级别相结合。否则它会允许多个更新同时完成并超过计数。
select 更新”似乎是个糟糕的主意。每次尝试都必须锁定 table 中的每一行,这会产生大量流失。而且它仍然可能会遗漏一些,具体取决于是否曾经使用 status='VALIDATED'
插入行