PostgreSQL 11 preview - 支持 数组外键约束

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介:

标签

PostgreSQL , 约束 , foreign key , 数组 , 外键


背景

通常,数据库的外键约束是一种一行对一行的约束,然而,在某些场景中,可能要求一行匹配多行的约束。例如:

1、表1:商品表,商品ID为主键

2、表2:用户购买的商品列表,商品ID数组

要求表2数组内的每一个元素都在商品表ID字段中存在,同时要求在删除或更新商品表的商品ID时,可以影响到外键。

PostgreSQL本身支持一类外键(兼容SQL标准),同时支持数组类型,为了实现这个功能,同时性能足够的好,可以结合GIN倒排索引。

patch如下

https://commitfest.postgresql.org/17/1252/

数组外键约束语法

FOREIGN KEY (EACH ELEMENT OF column_name) REFERENCES table_name  

例如

+     CREATE TABLE drivers (  
+         driver_id integer PRIMARY KEY,  
+         first_name text,  
+         last_name text  
+     );  
+  
+     CREATE TABLE races (  
+         race_id integer PRIMARY KEY,  
+         title text,  
+         race_day date,  
+         final_positions integer[],  
+         FOREIGN KEY <emphasis> (EACH ELEMENT OF final_positions) REFERENCES drivers </emphasis>  
+     );  

ACTION目前仅支持NO ACTION或RESTRICT(理论上是可以支持其他action的。未来版本迭代,相信会支持其他action)

NO ACTION,如果有任何元素不在主表中,则报错,支持延迟CHECK,如事务结束时CHECK。

RESTRICT,同NO ACTION,但是不支持延迟CHECK。

+-- Allowed references with actions (NO ACTION, RESTRICT)  
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE NO ACTION, ftest2 int );  
+DROP TABLE FKTABLEFORARRAY;  
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE RESTRICT, ftest2 int );  
+DROP TABLE FKTABLEFORARRAY;  
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE NO ACTION, ftest2 int );  
+DROP TABLE FKTABLEFORARRAY;  
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE RESTRICT, ftest2 int );  
+DROP TABLE FKTABLEFORARRAY;  
+-- Not allowed references (SET NULL, SET DEFAULT, CASCADE)  
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET DEFAULT, ftest2 int );  
+ERROR:  Array Element Foreign Keys support only NO ACTION and RESTRICT actions  
+DROP TABLE IF EXISTS FKTABLEFORARRAY;  
+NOTICE:  table "fktableforarray" does not exist, skipping  
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET NULL, ftest2 int );  
+ERROR:  Array Element Foreign Keys support only NO ACTION and RESTRICT actions  
+DROP TABLE IF EXISTS FKTABLEFORARRAY;  
+NOTICE:  table "fktableforarray" does not exist, skipping  
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE CASCADE, ftest2 int );  
+ERROR:  Array Element Foreign Keys support only NO ACTION and RESTRICT actions  

性能相关

由于主表UPDATE或DELETE时,需要CHECK数组表内的元素是否都能匹配到主表的正在被变更的值,所以为了加速,需要高效的进行“是否满足约束条件”判断,使得主表的DML操作性能影响较小。

对于普通外键,我们使用普通索引就可以,而对于数组外键,可以用GIN索引,加速数组包含、相交的判断。

例如

1、更新需要满足

array_column @> array[主表正在变更的OLD.VALUE] == false  

2、删除需要满足

array_column @> array[主表正在删除的old.VALUE] == false  

3、延迟判定,可以使用批量操作,只判定一次

array_column && array[主表正在变更或删除的old.VALUEs] == false  

描述

+     <para>  
+      It is advisable to index the refrencing column using GIN index as it   
+      considerably enhances the performance. Also concerning coercion while using the   
+      GIN index:  
+          
+      <programlisting>  
+       CREATE TABLE pktableforarray ( ptest1 int2 PRIMARY KEY, ptest2 text );  
+       CREATE TABLE fktableforarray ( ftest1 int4[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );  
+      </programlisting>  
+      This syntax is fine since it will cast ptest1 to int4 upon RI checks,          
+  
+      <programlisting>  
+       CREATE TABLE pktableforarray ( ptest1 int4 PRIMARY KEY, ptest2 text );  
+       CREATE TABLE fktableforarray ( ftest1 int2[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );          
+      </programlisting>  
+       however, this syntax will cast ftest1 to int4 upon RI checks, thus defeating the  
+       purpose of the index.  
+     </para>  

元数据

1、获取约束定义

List of functions  
   Schema   |              Name              | Result data type | Argument data types | Type   
------------+--------------------------------+------------------+---------------------+------  
 pg_catalog | pg_get_constraintdef           | text             | oid                 | func  
 pg_catalog | pg_get_constraintdef           | text             | oid, boolean        | func  
 pg_catalog | pg_get_partition_constraintdef | text             | oid                 | func  
(3 rows)  

2、约束定义元数据表

               Table "pg_catalog.pg_constraint"  
    Column     |     Type     | Collation | Nullable | Default   
---------------+--------------+-----------+----------+---------  
 conname       | name         |           | not null |   
 connamespace  | oid          |           | not null |   
 contype       | "char"       |           | not null |   
 condeferrable | boolean      |           | not null |   
 condeferred   | boolean      |           | not null |   
 convalidated  | boolean      |           | not null |   
 conrelid      | oid          |           | not null |   
 contypid      | oid          |           | not null |   
 conindid      | oid          |           | not null |   
 confrelid     | oid          |           | not null |   
 confupdtype   | "char"       |           | not null |   
 confdeltype   | "char"       |           | not null |   
 confmatchtype | "char"       |           | not null |   
 conislocal    | boolean      |           | not null |   
 coninhcount   | integer      |           | not null |   
 connoinherit  | boolean      |           | not null |   
 conkey        | smallint[]   |           |          |   
 confkey       | smallint[]   |           |          |   
 conpfeqop     | oid[]        |           |          |   
 conppeqop     | oid[]        |           |          |   
 conffeqop     | oid[]        |           |          |   
 conexclop     | oid[]        |           |          |   
 conbin        | pg_node_tree |           |          |   
 consrc        | text         |           |          |   

3、约束类型解释(p, 普通外键约束。e, 数组元素外键约束)

pg_constraint.confreftype

+      <entry><structfield>confreftype</structfield></entry>  
+      <entry><type>char[]</type></entry>  
+      <entry></entry>  
+      <entry>If a foreign key, the reference semantics for each column:  
+       <literal>p</literal> = plain (simple equality),  
+       <literal>e</literal> = each element of referencing array must have a match  
+      </entry>  
+     </row>  

例子

+     CREATE TABLE available_moves (  
+         kind text,  
+         move text,  
+         description text,  
+         PRIMARY KEY (kind, move)  
+     );  
+  
+     CREATE TABLE paths (  
+         description text,  
+         kind text,  
+         moves text[],  
+         <emphasis>FOREIGN KEY (kind, EACH ELEMENT OF moves) REFERENCES available_moves (kind, move)</emphasis>  
+     );  
+  
+     INSERT INTO available_moves VALUES ('relative', 'LN', 'look north');  
+     INSERT INTO available_moves VALUES ('relative', 'RL', 'rotate left');  
+     INSERT INTO available_moves VALUES ('relative', 'RR', 'rotate right');  
+     INSERT INTO available_moves VALUES ('relative', 'MF', 'move forward');  
+     INSERT INTO available_moves VALUES ('absolute', 'N', 'move north');  
+     INSERT INTO available_moves VALUES ('absolute', 'S', 'move south');  
+     INSERT INTO available_moves VALUES ('absolute', 'E', 'move east');  
+     INSERT INTO available_moves VALUES ('absolute', 'W', 'move west');  
+  
+     INSERT INTO paths VALUES ('L-shaped path', 'relative', '{LN, RL, MF, RR, MF, MF}');  
+     INSERT INTO paths VALUES ('L-shaped path', 'absolute', '{W, N, N}');  
+-- EACH-ELEMENT FK CONSTRAINTS  
+CREATE TABLE PKTABLEFORARRAY ( ptest1 int PRIMARY KEY, ptest2 text );  
+-- Insert test data into PKTABLEFORARRAY  
+INSERT INTO PKTABLEFORARRAY VALUES (1, 'Test1');  
+INSERT INTO PKTABLEFORARRAY VALUES (2, 'Test2');  
+INSERT INTO PKTABLEFORARRAY VALUES (3, 'Test3');  
+INSERT INTO PKTABLEFORARRAY VALUES (4, 'Test4');  
+INSERT INTO PKTABLEFORARRAY VALUES (5, 'Test5');  
  
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{1,2},{6,NULL}}', 20);  
+ERROR:  insert or update on table "fktableforarraymdim" violates foreign key constraint "fktableforarraymdim_ftest1_fkey"  
+DETAIL:  Key (EACH ELEMENT OF ftest1)=({{1,2},{6,NULL}}) is not present in table "pktableforarray".  
+INSERT INTO FKTABLEFORARRAYNOTNULL VALUES (NULL, 21);  
+ERROR:  null value in column "ftest1" violates not-null constraint  
+DETAIL:  Failing row contains (null, 21).  
+-- Check FKTABLE  
+SELECT * FROM FKTABLEFORARRAY;  
+  ftest1  | ftest2   
+----------+--------  
+ {1}      |      3  
+ {2}      |      4  
+ {1}      |      5  
+ {3}      |      6  
+ {1}      |      7  
+ {4,5}    |      8  
+ {4,4}    |      9  
+          |     10  
+ {}       |     11  
+ {1,NULL} |     12  
+ {NULL}   |     13  
+(11 rows)  
+  
+-- Delete a row from PK TABLE (must fail due to ON DELETE NO ACTION)  
+DELETE FROM PKTABLEFORARRAY WHERE ptest1=1;  
+ERROR:  update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey" on table "fktableforarray"  
+DETAIL:  Key (EACH ELEMENT OF ptest1)=(1) is still referenced from table "fktableforarray".  
+-- Check FKTABLE for removal of matched row  
+SELECT * FROM FKTABLEFORARRAY;  
+  ftest1  | ftest2   
+----------+--------  
+ {1}      |      3  
+ {2}      |      4  
+ {1}      |      5  
+ {3}      |      6  
+ {1}      |      7  
+ {4,5}    |      8  
+ {4,4}    |      9  
+          |     10  
+ {}       |     11  
+ {1,NULL} |     12  
+ {NULL}   |     13  
+(11 rows)  
+  
+-- Update a row from PK TABLE (must fail due to ON UPDATE NO ACTION)  
+UPDATE PKTABLEFORARRAY SET ptest1=7 WHERE ptest1=1;  
+ERROR:  update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey" on table "fktableforarray"  
+DETAIL:  Key (EACH ELEMENT OF ptest1)=(1) is still referenced from table "fktableforarray".  

小结

PostgreSQL支持丰富的约束功能,包括:

  • 主键

  • 唯一键

  • NOT NULL

  • check

  • 外键

  • 排他

  • 数组外键

数组外键使用GIN索引,可以实现高效率的是否满足约束判定,使得业务系统又多了一重数据强约束的保障。

另一方面,主外键的锁粒度也进行了细化,早在2013年的版本中,就支持了4种行锁粒度,降低主外键数据操作的锁冲突。原理参考末尾文档。

参考

《会议室预定系统实践(解放开发) - PostgreSQL tsrange(时间范围类型) + 排他约束》

https://commitfest.postgresql.org/17/1252/

https://www.postgresql.org/docs/10/static/sql-createtable.html

《PostgreSQL add 2 DML LOCK TUPLE MODE to 4》

《PostgreSQL performance speedup by avoid lock references tuple when add or update(new) Foreign Key's value》

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
存储 SQL Oracle
AnalyticDB PostgreSQL 7.0 支持存储过程(CREATE PROCEDURE)特性
AnalyticDB PostgreSQL 7.0 新增了存储过程功能的支持,让用户在使用ADB PG时能够更方便高效地开发业务,并能够更好地兼容Oracle等传统数仓的业务。
494 1
AnalyticDB PostgreSQL 7.0 支持存储过程(CREATE PROCEDURE)特性
|
SQL 弹性计算 关系型数据库
PostgreSQL 12 preview - CTE 增强,支持用户语法层控制 materialized 优化
标签 PostgreSQL , CTE , materialized , not materialized , push down 背景 PostgreSQL with 语法,能跑非常复杂的SQL逻辑,包括递归,多语句物化计算等。 在12以前的版本中,WITH中的每一个CTE(common table express),都是直接进行物化的,也就是说外层的条件不会推到CTE(物化节点)里
1000 0
|
存储 SQL Oracle
10 PostgreSQL 表级复制-物化视图篇, 支持异地,异构如 Oracle 到 pg 的物化视图|学习笔记
快速学习10 PostgreSQL 表级复制-物化视图篇,支持异地,异构如 Oracle 到 pg 的物化视图
10 PostgreSQL 表级复制-物化视图篇, 支持异地,异构如 Oracle 到 pg 的物化视图|学习笔记
|
SQL 关系型数据库 PostgreSQL
PostgreSQL 数组类型
PostgreSQL 数组类型
149 0
|
搜索推荐 关系型数据库 PostgreSQL
postgresql 标签分组实战(可用于用户画像的实践)-数组篇
基于数组方式方面的基础应用,如有更大数据量的标签组合的时候,请参考下德哥写的文章 https://developer.aliyun.com/article/307731
311 0
|
SQL 并行计算 关系型数据库
Citus 分布式 PostgreSQL 集群 - SQL Reference(SQL支持和变通方案)
Citus 分布式 PostgreSQL 集群 - SQL Reference(SQL支持和变通方案)
186 0
|
关系型数据库 数据库 PostgreSQL
开发踩坑记录之三:PostgreSQL数据库表唯一性约束失效
在设计数据库表过程中,我们通常会对数据库表进行唯一性约束,以防止事务不一致导致的相同数据的重复插入问题。但是在实际开发中发现,即使设置了数据库表的唯一性约束,仍然出现了相同数据重复插入的问题。
|
弹性计算 关系型数据库 数据库
PostgreSQL 如何让心跳永远不死,支持半同步自动同步、异步升降级 - udf 心跳
标签 PostgreSQL , 同步 , 半同步 , 流复制 , 心跳 , 自动降级 , 自动升级 , dblink , 异步调用 背景 在心跳时,通过自定义UDF,实现心跳永远不被堵塞,并且支持更加当前的配置自动的进行同步、异步模式的升降级。实现半同步的功能。 UDF输入 1、优先模式(同步、异步) 2、同步等待超时时间 当优先为同步模式时,假设当前为同步配置,如果备库异常导致
1927 0
|
弹性计算 关系型数据库 数据库连接
PostgreSQL 12 preview - Move max_wal_senders out of max_connections for connection slot handling
标签 PostgreSQL , max_wal_senders , max_connections , sorry, too many clients already 背景 如果你需要使用PG的流复制,上游节点的max_wal_senders参数,用来限制这个节点同时最多可以有多少个wal sender进程。 包括逻辑复制、物理复制、pg_basebackup备份等,只要是使用stre
375 0

相关产品

  • 云原生数据库 PolarDB
  • 云数据库 RDS PostgreSQL 版