【问题标题】:Consistently update multiple rows in a table, isolating from concurrent interference一致更新表中的多行,隔离并发干扰
【发布时间】:2015-03-23 13:04:54
【问题描述】:

我的任务是同步位于不同数据库中的两个表。因此,对于源表中发生的每次插入、更新和删除,这些更改都必须复制到目标表中。目标表将是源表的克隆。为了实现这一点,我决定在源表中安装触发器。

但我非常担心这些更新的并发方面,因为多个用户同时使用这些表,有时触发器必须更新目标表中的多行。从要在触发器中执行的更改的角度来看,我非常相信逻辑是正确的,但不是关于隔离级别,因为我不是这方面的专家。

因此,我将向您展示负责对目标表进行插入和更新的触发器,并请您查看并发方面是否存在任何问题。但在此之前,让我向您展示表格和一些用例:

这是源表(为简单起见,假设目标表具有相同的结构):

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

因此,如您所见,只有一行受到更新的影响。现在假设我想将 id 1(即 NULL)的父 id 更改为指向 id 6(在原始行集中)。因此,更改后您将拥有:

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;

将解决当前行的问题,但不会解决需要更新的其他行。

如果有人告诉我如何正确地纠正触发器以同时正常工作,我将非常高兴。

提前谢谢你。

马科斯

【问题讨论】:

  • two tables living in differents databases. 不同的数据库是一个事务的一部分?您至少需要两阶段提交(实际上是两个同步事务)
  • @joop 使用 postgres_fdw [link] (postgresql.org/docs/9.3/static/postgres-fdw.html) 您可以使位于不同数据库中的两个表共享同一个事务。这就是我正在使用的。触发器已在事务下运行。
  • @joop 更具体地说,请参见 F.31.3 部分。 (postgresql.org/docs/9.3/static/postgres-fdw.html) 中的事务管理

标签: sql postgresql plpgsql isolation-level transaction-isolation


【解决方案1】:

也许使用事务可能会有所帮助...据我所知,它们会阻止对数据库的所有修改,直到所有操作都完成,因此它们会立即提交更改以避免不一致的更新。见transactions,它可能就是你要找的东西

【讨论】:

  • 触发器已经在应用程序代码启动的事务下运行,但事务不会将您与并发问题引起的问题隔离开来,例如 dirty read不可重复读取等(postgresql.org/docs/9.3/static/transaction-iso.html)。您必须正确编码查询和更新以避免这些问题。这就是我在这里寻求的。如果有人能指出上述触发器中的任何这些缺陷,我将不胜感激。
猜你喜欢
  • 1970-01-01
  • 2014-12-12
  • 1970-01-01
  • 1970-01-01
  • 2017-09-22
  • 1970-01-01
  • 1970-01-01
  • 2018-11-04
  • 1970-01-01
相关资源
最近更新 更多