1.Base基础/3.Icon图标/操作/search备份
1.Base基础/3.Icon图标/操作/search备份
EN
文档
关于AntDB
部署与升级
使用教程
运维
调优
工具和插件
高级服务
数据安全
参考
  • 文档首页 /
  • 快速入门 /
  • 常用高级特性

常用高级特性

更新时间:2024-07-01 14:39:45

视图

假设天气记录和城市位置的组合列表对应用有用,但又不想每次需要使用它时都敲入整个查询。可以在该查询上创建一个视图,这会给该查询一个名字,可以像使用一个普通表一样来使用:

CREATE VIEW myview AS
    SELECT city, temp_lo, temp_hi, prcp, date, location
        FROM weather, cities
        WHERE city = name;

SELECT * FROM myview;

对视图的使用是成就一个好的 SQL 数据库设计的关键方面。视图允许用户通过始终如一的接口封装表的结构细节,这样可以避免表结构随着应用的进化而改变。

视图几乎可以用在任何可以使用表的地方,在其他视图基础上创建视图也并不少见。

事务

事务是所有数据库系统的基础概念。事务最重要的一点是它将多个步骤捆绑成了一个单一的、要么全完成要么全不完成的操作。步骤之间的中间状态对于其他并发事务是不可见的,并且如果有某些错误发生导致事务不能完成,则其中任何一个步骤都不会对数据库造成影响。

例如,考虑一个保存着多个客户账户余额和支行总存款额的银行数据库。假设希望记录一笔从 Alice 的账户到 Bob 的账户的额度为 100.00 元的转账。在最大程度地简化后,涉及到的 SQL 命令是:

UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
UPDATE branches SET balance = balance - 100.00
    WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Alice');
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Bob';
UPDATE branches SET balance = balance + 100.00
    WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Bob');

这些命令的细节在这里并不重要,关键点是为了完成这个相当简单的操作涉及到多个独立的更新。的银行职员希望确保这些更新要么全部发生,或者全部不发生。当然不能发生因为系统错误导致 Bob 收到 100 元而 Alice 并未被扣款的情况。Alice 当然也不希望自己被扣款而 Bob 没有收到钱。需要一种保障,当操作中途某些错误发生时已经执行的步骤不会产生效果。将这些更新组织成一个事务就可以给这种保障。一个事务被称为是原子的:从其他事务的角度来看,要么整个发生要么完全不发生。

同样希望能保证一旦一个事务被数据库系统完成并认可,它就被永久地记录下来且即便其后发生崩溃也不会被丢失。例如,如果正在记录 Bob 的一次现金提款,当然不希望他刚走出银行大门,对他账户的扣款就消失。一个事务型数据库保证一个事务在被报告为完成之前它所做的所有更新都被记录在持久存储(即磁盘)。

事务型数据库的另一个重要性质与原子更新的概念紧密相关:当多个事务并发运行时,每一个都不能看到其他事务未完成的修改。例如,如果一个事务正忙着总计所有支行的余额,它不会只包括 Alice 的支行的扣款而不包括 Bob 的支行的存款,或者反之。所以事务的全做或全不做并不只体现在它们对数据库的持久影响,也体现在它们发生时的可见性。一个事务所做的更新在它完成之前对于其他事务是不可见的,而之后所有的更新将同时变得可见。

在 AntDB 中,开启一个事务需要将 SQL 命令用 BEGINCOMMIT 命令包围起来。因此银行事务看起来会是这样:

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
-- etc etc
COMMIT;

如果,在事务执行中并不想提交(或许是注意到 Alice 的余额不足),可以发出 ROLLBACK 命令而不是 COMMIT 命令,这样所有目前的更新将会被取消。

AntDB 实际上将每一个 SQL 语句都作为一个事务来执行。如果没有发出 BEGIN 命令,则每个独立的语句都会被加上一个隐式的 BEGIN 以及(如果成功) COMMIT 来包围它。一组被 BEGINCOMMIT 包围的语句也被称为一个事务块

注意

某些客户端库会自动发出 BEGINCOMMIT 命令,因此可能会在不被告知的情况下得到事务块的效果。具体请查看所使用的接口文档。

也可以利用保存点来以更细的粒度来控制一个事务中的语句。保存点允许有选择性地放弃事务的一部分而提交剩下的部分。在使用 SAVEPOINT 定义一个保存点后,可以在必要时利用 ROLLBACK TO 回滚到该保存点。该事务中位于保存点和回滚点之间的数据库修改都会被放弃,但是早于该保存点的修改则会被保存。

在回滚到保存点之后,它的定义依然存在,因此可以多次回滚到它。反过来,如果确定不再需要回滚到特定的保存点,它可以被释放以便系统释放一些资源。记住不管是释放保存点还是回滚到保存点都会释放定义在该保存点之后的所有其他保存点。

所有这些都发生在一个事务块内,因此这些对于其他数据库会话都不可见。当提交整个事务块时,被提交的动作将作为一个单元变得对其他会话可见,而被回滚的动作则永远不会变得可见。

记住那个银行数据库,假设从 Alice 的账户扣款 100 元,然后存款到 Bob 的账户,结果直到最后才发现应该存到 Wally 的账户。可以通过使用保存点来做这件事:

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Bob';
-- oops ... forget that and use Wally's account
ROLLBACK TO my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Wally';
COMMIT;

当然,这个例子是被过度简化的,但是在一个事务块中使用保存点存在很多种控制可能性。此外,ROLLBACK TO 是唯一的途径来重新控制一个由于错误被系统置为中断状态的事务块,而不是完全回滚它并重新启动。

窗口函数

一个窗口函数在一系列与当前行有某种关联的表行上执行一种计算。这与一个聚集函数所完成的计算有可比之处。但是窗口函数并不会使多行被聚集成一个单独的输出行,这与通常的非窗口聚集函数不同。取而代之,行保留它们独立的标识。在这些现象背后,窗口函数可以访问的不仅仅是查询结果的当前行。

下面是一个例子用于展示如何将每一个员工的薪水与他/她所在部门的平均薪水进行比较:

SELECT depname, empno, salary, avg(salary) OVER (PARTITION BY depname) FROM empsalary;
  depname  | empno | salary |          avg
-----------+-------+--------+-----------------------
 develop   |    11 |   5200 | 5020.0000000000000000
 develop   |     7 |   4200 | 5020.0000000000000000
 develop   |     9 |   4500 | 5020.0000000000000000
 develop   |     8 |   6000 | 5020.0000000000000000
 develop   |    10 |   5200 | 5020.0000000000000000
 personnel |     5 |   3500 | 3700.0000000000000000
 personnel |     2 |   3900 | 3700.0000000000000000
 sales     |     3 |   4800 | 4866.6666666666666667
 sales     |     1 |   5000 | 4866.6666666666666667
 sales     |     4 |   4800 | 4866.6666666666666667
(10 rows)

最开始的三个输出列直接来自于表 empsalary,并且表中每一行都有一个输出行。第四列表示对与当前行具有相同 depname 值的所有表行取得平均值(这实际和非窗口 avg 聚集函数是相同的函数,但是 OVER 子句使得它被当做一个窗口函数处理并在一个合适的窗口帧上计算)。

一个窗口函数调用总是包含一个直接跟在窗口函数名及其参数之后的 OVER 子句。这使得它从句法上和一个普通函数或非窗口函数区分开来。OVER 子句决定究竟查询中的哪些行被分离出来由窗口函数处理。OVER 子句中的 PARTITION BY 子句指定了将具有相同 PARTITION BY 表达式值的行分到组或者分区。对于每一行,窗口函数都会在当前行同一分区的行上进行计算。

可以通过 OVER 上的 ORDER BY 控制窗口函数处理行的顺序(窗口的 ORDER BY 并不一定要符合行输出的顺序)。下面是一个例子:

SELECT depname, empno, salary,
       rank() OVER (PARTITION BY depname ORDER BY salary DESC) FROM empsalary;
  depname  | empno | salary | rank
-----------+-------+--------+------
 develop   |     8 |   6000 |    1
 develop   |    10 |   5200 |    2
 develop   |    11 |   5200 |    2
 develop   |     9 |   4500 |    4
 develop   |     7 |   4200 |    5
 personnel |     2 |   3900 |    1
 personnel |     5 |   3500 |    2
 sales     |     1 |   5000 |    1
 sales     |     4 |   4800 |    2
 sales     |     3 |   4800 |    2
(10 rows)

如上所示,rank 函数在当前行的分区内按照 ORDER BY 子句的顺序为每一个可区分的 ORDER BY 值产生了一个数字等级。rank 不需要显式的参数,因为它的行为完全决定于 OVER 子句。

一个窗口函数所考虑的行属于那些通过查询的 FROM 子句产生并通过 WHEREGROUP BYHAVING 过滤的“虚拟表“。例如,一个由于不满足 WHERE 条件被删除的行是不会被任何窗口函数所见的。在一个查询中可以包含多个窗口函数,每个窗口函数都可以用不同的 OVER 子句来按不同方式划分数据,但是它们都作用在由虚拟表定义的同一个行集上。

已经看到如果行的顺序不重要时 ORDER BY 可以忽略。PARTITION BY 同样也可以被忽略,在这种情况下会产生一个包含所有行的分区。

这里有一个与窗口函数相关的重要概念:对于每一行,在它的分区中的行集被称为它的窗口帧。 一些窗口函数只作用在窗口帧中的行上,而不是整个分区。默认情况下,如果使用 ORDER BY,则帧包括从分区开始到当前行的所有行,以及后续任何与当前行在 ORDER BY 子句上相等的行。如果 ORDER BY 被忽略,则默认帧包含整个分区中所有的行。下面是使用 sum 的例子:

SELECT salary, sum(salary) OVER () FROM empsalary;
 salary |  sum
--------+-------
   5200 | 47100
   5000 | 47100
   3500 | 47100
   4800 | 47100
   3900 | 47100
   4200 | 47100
   4500 | 47100
   4800 | 47100
   6000 | 47100
   5200 | 47100
(10 rows)

如上所示,由于在 OVER 子句中没有 ORDER BY,窗口帧和分区一样,而如果缺少 PARTITION BY 则和整个表一样。换句话说,每个合计都会在整个表上进行,这样为每一个输出行得到的都是相同的结果。但是如果加上一个 ORDER BY 子句,会得到非常不同的结果:

SELECT salary, sum(salary) OVER (ORDER BY salary) FROM empsalary;
 salary |  sum
--------+-------
   3500 |  3500
   3900 |  7400
   4200 | 11600
   4500 | 16100
   4800 | 25700
   4800 | 25700
   5000 | 30700
   5200 | 41100
   5200 | 41100
   6000 | 47100
(10 rows)

这里的合计是从第一个(最低的)薪水一直到当前行,包括任何与当前行相同的行(注意相同薪水行的结果)。

窗口函数只允许出现在查询的 SELECT 列表和 ORDER BY 子句中。它们不允许出现在其他地方,例如 GROUP BYHAVINGWHERE 子句中。这是因为窗口函数的执行逻辑是在处理完这些子句之后。另外,窗口函数在非窗口聚集函数之后执行。这意味着可以在窗口函数的参数中包括一个聚集函数,但反过来不行。

如果需要在窗口计算执行后进行过滤或者分组,可以使用子查询。例如:

SELECT depname, empno, salary, enroll_date
FROM
  (SELECT depname, empno, salary, enroll_date,
          rank() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos
     FROM empsalary
  ) AS ss
WHERE pos < 3;

上述查询仅仅显示了内层查询中 rank 低于 3 的结果。

当一个查询涉及到多个窗口函数时,可以将每一个分别写在一个独立的 OVER 子句中。但如果多个函数要求同一个窗口行为时,这种做法是冗余的而且容易出错的。替代方案是,每一个窗口行为可以被放在一个命名的 WINDOW 子句中,然后在 OVER 中引用它。例如:

SELECT sum(salary) OVER w, avg(salary) OVER w
  FROM empsalary
  WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);

继承

继承是面向对象数据库中的概念,它展示了数据库设计的新的可能性。

创建两个表:表 cities和表 capitals。自然地,首都也是城市,所以需要有某种方式能够在列举所有城市的时候也隐式地包含首都。如果真的聪明,会设计如下的模式:

CREATE TABLE capitals (
  name       text,
  population real,
  elevation  int,    -- (in ft)
  state      char(2)
);

CREATE TABLE non_capitals (
  name       text,
  population real,
  elevation  int     -- (in ft)
);

CREATE VIEW cities AS
  SELECT name, population, elevation FROM capitals
    UNION
  SELECT name, population, elevation FROM non_capitals;

这个模式对于查询而言工作正常,但是需要更新一些行时就变得不好用了。

更好的方案是:

CREATE TABLE cities (
  name       text,
  population real,
  elevation  int     -- (in ft)
);

CREATE TABLE capitals (
  state      char(2) UNIQUE NOT NULL
) INHERITS (cities);

在这种情况下,一个capitals 的行从它的父亲 cities 继承了所有列(namepopulationelevation)。 列 name 的类型是 text,一种用于变长字符串的本地 AntDB 类型。 capitals 表有一个附加列,state 用于显示他们的州缩写。 在 AntDB 中,一个表可以从 0 个或者多个表继承。

例如,如下查询可以寻找所有海拔 500 尺以上的城市名称,包括州首都:

SELECT name, elevation
  FROM cities
  WHERE elevation > 500;

返回为:

   name    | elevation
-----------+-----------
 Las Vegas |      2174
 Mariposa  |      1953
 Madison   |       845
(3 rows)

在另一方面,下面的查询可以查找所有海拔高于 500 尺且不是州首府的城市:

SELECT name, elevation
    FROM ONLY cities
    WHERE elevation > 500;
   name    | elevation
-----------+-----------
 Las Vegas |      2174
 Mariposa  |      1953
(2 rows)

其中 cities 之前的 ONLY 用于指示查询只在 cities 表上进行而不会涉及到继承层次中位于 cities 之下的其他表。很多已经讨论过的命令 — SELECTUPDATEDELETE — 都支持这个 ONLY 记号。

子事物

子事物定义

在 AntDB 中,事务(Transaction)是执行一系列数据库操作的最小单位,它保证了操作的原子性、一致性、隔离性和持久性(ACID属性)。事务可以嵌套,也就是一个事务中可以包含另一个事务,这种事务被称为子事务。

子事务允许你在一个事务中执行多个操作,并在完成所有操作后提交或回滚整个事务。如果在子事务中的某个操作失败,整个父事务都会回滚,这样可以保证父事务的一致性。

在 AntDB 中,你可以使用 SAVEPOINT 语句在事务中创建一个保存点,然后回滚到这个保存点而不是整个事务。这样,只有子事务中的部分操作被回滚,而其他操作仍然保持有效。

集中式支持子事物

AntDB 集中式支持下述子事物相关操作。

SAVEPOINT

SAVEPOINT — 在当前事务中定义一个新的保存点

大纲

SAVEPOINT savepoint_name

描述

SAVEPOINT 在当前事务中建立一个新保存点。

保存点是事务内的一种特殊标记,它允许所有在它被建立之后执行的命令被 回滚,把该事务的状态恢复到它处于保存点时的样子。

参数

  • savepoint_name:给新保存点的名字。

注解

使用 ROLLBACK TO SAVEPOINT 回滚到一个保存点。 使用 RELEASE SAVEPOINT 销毁一个保存点, 但保持在它被建立之后执行的命令的效果。

保存点只能在一个事务块内建立。可以在一个事务内定义多个保存点。

示例

要建立一个保存点并且后来撤销在它建立之后执行的所有命令的效果:

CREATE TABLE table1(a int);
BEGIN;
    INSERT INTO table1 VALUES (1);
    SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (2);
    ROLLBACK TO SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (3);
COMMIT;

上面的事务将插入值 1 和 3,但不会插入 2。

要建立并且稍后销毁一个保存点:

BEGIN;
    INSERT INTO table1 VALUES (3);
    SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (4);
    RELEASE SAVEPOINT my_savepoint;
COMMIT;

上面的事务将插入 3 和 4。

兼容性

当建立另一个同名保存点时,SQL 要求之前的那个保存点自动被销毁。在 AntDB 中,旧的保存点会被保留,不过在进行 回滚或释放时只能使用最近的那一个(用 RELEASE SAVEPOINT释放较新的保存点将会 导致较旧的保存点再次变得可以被 ROLLBACK TO SAVEPOINTRELEASE SAVEPOINT访问)。在其他方面, SAVEPOINT完全符合 SQL。

ROLLBACK TO SAVEPOINT

ROLLBACK TO SAVEPOINT — 回滚到一个保存点

大纲

ROLLBACK [ WORK | TRANSACTION ] TO [ SAVEPOINT ] savepoint_name

描述

回滚在该保存点被建立之后执行的所有命令。该保存点保持有效并且可以在以后再次回滚到它(如果需要)。

ROLLBACK TO SAVEPOINT隐式地销毁在所提及的保存点之后建立的所有保存点。

参数

  • savepoint_name: 要回滚到的保存点。

注解

使用 RELEASE SAVEPOINT 销毁一个保存点而不抛弃在它建立之后被执行的命令的效果。

指定一个没有被建立的保存点是一种错误。

相对于保存点,游标有一点非事务的行为。在保存点被回滚时,任何在该保存点 内被打开的游标将会被关闭。如果一个先前打开的游标在一个保存点内被 FETCHMOVE命令所影响,而该该保存点 后来又被回滚,那么该游标将保持FETCH使它指向的位置(也就是说由FETCH导致的游标动作不会被回滚)。回滚也不能 撤销关闭一个游标。不过,其他由游标查询导致的副作用(例如 被该查询所调用的易变函数的副作用) 可以被回滚,只要它们发生在一个后来被回滚的保存点期间。 如果一个游标的执行导致事务中止,它会被置于一种不能被执行的状态,这样当 事务被用ROLLBACK TO SAVEPOINT恢复后,该游标也不再能被使用。

示例

要撤销在my_savepoint建立后执行的命令的效果:

ROLLBACK TO SAVEPOINT my_savepoint;

游标位置不会受保存点回滚的影响:

BEGIN;

DECLARE foo CURSOR FOR SELECT 1 UNION SELECT 2;

SAVEPOINT foo;

FETCH 1 FROM foo;
 ?column? 
----------
        1

ROLLBACK TO SAVEPOINT foo;

FETCH 1 FROM foo;
 ?column? 
----------
        2

COMMIT;

兼容性

SQL 标准指定关键词 SAVEPOINT是强制的,但是 AntDB 允许省略它。SQL 只允许WORK而不是TRANSACTION作为ROLLBACK之后的噪声词。 还有 SQL 有一个可选的子句 AND [ NO ] CHAIN,当前 AntDB 并不支持。在其他方面,这个命令符合 SQL 标准。

RELEASE SAVEPOINT

RELEASE SAVEPOINT — 销毁一个之前定义的保存点

大纲

RELEASE [ SAVEPOINT ] savepoint_name

描述

RELEASE SAVEPOINT销毁在当前事务中之前定义的一个保存点。

销毁一个保存点会使得它不能再作为一个回滚点,但是它没有其他用户 可见的行为。它不会撤销在该保存点被建立之后执行的命令的效果(要这样做,可见 ROLLBACK TO SAVEPOINT)。当不再需要一个保存点时销毁它允许系统在事务结束之前回收一些资源。

RELEASE SAVEPOINT也会销毁所有在该保存点建立之后建立的保存点。

参数

  • savepoint_name

    要销毁的保存点的名称。

注解

指定一个不是之前定义的保存点名称是错误。

当事务处于中止状态时不能释放保存点。

如果多个保存点具有相同的名称,只有最近被定义的那个会被释放。

示例

建立并且销毁一个保存点:

BEGIN;
    INSERT INTO table1 VALUES (3);
    SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (4);
    RELEASE SAVEPOINT my_savepoint;
COMMIT;

上述事务将插入 3 和 4。

兼容性

这个命令符合 SQL 标准。该标准指定关键词 SAVEPOINT是强制需要的,但 AntDB 允许省略。

分布式支持子事物

AntDB 只有 CN 节点可以运行子事物相关 SQL。其功能与集中式类似。但是下述场景排除在外。

  • 分布式不支持子事务内 set 参数
  • 分布式不支持 savepoint 之后 prepare transaction
  • 分布式不支持嵌套子事务
  • 分布式不支持匿名块,函数中定义子事物

示例如下:

-- 分布式不支持子事务内 set 参数
antdb=# BEGIN;
BEGIN
antdb=*# SAVEPOINT first_sp;
SAVEPOINT
antdb=*# SET vacuum_cost_delay TO 80.1;         --error
ERROR:  parameters cannot be set in subtransactions
antdb=!# rollback to SAVEPOINT first_sp;
ROLLBACK
antdb=*# release SAVEPOINT first_sp;
RELEASE
antdb=*# SET vacuum_cost_delay TO 80.1;         --ok
SET
antdb=*# rollback;
ROLLBACK


-- 分布式不支持 savepoint 之后 prepare transaction
antdb=# create table test_sp_prepared(a int, b text);
CREATE TABLE
antdb=# begin;
BEGIN
antdb=*# savepoint s1;
SAVEPOINT
antdb=*# prepare transaction 'x1';              --error
ERROR:  PREPARE TRANSACTION statement is not supported in transaction which has executed savepoint.
antdb=!# rollback to savepoint s1;
ROLLBACK
antdb=*# release s1;
RELEASE
antdb=*# insert into test_sp_prepared values (1,'aaa');
INSERT 0 1
antdb=*# update test_sp_prepared set b = 'bbb' where a = '1';
UPDATE 1
antdb=*# prepare transaction 'x1';               --error
ERROR:  PREPARE TRANSACTION statement is not supported in transaction which has executed savepoint.
antdb=!# rollback;
ROLLBACK
antdb=# drop table test_sp_prepared;
DROP TABLE


-- 分布式不支持嵌套子事务
antdb=# create table t_save(id int, num int);
CREATE TABLE
antdb=# BEGIN;
BEGIN
antdb=*#     INSERT INTO t_save VALUES (1,1);
INSERT 0 1
antdb=*#     SAVEPOINT s1;
SAVEPOINT
antdb=*#     INSERT INTO t_save VALUES (2,2);
INSERT 0 1
antdb=*#     SAVEPOINT s2;                              --error
ERROR:  do not support nested savepoint for now
antdb=!#     INSERT INTO t_save VALUES (3,3);
ERROR:  current transaction is aborted, commands ignored until end of transaction block
antdb=!#     ROLLBACK TO s1;
ROLLBACK
antdb=*#     INSERT INTO t_save VALUES (4,4);
INSERT 0 1
antdb=*# COMMIT;
COMMIT
antdb=# select * from t_save order by 1;
 id | num
----+-----
  1 |   1
  4 |   4
(2 rows)

antdb=# drop table t_save;
DROP TABLE


-- 分布式不支持匿名块中定义子事物
antdb=# -- 匿名块
antdb=# DROP TABLE if exists temp_table;
NOTICE:  table "temp_table" does not exist, skipping
DROP TABLE
antdb=# CREATE TABLE temp_table (id SERIAL PRIMARY KEY, name VARCHAR(50));
CREATE TABLE
antdb=# DO $$
antdb$# DECLARE
antdb$#   counter INTEGER := 0;
antdb$# BEGIN
antdb$#   -- 开始事务
antdb$#   BEGIN
antdb$#
antdb$#   -- 执行一系列操作
antdb$#   INSERT INTO temp_table (name) VALUES ('John'), ('Jane');
antdb$#
antdb$#   -- 增加计数器
antdb$#   counter := counter + 1;
antdb$#
antdb$#   -- 提交事务
antdb$#   -- COMMIT;
antdb$#   END;
antdb$#   -- 输出结果
antdb$#   RAISE NOTICE 'Inserted and updated % rows', counter;
antdb$# EXCEPTION
antdb$#   -- 处理异常
antdb$#   WHEN OTHERS THEN
antdb$#     -- 回滚事务
antdb$#     ROLLBACK;
antdb$#     RAISE NOTICE 'An error occurred: %', SQLERRM;
antdb$# END $$;
NOTICE:  An error occurred: cannot assign XIDs in child transaction 
DO
antdb=#
antdb=# select * from temp_table;          -- 表格仍为空,说明上述匿名块执行无效
 id | name
----+------
(0 rows)


-- 分布式不支持函数中定义子事物
antdb=# drop table if exists t1;
NOTICE:  table "t1" does not exist, skipping
DROP TABLE
antdb=# create table t1(a int, b text);
CREATE TABLE
antdb=#
antdb=# CREATE OR REPLACE FUNCTION custom_function() RETURNS VOID AS $$
antdb$# DECLARE
antdb$#   -- 声明变量或常量
antdb$# BEGIN
antdb$#   -- 开始事务
antdb$#   BEGIN
antdb$#     -- 在事务块内执行您的逻辑
antdb$#     -- 例如:
antdb$#     insert into t1 values (1,'aaa');
antdb$#     -- 其他数据库操作语句...
antdb$#   EXCEPTION
antdb$#     WHEN OTHERS THEN
antdb$#       -- 处理异常
antdb$#       RAISE NOTICE '发生异常: %', SQLERRM;
antdb$#   END;
antdb$#
antdb$#   -- 提交事务
antdb$# END;
antdb$# $$ LANGUAGE plpgsql;
CREATE FUNCTION
antdb=#
antdb=# select custom_function();     --NOTICE:  发生异常: cannot assign XIDs in child transaction
NOTICE:  发生异常: cannot assign XIDs in child transaction
 custom_function
-----------------

(1 row)

antdb=# select * from t1;            -- 表格仍为空,说明上述函数执行无效
 a | b
---+---
(0 rows)
问题反馈