常用高级特性
视图
假设天气记录和城市位置的组合列表对应用有用,但又不想每次需要使用它时都敲入整个查询。可以在该查询上创建一个视图,这会给该查询一个名字,可以像使用一个普通表一样来使用:
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 命令用 BEGIN
和 COMMIT
命令包围起来。因此银行事务看起来会是这样:
BEGIN;
UPDATE accounts SET balance = balance - 100.00
WHERE name = 'Alice';
-- etc etc
COMMIT;
如果,在事务执行中并不想提交(或许是注意到 Alice 的余额不足),可以发出 ROLLBACK
命令而不是 COMMIT
命令,这样所有目前的更新将会被取消。
AntDB 实际上将每一个 SQL 语句都作为一个事务来执行。如果没有发出 BEGIN
命令,则每个独立的语句都会被加上一个隐式的 BEGIN
以及(如果成功) COMMIT
来包围它。一组被 BEGIN
和 COMMIT
包围的语句也被称为一个事务块。
注意
某些客户端库会自动发出 BEGIN
和 COMMIT
命令,因此可能会在不被告知的情况下得到事务块的效果。具体请查看所使用的接口文档。
也可以利用保存点来以更细的粒度来控制一个事务中的语句。保存点允许有选择性地放弃事务的一部分而提交剩下的部分。在使用 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
子句产生并通过 WHERE
、GROUP BY
、HAVING
过滤的“虚拟表“。例如,一个由于不满足 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 BY
、HAVING
和 WHERE
子句中。这是因为窗口函数的执行逻辑是在处理完这些子句之后。另外,窗口函数在非窗口聚集函数之后执行。这意味着可以在窗口函数的参数中包括一个聚集函数,但反过来不行。
如果需要在窗口计算执行后进行过滤或者分组,可以使用子查询。例如:
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
继承了所有列(name
、population
和 elevation
)。 列 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
之下的其他表。很多已经讨论过的命令 — SELECT
、UPDATE
和 DELETE
— 都支持这个 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 SAVEPOINT
和 RELEASE SAVEPOINT
访问)。在其他方面, SAVEPOINT
完全符合 SQL。
ROLLBACK TO SAVEPOINT
ROLLBACK TO SAVEPOINT — 回滚到一个保存点
大纲
ROLLBACK [ WORK | TRANSACTION ] TO [ SAVEPOINT ] savepoint_name
描述
回滚在该保存点被建立之后执行的所有命令。该保存点保持有效并且可以在以后再次回滚到它(如果需要)。
ROLLBACK TO SAVEPOINT
隐式地销毁在所提及的保存点之后建立的所有保存点。
参数
savepoint_name
: 要回滚到的保存点。
注解
使用 RELEASE SAVEPOINT 销毁一个保存点而不抛弃在它建立之后被执行的命令的效果。
指定一个没有被建立的保存点是一种错误。
相对于保存点,游标有一点非事务的行为。在保存点被回滚时,任何在该保存点 内被打开的游标将会被关闭。如果一个先前打开的游标在一个保存点内被 FETCH
或MOVE
命令所影响,而该该保存点 后来又被回滚,那么该游标将保持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)