Kay*_*Ess 4 postgresql constraint transaction data-integrity postgresql-8.4
我们有一个表来记录系统上发生的处理,我们需要确保只有一行处于“处理中”状态。
我想确保结果始终为零或一:
select count(id) from jobs where status in ('P', 'G');
Run Code Online (Sandbox Code Playgroud)
我们正在使用显式事务,因此理想情况下,此检查将在提交时进行,如果不变量不成立则中止事务。对于我们来说,处理任何偶尔会引发错误的异常处理比突然以多个“正在进行”的工作结束要容易得多。
该解决方案只需要与 Postgres 一起使用,因此我们很乐意为此采用非标准解决方案。我们目前使用 8.4,但如果有任何不同,我们将在某个时候升级到 9.x。
用 Postgres 9.1 & 9.2测试。其中大部分也应该适用于 8.4。
如果中间状态在单个事务的过程中不违反约束,则常量值上的部分 UNIQUE 索引可以完成这项工作。鉴于此测试用例:
CREATE TEMP TABLE jobs(jobs_id int primary key, status text);
INSERT INTO jobs (jobs_id, status)
VALUES (1, 'A'), (2, 'B'), (3, 'C'), (4, 'G');
Run Code Online (Sandbox Code Playgroud)
使用这个索引:
CREATE UNIQUE INDEX jobs_status_uni_idx ON jobs ((TRUE))
WHERE status in ('P', 'G');
Run Code Online (Sandbox Code Playgroud)
请注意值周围的附加括号TRUE
。
测试(一次一行):
INSERT INTO jobs (jobs_id, status) VALUES (5, 'G'); -- fails
INSERT INTO jobs (jobs_id, status) VALUES (5, 'P'); -- fails
DELETE FROM jobs WHERE status = 'G';
INSERT INTO jobs (jobs_id, status) VALUES (5, 'P'); -- succeeds
INSERT INTO jobs (jobs_id, status) VALUES (6, 'G'); -- fails
Run Code Online (Sandbox Code Playgroud)
有关更多解释的相关答案:
PostgreSQL 多列唯一约束和 NULL 值
DEFERRED
约束的高级案例理想情况下,此检查将在提交时进行
一个部分唯一索引不能被推迟,并始终立即检查。因此,继续上面的例子,这将失败:
BEGIN;
INSERT INTO jobs (jobs_id, status) VALUES (6, 'G'); -- fails immediately!
DELETE FROM jobs WHERE status = 'P';
COMMIT;
Run Code Online (Sandbox Code Playgroud)
示例是显式事务处理。您的客户端可能具有自动事务处理(autocommit
打开或关闭)。
如果你需要这个来工作,你需要一个DEFERRABLE
约束,INITIALLY DEFERRED
或者SET CONSTRAINTS ALL | 交易中的名称 DEFERRED。
非延迟唯一性约束
当 a
UNIQUE
orPRIMARY KEY
约束不可延迟时,每当插入或修改行时,PostgreSQL 都会立即检查唯一性。SQL 标准规定唯一性应该只在语句的末尾强制执行;例如,当单个命令更新多个键值时,这会有所不同。要获得符合标准的行为,请将约束声明为DEFERRABLE
但不延迟(即INITIALLY IMMEDIATE
)。请注意,这可能比立即唯一性检查慢得多。
但是, aUNIQUE CONSTRAINT
只能为列定义,不能为表达式定义。
为此目的添加一个冗余列并使其与触发器保持同步。像这样:
ALTER TABLE jobs ADD COLUMN status_uni boolean;
UPDATE jobs set status_uni = TRUE WHERE status in ('P', 'G'); -- rest stays NULL
Run Code Online (Sandbox Code Playgroud)
ALTER TABLE jobs ADD CONSTRAINT jobs_status_uni
UNIQUE(status_uni) DEFERRABLE INITIALLY DEFERRED;
Run Code Online (Sandbox Code Playgroud)
附加列很便宜,因为它填充了 NULL,现有的NULL 位图通常应该吞下它,而无需额外的物理磁盘空间。
NULL
值不违反每个定义的唯一约束。
创建触发器以始终保持列最新:
CREATE OR REPLACE FUNCTION trg_jobs_status()
RETURNS trigger AS
$func$
BEGIN
NEW.status_uni = (NEW.status IN ('G', 'P') OR NULL);
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
对于所有插入:
CREATE TRIGGER insbef
BEFORE INSERT ON jobs
FOR EACH ROW EXECUTE PROCEDURE trg_jobs_status();
Run Code Online (Sandbox Code Playgroud)
优化,只针对相关更新:
CREATE TRIGGER upbef
BEFORE UPDATE OF status, status_uni ON jobs
FOR EACH ROW EXECUTE PROCEDURE trg_jobs_status();
Run Code Online (Sandbox Code Playgroud)
现在你的交易会通过,只要最后状态是一致的:
BEGIN;
-- SET CONSTRAINTS jobs_status_uni IMMEDIATE; -- only for INITIALLY IMMEDIATE
INSERT INTO jobs (jobs_id, status) VALUES (6, 'G'); -- check deferred
DELETE FROM jobs WHERE status = 'P';
COMMIT; -- succeeds!
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
3173 次 |
最近记录: |