mis*_*nry 6 sql postgresql triggers database-design sql-insert
我正在处理的问题领域是电子商务的退货管理。
我正在使用 Postgres (11.9) 并具有以下表格(我已从每个表中删除了一些与问题无关的字段):
CREATE TABLE "order" (
id BIGSERIAL PRIMARY KEY,
platform text NOT NULL,
platform_order_id text NOT NULL,
CONSTRAINT platform_order_id_unique UNIQUE (platform, platform_order_id)
);
CREATE TABLE order_item (
id BIGSERIAL PRIMARY KEY,
order_id int8 NOT NULL,
platform_item_id text NOT NULL,
quantity integer,
CONSTRAINT FK_order_item_order_id FOREIGN KEY (order_id) REFERENCES "order",
CONSTRAINT platform_item_id_unique UNIQUE (order_id, platform_item_id)
);
CREATE TABLE return (
id BIGSERIAL PRIMARY KEY,
order_id int8 NOT NULL,
CONSTRAINT FK_return_order_id FOREIGN KEY (order_id) REFERENCES "order"
);
CREATE TABLE return_item (
return_id int8 NOT NULL,
order_item_id int8 NOT NULL,
quantity integer NOT NULL,
CONSTRAINT FK_return_item_return_id FOREIGN KEY (return_id) REFERENCES return,
CONSTRAINT FK_return_item_item_id FOREIGN KEY (order_item_id) REFERENCES order_item
);
Run Code Online (Sandbox Code Playgroud)
为了简要解释该域,我从电子商务平台提取订单并将其存储在我的数据库中。订单由一个或多个带有quantity > 1. 当用户希望退货时,他们最多可以退回每次退货的数量。
更具体地说,如果我在一个订单中购买两件黑色小T恤,您会order在数据库中找到一个order_item数量为 的单件2。我将能够创建两个单独的回报,每个回报都有一个return_item引用相同order_item_id但数量为 1 的回报。
order_item并return_item插入到不同的事务中,并且我不会阻止多个事务同时更新其中任何一个事务。
如何确保特定的每个for quantityall的总和值不超过相应for with said中存储的数量?return_itemorder_item_idorder_itemid
用更简单的英语来说,当原始订单中第三件商品的数量为 2(如我所描述的示例中所示)时,如何防止该商品被退回?
在大多数情况下,编写应用程序检查来捕获此问题很容易,并且WHERE向我的return_item插入添加业务规则检查子句也不难,但是这些解决方案都没有为我提供唯一性约束所提供的一致性保证。我将如何编写一个触发器以在此处插入时出错?或者有比触发器更好的方法吗?
您专门要求触发解决方案。根据记录,您也可以使用纯 SQL 实现相同的目的,只要您可以确保所有客户端都使用必要的语句。相关示例:
您提到并发写入访问是可能的。这使得事情变得更加复杂。例如,两个事务可能会尝试同时从同一order_item事务返回一个项目。两者都检查并发现可以返回另外一项并执行此操作,从而超出了order_item.quantity1 的数量。经典并发警告。
为了防御它,您可以使用SERIALIZABLE事务隔离。但这要昂贵得多,并且所有可能写入相关表的事务都必须坚持它。
或者,在默认隔离级别中删除策略行锁READ COMMITTED。这是一个基本的实现:
触发功能:
CREATE FUNCTION trg_return_item_insup_bef()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
DECLARE
_ordered_items int;
_remaining_items int;
BEGIN
SELECT quantity
FROM order_item
WHERE id = NEW.order_item_id
FOR NO KEY UPDATE -- lock the parent row first ... (!!!)
INTO _ordered_items; -- ... while fetching quantity
SELECT _ordered_items - COALESCE(sum(quantity), 0)
FROM return_item
WHERE order_item_id = NEW.order_item_id
INTO _remaining_items;
IF NEW.quantity > _remaining_items THEN
RAISE EXCEPTION 'Tried to return % items, but only % of % are left.'
, NEW.quantity, _remaining_items, _ordered_items;
END IF;
RETURN NEW;
END
$func$;
Run Code Online (Sandbox Code Playgroud)
扳机:
CREATE TRIGGER insup_bef
BEFORE INSERT OR UPDATE ON return_item
FOR EACH ROW
EXECUTE PROCEDURE trg_return_item_insup_bef();
Run Code Online (Sandbox Code Playgroud)
db<>在这里摆弄
任何返回项目的尝试都会order_item首先锁定父行。竞争事务必须等到这一事务被提交,然后才能看到新提交的行。这消除了竞争条件。FOR NO KEY UPDATE是正确的锁定强度。既不能太弱也不能太强。
写入order_item也会干扰项目总数。但它们也会(隐式地)取出写锁,并被迫以相同的方式排队。但如果以后可以更新order_item.quantity,您将必须在触发器中添加类似的检查(以防其降低)。
我将基本信息添加到超出数量时引发的错误消息中。您可以在那里放置更多或更少的信息。
示例设置可以优化。“order”是保留字。该表return在示例中毫无用处,也是如此return_item.return_id。PK 中缺失return_item。order_item.quantity应该NOT NULL CHECK (quantity > 0)。COALESCE在正确的实现中,触发函数是多余的。但这些都是次要的注释。
| 归档时间: |
|
| 查看次数: |
1025 次 |
| 最近记录: |