开发版本: 19 / devel
此文档适用于不受支持的 PostgreSQL 版本。
您可能需要查看当前版本的相同页面,或上面列出的其他受支持版本。

6.4. 更新和删除时态数据 #

可以使用特殊语法来更新和删除应用时间时态表(见Section 5.7.1)。对于插入并不需要额外语法:用户只需像对待其他列一样提供应用时间值即可。在更新或删除时,用户可以针对历史的特定部分进行操作。只有与该历史重叠的行会受影响,而且在这些行中,只有被指定的历史部分会被更改。如果某一行在被指定范围之外还包含更多历史,那么它的应用时间会被缩减到指定部分之内,同时还会插入新行以保留未被指定的历史。

回顾Figure 5.1中的示例表,其数据如下:

 product_no | price |        valid_at
------------+-------+-------------------------
          5 |  5.00 | [2020-01-01,2022-01-01)
          5 |  8.00 | [2022-01-01,)
          6 |  9.00 | [2021-01-01,2024-01-01)

一个时态更新可能如下所示:

UPDATE products
  FOR PORTION OF valid_at FROM '2023-09-01' TO '2025-03-01'
  SET price = 12.00
  WHERE product_no = 5;

该命令会更新产品 5 的第二条记录。它会把价格设为 12.00,并把应用时间设为 [2023-09-01,2025-03-01)。然后,由于该行原来的应用时间是 [2022-01-01,),命令必须插入两个时态残留:一个用于 2023 年 9 月 1 日之前的历史, 另一个用于 2025 年 3 月 1 日之后的历史。更新后,表中产品 5 会有四行:

 product_no | price |        valid_at
------------+-------+-------------------------
          5 |  5.00 | [2020-01-01,2022-01-01)
          5 |  8.00 | [2022-01-01,2023-09-01)
          5 | 12.00 | [2023-09-01,2025-03-01)
          5 |  8.00 | [2025-03-01,)
          6 |  9.00 | [2021-01-01,2024-01-01)

新的历史可以画成Figure 6.1所示。

Figure 6.1. 时态更新示例


同样,在从表中删除行时,也可以针对历史的特定部分进行操作。在这种情况下,原始行会被移除, 但会插入新的时态残留以保留未被触及的历史。 时态删除的语法如下:

DELETE FROM products
  FOR PORTION OF valid_at FROM '2021-08-01' TO '2023-09-01'
  WHERE product_no = 5;

继续前面的示例,这个命令会删除两条记录。第一条记录会生成一个时态残留,而第二条记录则会被完全删除。 此时表中的行会变成:

 product_no | price |        valid_at
------------+-------+-------------------------
          5 |  5.00 | [2020-01-01,2021-08-01)
          5 | 12.00 | [2023-09-01,2025-03-01)
          5 |  8.00 | [2025-03-01,)
          6 |  9.00 | [2021-01-01,2024-01-01)

新的历史可以画成Figure 6.2所示。

Figure 6.2. 时态删除示例


除了使用FROM ... TO ...语法之外,时态更新/删除命令还可以直接在括号中给出目标范围/多范围。例如:DELETE FROM products FOR PORTION OF valid_at ('[2028-01-01,)') ...。当应用时间存储在多范围列中时,必须使用这种语法。

当应用时间存储在范围类型列中时,每个被更新/删除的行会产生零个、一个或两个时态残留。对于多范围列,则只会产生零个或一个时态残留。残留边界由range_minus_multimultirange_minus_multi计算得出(见Section 9.21)。

传给FOR PORTION OF的边界必须是常量。允许使用now()之类的函数,但不允许使用列引用。

在插入时态残留时,会触发所有INSERT触发器,但会跳过插入行的权限检查。

READ COMMITTED模式下,当两个并发事务同时触及同一行时,时态更新和删除可能产生意外结果。第二次更新或删除可能会完全或部分丢失。Figure 6.3展示了这一场景。会话 2 搜索要更改的行,并找到了会话 1 已经修改过的一行。它等待会话 1 提交。然后它重新检查该行是否仍然满足搜索条件(包括FOR PORTION OF所针对的起止时间)。会话 1 可能已经更改了这些时间,使它们不再符合条件。

此外,会话 1 插入的时态残留在会话 2 的事务中不可见,因为它们尚未提交。因此,会话 2 没有什么可更新/删除的对象:既没有被修改的行,也没有残留。会话 2 原本打算更改的那部分历史不会受到影响。

Figure 6.3. 时态隔离示例


要解决这些问题,应当在每次时态更新/删除之前,先用与之相同的条件(包括目标应用时间部分)执行一次SELECT FOR UPDATE。这样,真正的更新/删除只有在锁持有后才会开始,所有并发产生的残留都将可见。在更高的事务隔离级别下,则不需要这个锁。