可以使用特殊语法来更新和删除应用时间时态表(见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_multi和 multirange_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。这样,真正的更新/删除只有在锁持有后才会开始,所有并发产生的残留都将可见。在更高的事务隔离级别下,则不需要这个锁。