问题描述
问题陈述@H_502_2@
在 Postgresql 10.7 中,我有一个查询,它在子选择中使用“SELECT .. FOR NO KEY UPDATE SKIP LOCKED”并聚合另一个表中的行。 在聚合表中使用 select 和 insert 同时执行两个事务我能够观察到状态,当插入成功时,但 select 没有获取它并且未处理。
重现步骤@H_502_2@
数据库设置:
CREATE TABLE customer (id SERIAL PRIMARY KEY);
CREATE TABLE transaction (id SERIAL PRIMARY KEY,customer_id INT REFERENCES customer(id),amount NUMERIC(15,2) NOT NULL);
CREATE TABLE balance (customer_id INT REFERENCES customer(id),2) NOT NULL,transaction_id INT NOT NULL REFERENCES transaction(id));
CREATE TABLE customer_balance_update (customer_id INT REFERENCES customer(id) UNIQUE,is_balance_updated BOOLEAN NOT NULL DEFAULT FALSE);
CREATE OR REPLACE FUNCTION customer_balance_update_trigger() RETURNS TRIGGER AS $function$
BEGIN
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
INSERT INTO customer_balance_update(customer_id,is_balance_updated)
VALUES (NEW.customer_id,TRUE)
ON CONFLICT (customer_id) DO UPDATE SET is_balance_updated = EXCLUDED.is_balance_updated;
END IF;
RETURN NULL;
END;
$function$ LANGUAGE plpgsql;
CREATE TRIGGER customer_balance_update_trigger
AFTER INSERT OR UPDATE
ON balance
FOR EACH ROW
EXECUTE PROCEDURE customer_balance_update_trigger();
CREATE OR REPLACE FUNCTION transaction_insert_trigger() RETURNS TRIGGER AS $function$
BEGIN
IF (TG_OP = 'INSERT') THEN
INSERT INTO balance(customer_id,amount,transaction_id)
VALUES (NEW.customer_id,NEW.amount,NEW.id);
END IF;
RETURN NULL;
END;
$function$ LANGUAGE plpgsql;
CREATE TRIGGER transaction_insert_trigger
AFTER INSERT
ON transaction
FOR EACH ROW
EXECUTE PROCEDURE transaction_insert_trigger();
INSERT INTO customer(id) VALUES (1);
INSERT INTO customer_balance_update(customer_id,is_balance_updated) VALUES (1,TRUE);
测试用例:
请注意,在每次执行之前,请确保 balance
表为空,并且 customer_balance_update
有 1 个客户的行,其中 is_balance_updated = TRUE
。
我有 Java 应用程序,它有 2 个线程:第一个用于选择,第二个用于插入。 在第一个线程中,我有以下伪代码(它作为一个事务执行,最后提交):
SELECT c.customer_id,COALESCE(SUM(b.amount),0) AS balance
FROM (SELECT customer_id AS customer_id
FROM customer_balance_update
WHERE is_balance_updated = TRUE AND customer_id = 1
FOR NO KEY UPDATE SKIP LOCKED) AS c
LEFT JOIN balance b ON c.customer_id = b.customer_id
GROUP BY c.customer_id
// store selected balance if result not empty
// if balance present execute this
UPDATE customer_balance_update
SET is_balance_updated = FALSE
WHERE customer_id = 1
在第二个线程中,我有以下伪代码(它作为另一个事务执行,最后提交):
INSERT INTO transaction(customer_id,amount) VALUES (1,1)
当两个线程完成时,我会做以下检查:
SELECT is_balance_updated FROM customer_balance_update WHERE customer_id = 1
我认为我可以观察到三种情况:
- 第一个线程中的 SELECT 速度更快,并且我已经锁定了
customer_balance_update
行,因此获取的余额为0.0
并且带有 INSERT 的线程只有在第一个线程提交后才会完成。在这种情况下,稍后选择的is_balance_updated
标志应该为 true。 - 在第二个线程中插入更快,在第一个线程中,在执行选择时,我跳过了锁定的行,没有选择任何内容。在这种情况下,提取的余额将为
null
,稍后选择的is_balance_updated
标志应为 true。 - 第二个线程中的 INSERT 甚至在第一个线程中的 select 之前执行,因此我选择了
1.0
金额的余额。稍后选择的is_balance_updated
标志应为 false。
但是,我能够观察到第四个案例:
-
is_balance_updated
是 false 并且在第一个线程余额中捕获的是0.0
。
有人知道这是怎么可能的吗?
实际上,当我将 SELECT .. FOR NO KEY UPDATE
和 SELECT ... FROM balance
拆分为两个连续查询时,我无法重现上述问题。
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)