Sql 一致更新表中的多行,避免并发干扰
我的任务是同步位于不同数据库中的两个表。因此,对于源表中发生的每一次插入、更新和删除,都必须在目标表中复制这些更改。目标表将是源表的克隆。为了实现这一点,我决定在源表中安装触发器 但我非常关心这些更新的并发方面,因为多个用户同时使用表,有时触发器必须更新目标表中的多行。从触发器中要执行的更改的角度来看,我非常确信逻辑是正确的,但隔离级别不是这样,因为我不是这方面的专家 因此,我将向您展示对目标表上的插入和更新负责的触发器,并要求您查看并发方面是否存在任何问题。但在此之前,让我向您展示表格和一些用例: 为简单起见,这是源表,假设目标表具有相同的结构:Sql 一致更新表中的多行,避免并发干扰,sql,postgresql,plpgsql,isolation-level,transaction-isolation,Sql,Postgresql,Plpgsql,Isolation Level,Transaction Isolation,我的任务是同步位于不同数据库中的两个表。因此,对于源表中发生的每一次插入、更新和删除,都必须在目标表中复制这些更改。目标表将是源表的克隆。为了实现这一点,我决定在源表中安装触发器 但我非常关心这些更新的并发方面,因为多个用户同时使用表,有时触发器必须更新目标表中的多行。从触发器中要执行的更改的角度来看,我非常确信逻辑是正确的,但隔离级别不是这样,因为我不是这方面的专家 因此,我将向您展示对目标表上的插入和更新负责的触发器,并要求您查看并发方面是否存在任何问题。但在此之前,让我向您展示表格和一些用
CREATE TABLE SRC_DEPARTMENTS
(
ID_DEPARTMENT INT NOT NULL PRIMARY KEY,
NAME VARCHAR(80) NOT NULL,
ID_PARENT_DEPARTMENT INT,
HIERARCHY VARCHAR(50) NOT NULL,
ACTIVE BOOLEAN NOT NULL DEFAULT TRUE,
FOREIGN KEY (ID_PARENT_DEPARTMENT) REFERENCES SRC_DEPARTMENTS (ID_DEPARTMENT) ON DELETE CASCADE
);
现在假设目标表中有以下行:
ID_DEPARTMENT | ID_PARENT_DEPARTMENT | HIERARCHY
--------------+----------------------+----------
1 | | 1
2 | 1 | 1.2
3 | 2 | 1.2.3
4 | 3 | 1.2.3.4
5 | | 5
6 | 5 | 5.6
我想把id为6的上级部门改为id为4。更改后,行应为:
ID_DEPARTMENT | ID_PARENT_DEPARTMENT | HIERARCHY
--------------+----------------------+----------
1 | | 1
2 | 1 | 1.2
3 | 2 | 1.2.3
4 | 3 | 1.2.3.4
6 | 4 | 1.2.3.4.6
5 | | 5
因此,正如您所看到的,只有一行受到更新的影响。现在假设我想将ID1的父id更改为NULL,以指向原始行集中的ID6。因此,更改后,您将有:
ID_DEPARTMENT | ID_PARENT_DEPARTMENT | HIERARCHY
--------------+----------------------+----------
5 | | 5
6 | 5 | 5.6
1 | 6 | 5.6.1
2 | 1 | 5.6.1.2
3 | 2 | 5.6.1.2.3
4 | 3 | 5.6.1.2.3.4
因此,在本例中,我必须更新多行以更正层次结构
所以,我希望对多行的更改能够一致地执行,我想我的触发器没有考虑到这一点。这是触发因素:
CREATE OR REPLACE FUNCTION insert_update_department() RETURNS trigger AS $$
DECLARE
_id_parent_department INT;
_id_parent_department_changed BOOLEAN := FALSE;
_hierarchy VARCHAR(50);
_current_hierarchy VARCHAR(50);
BEGIN
IF TG_OP = 'UPDATE' AND (
NEW.NAME IS NOT DISTINCT FROM OLD.NAME AND
NEW.ID_PARENT_DEPARTMENT IS NOT DISTINCT FROM OLD.ID_PARENT_DEPARTMENT AND
NEW.ACTIVE IS NOT DISTINCT FROM OLD.ACTIVE) THEN
RETURN NULL;
END IF;
IF TG_OP = 'INSERT' OR NEW.ID_PARENT_DEPARTMENT IS DISTINCT FROM OLD.ID_PARENT_DEPARTMENT THEN
IF NEW.ID_PARENT_DEPARTMENT IS NULL OR NEW.ID_PARENT_DEPARTMENT = NEW.ID_PARENT_DEPARTMENT THEN
_id_parent_department := NULL;
ELSE
_id_parent_department := NEW.ID_PARENT_DEPARTMENT;
END IF;
IF _id_parent_department IS NULL THEN
_hierarchy := '';
ELSE
SELECT HIERARCHY || '.'
INTO _hierarchy
FROM DST_DEPARTMENTS
WHERE ID_DEPARTMENT = _id_parent_department;
END IF;
_hierarchy := _hierarchy || cast(NEW.ID_DEPARTMENT AS TEXT);
IF TG_OP = 'UPDATE' THEN
SELECT HIERARCHY || '.'
INTO _current_hierarchy
FROM DST_DEPARTMENTS
WHERE ID_DEPARTMENT = NEW.ID_DEPARTMENT;
UPDATE DST_DEPARTMENTS SET
HIERARCHY = _hierarchy || '.' || substr(HIERARCHY, length(_current_hierarchy) + 1)
WHERE HIERARCHY LIKE _current_hierarchy || '%';
END IF;
_id_parent_department_changed := TRUE;
END IF;
IF TG_OP = 'INSERT' THEN
INSERT INTO DST_DEPARTMENTS VALUES (
NEW.ID_DEPARTMENT,
_name,
_id_parent_department,
_hierarchy,
NEW.ACTIVE
);
ELSE
UPDATE DST_DEPARTMENTS SET
NAME = _name,
ID_PARENT_DEPARTMENT = CASE WHEN _id_parent_department_changed THEN _id_parent_department ELSE ID_PARENT_DEPARTMENT END,
HIERARCHY = CASE WHEN _id_parent_department_changed THEN _hierarchy ELSE HIERARCHY END,
ACTIVE = NEW.ACTIVE
WHERE ID_DEPARTMENT = NEW.ID_DEPARTMENT;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER z_insert_update_department
AFTER INSERT OR UPDATE ON SRC_DEPARTMENTS
FOR EACH ROW
EXECUTE PROCEDURE insert_update_department();
也许可以从以下内容更改这些行:
SELECT HIERARCHY || '.'
INTO _current_hierarchy
FROM DST_DEPARTMENTS
WHERE ID_DEPARTMENT = NEW.ID_DEPARTMENT;
为此:
SELECT HIERARCHY || '.'
INTO _current_hierarchy
FROM DST_DEPARTMENTS
WHERE ID_DEPARTMENT = NEW.ID_DEPARTMENT
FOR UPDATE;
将解决当前行的问题,但不解决需要更新的其他行的问题
如果有人告诉我怎样做才是正确的,我会很高兴纠正触发器,使其同时正常工作
先谢谢你
Marcos也许使用事务处理可能会有所帮助。。。据我所知,它们会阻止对数据库的所有修改,直到所有操作完成,因此它们会立即提交更改,以避免不一致的更新。看,它可能就是您正在寻找的两个位于不同数据库中的表。不同的数据库是一个事务的一部分吗?您至少需要两个阶段的提交,这两个阶段实际上是两个同步的transactions@joop使用postgres_fdw[link],您可以使位于不同数据库中的两个表共享同一事务。这就是我用的。触发器已在某个事务下运行。@joop更具体地说,请参见第F.31.3节。中的事务管理。触发器已在由应用程序代码启动的事务下运行,但事务不会将您与并发问题(如脏读、不可重复读等)隔离开来。您必须正确编写查询和更新代码以避免这些问题。这就是我在这里寻找的。如果有人能指出上述触发器中的任何缺陷,我将非常感激。