KingbaseES 约束
什么是约束
约束是限制数据库中值的规则,除了数据类型提供的限制之外,对可接受值的附加要求,就是更窄的限制条件。
约束是对表上的数据列,强制执行的规则,防止将无效数据输入数据库,保证了数据的准确性和可靠性。这通常是基于应用程序提供的附加上下文,反应字段的特性。例如,age字段使用int数据类型来存储整数,但是,部分合法的整数范围作为有效年龄,则没有意义,比如负整数就是不合理的。这种情况下,可以使用约束来表达这个逻辑需求。
作为应用程序开发人员,可以在应用程序中构建此类逻辑。但是约束将此逻辑添加到数据库中,可以长期保护数据,免受无理的数据,或运行未知程序,产生不正常且不符合逻辑的数据。
约束对于在INSERT语句中捕获异常值,也非常有用,比如应用程序中的未考虑的漏洞。
如何定义约束
KingbaseES 数据库允许您创建六种类型的约束并允许您以两种方式声明它们。
- NOT NULL Constraint - 确保列不能有 NULL 值。
- UNIQUE Constraint - 确保列中的所有值都不同。
- PRIMARY Key - 唯一标识数据库表中的每一行/记录。
- FOREIGN Key - 基于其他表中的列约束数据。
- CHECK Constraint - CHECK 约束确保列中的所有值都满足特定条件。
- EXCLUSION Constraint - EXCLUDE 约束确保如果使用指定的运算符,在指定的列或表达式上比较任意两行,则并非所有这些比较都将返回 TRUE。
可以通过两种方式在语法上定义约束:
- 列级别,作为单个列属性定义的一部分。
- 表级别,作为表定义的一部分。
几乎所有的约束都可以在这两种形式中使用而无需修改,只有NOT NULL不能用作表约束。但是,可以使用IS NOT NULL语句的CHECK表约束,可以达到类似的限制效果。
约束子句可以用在以下语句中:
- CREATE TABLE
- ALTER TABLE
列约束
列约束是附加到单个列的约束,用于确定列的插入值是否有效。根据基本数据类型要求检验输入值后,再进行评估。
列约束非常适合表达仅限于单个字段的需求。他们将约束条件直接附加到所涉及的列。例如,我们可以通过在列名和数据类型之后添加约束来对表中的age限制进行建模: Employee
CREATE TABLE Employee (
. . .
age int CHECK (age >= 0),
. . .
);
此代码段定义了一个 Employee 表,其中一列是int类型的age。age必须大于或等于零。列约束很容易理解,作为附加要求,添加到作用的列上的。
表约束
表约束可以表达列约束可以表达的任何限制,尤其是表达涉及多于一列的限制。表约束不是附加到特定列,而是定义为表的单独组件,并且可以引用表的任何列。
我们之前看到的列约束可以表示为这样的表约束:
CREATE TABLE Employee (
. . .
age int,
. . .
CHECK (age >= 0)
);
使用相同的基本语法,但约束单独列出。为了利用表约束引入复合约束的能力,我们可以使用逻辑AND运算符来连接来自不同列的多个条件。
例如,在表 Employee_promot 存储职工升职数据,要求是岗位时长大于2年和获得过奖励的职工数据:
CREATE TABLE Employee_promot (
. . .
postyear int ,
reward boolean,
. . .
CHECK ( postyear > 2 AND reward )
);
这里需要 CHECK约束, 来检查 postyear > 2,并且reward 为真 。由于要检查多个列,因此需要表约束。
示例中主要使用创建新表时定义约束规则,但也可以使用ALTER TABLE。 使用ALTER TABLE时,默认情况下,新约束会根据新约束检查表中已有的数据值,但是使用 NOT VALID 跳过约束限制。
ALTER TABLE Employee_promot ADD CONSTRAINT chk_emp_promot CHECK ( postyear > 2 AND reward )NOT VALID;
ALTER TABLE Employee_promot VALIDATE CONSTRAINT chk_emp_promot;
为约束创建名称
默认约束名称
当使用上述语法创建约束时,KingbaseES 会自动选择一个较为合理的名称。在上 Employee_promot 表的情况下,会将约束命名为Employee_promot_check:
INSERT INTO Employee_promot VALUES (14, false);
ERROR: new row for relation "employee_promot" violates check constraint "employee_promot_check"
DETAIL: Failing row contains (4, f).
当违反约束时,此名称为您提供有关表和约束类型的信息。但是,在表上存在多个约束的情况下,更具描述性的名称有助于帮助进行故障排除。
自定义约束名称
可以通过在约束定义前,加上CONSTRAINT关键字和名称,来指定约束的名称。
例如,如果你想在qualified_borrowers table中命名约束loan_worthiness,你可以像这样定义 table:
CREATE TABLE employee_promot (
. . .
postyear int ,
reward boolean,
. . .
CHECK chk_emp_promot ( postyear > 2 AND reward )
);
现在,当我们违反约束时,我们会得到更具描述性的标签:
INSERT INTO employee_promot VALUES (4, false);
ERROR: new row for relation "employee_promot" violates check constraint "chk_emp_promot"
DETAIL: Failing row contains (4, f).
同样以相同的方式命名列约束:
CREATE TABLE employee (
. . .
age int CONSTRAINT is_workager CHECK (age >= 22 AND age <= 60),
. . .
);
KingbaseES 的可用约束列表
上述介绍了约束如何工作的一些基础知识,可以更深入地了解哪些约束可用,以及如何使用它们。
CHECK约束
CHECK约束是一种通用约束,表中的每一行必须满足的条件。为了满足约束条件,表中的每一行都必须使条件为TRUE或未知(由于为 NULL )。当评估特定行的约束条件时,条件中的列都是引用该行中的列值。
KingbaseSE 不验证CHECK约束条件是否互斥。因此,在同一列上,创建多个CHECK约束,请仔细设计check表达式,以免发生逻辑冲突,同时CHECK约束的评估顺序是不固定的。
CHECK 约束以关键字开头,CHECK然后提供括在括号中的表达式。对于列约束,它放在数据类型声明之后。对于表约束,可以将它们放置在定义它们与之交互的列之后的任何位置。
例如,我们可以创建一个 novel_candidate 表,其中包含已被候选并有资格获得年度长篇奖的小说:
CREATE TABLE novel_candidate (
title text,
director varchar(250),
release_date date CHECK ('2021-01-01' <= release_date AND release_date <= '2021-12-31'),
length int,
votes int,
CHECK (votes >= 10 AND length >= 40)
);
有一个列检查约束来检查它release_date是否在 2021 年之内。之后,我们有一个表格检查约束,以确保电影已获得足够的票数进入候选,并且长度符合“超长”类别的要求。
评估检查约束时,可接受的值返回true。如果新记录的值满足所有类型要求和约束,则该记录将被添加到表中:
INSERT INTO novel_candidate VALUES (
'A great novel',
'Talented director',
'2021-05-21',
117,
45
);
INSERT 0 1
产生false的值会产生错误,指示不满足约束:
INSERT INTO film_nominations VALUES (
'A poor novel',
'Misguided director',
'2021-10-24',
128,
1
);
ERROR: new row for relation "film_nominations" violates check constraint "film_nominations_check"
DETAIL: Failing row contains (A poor film, Misguided director, 2021-07-16, 128, 1).
在这种情况下,除了所需的票数外,这部长篇小说满足了所有条件。数据库拒绝提交,因为它没有通过最终表检查约束。
非空约束
NOT NUL 约束保证列中的值不为空。虽然这是一个简单的约束,但是会非常频繁地使用。
如何在 KingbaseES 中添加非空约束
不能保存空值的列,需要在类型声明后添加 NOT NULL :
CREATE TABLE national_capitals (
country text NOT NULL,
capital text NOT NULL,
);
这是一个只有两列的表,国家和首都。由于这两个都是必填字段,留空没有意义,因此我们添加了NOT NULL约束。
现在插入空值会导致错误:
INSERT INTO national_capitals VALUES (
NULL,
'Beijing',
);
ERROR: null value in column "country" violates not-null constraint
DETAIL: Failing row contains (null, Beijing).
该NOT NULL约束仅用作列约束(不能用作表约束)。但是,您可以通过在表CHECK约束中使用 IS NOT NULL,来轻松解决此问题。
例如,这提供了使用表约束的等效保证:
CREATE TABLE national_capitals (
country text,
capital text,
CHECK (country IS NOT NULL AND capital IS NOT NULL)
);
UNIQUE约束
UNIQUE约束告诉列中的每个值都不能重复。可以用于许多场景中,在这些场景中应该不可能在多条记录中具有相同的值。
例如,根据定义,处理任何类型 ID 的列都应该具有唯一值。如果身份证护照号码、学生或客户ID或产品条形码,无法区分特定的人或物品,它们将毫无用处。
可以UNIQUE在列级别指定约束:
CREATE TABLE supplies (
supply_id integer UNIQUE,
name text,
inventory integer
);
它们也可以被指定为表约束:
CREATE TABLE supplies (
supply_id integer,
name text,
inventory integer,
UNIQUE (supply_id)
);
使用表约束的优点之一,UNIQUE约束是对列组合,执行唯一性检查。一起评估的两个或更多列来工作,各个列中的值可以重复,但指定的列值组合必须是唯一的。
作为一个例子,让我们回顾一下national_capitals我们之前使用的表格:
CREATE TABLE national_capitals (
country text NOT NULL,
capital text NOT NULL,
);
如果我们想确保不会为同一对添加多条记录,我们可以UNIQUE在此处为列添加约束:
CREATE TABLE national_capitals (
country text NOT NULL UNIQUE,
capital text NOT NULL UNIQUE,
);
这将确保国家和首都在每个表格中只出现一次。但是,有些国家有多个首都。这意味着可能有多个具有相同country值的记录。这些不适用于当前设计:
INSERT INTO national_capitals VALUES (
'Bolivia',
'Sucre'
);
INSERT INTO national_capitals VALUES (
'Bolivia',
'La Paz'
);
INSERT 0 1
ERROR: duplicate key value violates unique constraint "national_capitals_country_key"
DETAIL: Key (country)=(Bolivia) already exists.
如果我们仍然想确保在允许单个列中重复值的同时不会出现重复记录,那么对country和的组合进行唯一检查capital就足够了:
CREATE TABLE national_capitals (
country text,
capital text,
UNIQUE (country, capital)
);
现在,我们可以将玻利维亚的两个首都都添加到表中而不会出现错误:
INSERT INTO national_capitals VALUES (
'Bolivia',
'Sucre'
);
INSERT INTO national_capitals VALUES (
'Bolivia',
'La Paz'
);
INSERT 0 1
INSERT 0 1
但是,尝试添加相同的组合两次仍然会受到约束:
INSERT INTO national_capitals VALUES (
'Bolivia',
'Sucre'
);
INSERT INTO national_capitals VALUES (
'Bolivia',
'Sucre'
);
INSERT 0 1
ERROR: duplicate key value violates unique constraint "national_capitals_country_capital_key"
DETAIL: Key (country, capital)=(Bolivia, Sucre) already exists.
PRIMARY KEY约束
PRIMARY KEY约束有特殊用途,可用于唯一标识表中的一条记录。这意味着它必须可靠地唯一,并且每条记录都必须在该列中有一个值。
表的主键不是必须的,但每张表只能有一个主键。主键主要用于识别、检索、修改或删除表中的单个记录。
以supplie表为例:
CREATE TABLE supplies (
supply_id integer UNIQUE,
name text,
inventory integer
);
在这里,supplies表已经确定supply_id应该是唯一的。如果想将此列,用作主键(保证唯一性和非空值),就可以简单地将UNIQUE约束更改为PRIMARY KEY:
CREATE TABLE supplies (
supply_id integer PRIMARY KEY,
name text,
inventory integer
);
这样,如果需要更新特定供应的库存数量,我们可以使用主键来定位它:
INSERT INTO supplies VALUES (
38,
'Apple phone',
5
);
UPDATE supplies set inventory = 10 WHERE supply_id = 38;
INSERT 0 1
UPDATE 1
虽然许多表使用单个列作为主键,但也可以使用一组列创建主键,作为表约束。
该national_capitals表使用现有列创建主键,可以将UNIQUE表约束替换为PRIMARY KEY:
CREATE TABLE national_capitals (
country text,
captial text,
PRIMARY KEY (country, capital)
);
Foreign key 约束
Foreign key 是一个表中的列,它引用另一个表中的列值。在表包含相关数据的各种场景中,这是可取的并且通常是必需的。数据库能够轻松连接和引用存储在单独表中的数据,这是关系数据库的主要功能之一。
例如,您可能有一个orders用于跟踪单个订单的customers表格和一个用于跟踪联系信息和客户信息的表格。将这些信息分开放置是有意义的,因为客户可能有很多订单。但是,能够轻松链接这两个表中的记录以允许更复杂的操作也是有意义的。
如何在 KingbaseES 中创建外键约束
创建 customers 表:
CREATE TABLE customers (
customer_id serial PRIMARY KEY,
name text,
phone_number text,
);
这张表包括存储的名字和电话号码的列,指定了一个使用该PRIMARY KEY约束的 ID 列。serial如果未指定 ID值,则该数据类型用于自动生成序列中的下一个 ID值。
对于orders表,我们希望能够指定有关客户下的订单的信息。可以使用外键将订单链接到客户,使用REFERENCES约束来做到这一点,它定义了与另一个表中的列的外键关系:
CREATE TABLE orders (
order_id serial PRIMARY KEY,
order_date date,
customer integer REFERENCES customers
);
在这里,表示orders表中的customer列与orders表具有外键关系customers。由于我们没有在customers表中指定特定列,因此 数据库认为要链接到customers表中的主键:customer_id.
如果尝试将一个值插入到orders没有引用有效客户的表中,数据库将拒绝它:
INSERT INTO orders VALUES (
100,
'11-19-2019',
300
);
ERROR: insert or update on table "orders" violates foreign key constraint "orders_customer_fkey"
DETAIL: Key (customer)=(300) is not present in table "customers".
如果先添加客户,数据库就会接受订单:
INSERT INTO customers VALUES (
300,
'Jill',
'Smith',
'5551235677'
);
INSERT INTO orders VALUES (
100,
'11-19-2019',
300
);
INSERT 0 1
INSERT 0 1
虽然主键是外键的理想选择,因为它保证只匹配一条记录,但也可以使用其他列,只要它们是唯一的。因此只需在REFERENCES定义中的表名之后的括号中指定列:
CREATE TABLE example (
. . .
column type REFERENCES other_table (column)
);
还可以使用保证唯一的多列组合。使用 FOREIGN KEY 表约束,该约束的REFERENCES列组合,要与FOREIGN KEY列组合的列个数次序,保持一致:
CREATE TABLE tablename (
. . .
FOREIGN KEY (column1, column2) REFERENCES other_table (column1, column2)
);
在删除或更新时,决定如何处理外键
定义外键约束时,需要考虑的一个问题,就是删除或更新引用的表时,要做什么。
例如,使用 customers 和 orders表,当客户在表中有关联的订单时,需要指定,当从customers表中删除客户时,希望orders表如何响应。
可以有以下选项:
- RESTRICT : 意味着如果customer表中的记录被引用,数据库将拒绝删除该记录。要删除客户记录,首先必须从orders表中删除所有关联记录,然后才能从客户表中删除该值。
- CASCADE: 意味着当删除customer记录时,orders表中引用它的记录也被删除。这在许多情况下很有用,但必须小心使用,以免误删除数据。
- NO ACTION : 告诉数据库简单地删除客户,并且不对关联的orders记录做任何事情。如果稍后检查约束,它仍然会导致错误,但在初始删除期间不会发生这种情况。如果未指定其他操作,则这是默认操作。
- SET NULL: 告诉数据库在删除引用的记录时,将引用列设置为 null。所以如果我们从表中删除一个客户,orders表中相应的customer列值,将被设置为NULL.
- Set DEFAULT: 如果引用的记录被删除,数据库会将引用列更改为默认值,(如果该默认值非空,在被引用表中必须有一行匹配该默认值,否则该操作将会失败)。
在定义外键约束时,可以通过添加ON DELETE|ON UPDATE之后的操作,来指定这些操作。因此,如果我们想在删除或更新客户时从系统中删除或更新相关订单,我们可以这样指定:
CREATE TABLE orders (
order_id serial PRIMARY KEY,
order_date date,
customer integer REFERENCES customers ON DELETE CASCADE
);
这些类型的操作也可以在更新引用列时应用,而不是通过使用ON UPDATE而不是删除一个列ON DELETE。
EXCLUSION 约束
最后一种约束类型是 EXCLUSION 约束。虽然像这样的约束 CHECK 可以单独检查每一行的有效性,但 EXCLUSION 约束会检查多行的值。保证如果任意两行在指定列或表达式上使用指定操作符进行比较,不是所有的比较都将会返回TRUE。如果所有指定的操作符都测试相等,这就等价于一个UNIQUE约束,尽管一个普通的唯一约束将更快。不过,排除约束能够指定比简单相等更通用的约束。UNIQUE约束是一种特定类型的排除约束,它检查每一行是否具有针对相关列或列的不同值。
例如,您可以使用排除约束来确保两个日期范围之间没有重叠,并使用如下排除:
CREATE EXTENSION btree_gist;
CREATE TABLE bookings (
room int,
booking_start date,
booking_end date,
EXCLUDE USING gist (
room WITH =,
daterange(booking_start, booking_end, '[]') WITH &&
)
);
在这里,我们有一个用于酒店预订的创建表语句,其中包含房间号以及预订开始和结束日期。
- 指定CREATE EXTENSION btree_gist以确保我们将使用的索引方法在数据库中启用。
- 使用EXCLUDE USING语法添加排除约束。指定gistindex 方法,它告诉数据库如何索引和访问值以比较它们。
- 列出想要比较项目的方式。
- 指定room值应该与等号进行比较,这意味着约束将仅匹配具有相同room。
- daterange检查booking_start和列booking_end一起作为日期范围。
- 包括[]作为可选的第三个参数,以指示范围应包含性地进行比较。
- &&操作员指定日期范围应检查重叠。
因此,总的来说,该约束确保不会为重叠日期预订同一个房间。
查找数据库中的约束
需要查看已经存在的约束,可以使用下面的查询语句,它将显示所有类型的约束:
select * from dba_constraints;
约束的构想
- 在设置模式时,花点时间考虑一下 KingbaseES 的所有约束。
- 如果存在与其他主键的关联性,几乎总是需要建立联系一起的外键约束。
- 确保在创建外键时在开头添加级联。
- 如果稍后遇到不良数据,可以在事后添加约束。
- 完善的约束机制,可以为后续查询,省去数据校验的时间。
约束对性能的影响
准备1000万行数据,分别插入含有无约束、NOTNULL、CHECK、UNIQUE、主键、外键等目标表,对比执行时长。
具有不同约束的数据表和数据
\copy (select generate_series(1,10000000)) to 'dat1000.dmp' ;
create table t91(id int );
create table t92(id int not null );
create table t93(id int check ( id > 0 ) );
create table t94(id int unique );
create table t95(id int primary key );
create table t96(id int references t95);
约束带来的性能影响
\copy t91 from 'dat1000.dmp' ;
COPY 10000000
时间:2200.635 ms (00:02.201)
\copy t92 from 'dat1000.dmp' ;
COPY 10000000
时间:2217.424 ms (00:02.217)
\copy t93 from 'dat1000.dmp' ;
COPY 10000000
时间:2273.702 ms (00:02.274)
\copy t94 from 'dat1000.dmp' ;
COPY 10000000
时间:7386.427 ms (00:07.386)
\copy t95 from 'dat1000.dmp' ;
COPY 10000000
时间:7403.021 ms (00:07.403)
\copy t96 from 'dat1000.dmp' ;
COPY 10000000
时间:52048.446 ms (00:52.048)
异步处理约束
关闭约束,插入数据后启用
唯一约束
alter table T94 disable constraint t94_id_key;
ALTER TABLE
时间:46.060 ms
\copy t94 from 'dat1000.dmp' ;
COPY 10000000
时间:2265.332 ms (00:02.265)
alter table T94 enable constraint t94_id_key;
ALTER TABLE
时间:1487.519 ms (00:01.488)
外键约束
alter table T96 disable constraint t96_id_fkey;
ALTER TABLE
时间:0.617 ms
\copy t96 from 'dat1000.dmp' ;
COPY 10000000
时间:2290.434 ms (00:02.290)
alter table T96 enable constraint t96_id_fkey;
ALTER TABLE
时间:4492.627 ms (00:04.493)
在当前事务中,异步处理约束
会话 A
\set SQLTERM /
begin
alter table T96 disable constraint t96_id_fkey;
insert into t96 select * from t95 ;
alter table T96 enable constraint t96_id_fkey;
commit;
end;
/
ANONYMOUS BLOCK
时间:8670.807 ms (00:08.671)
同时,会话 B
select count(*) from t96;
count
-------
0
(1 行记录)
时间:0.614 ms
select count(*) from t96;
count
-------
0
(1 行记录)
时间:50.618 ms
insert into t961 values (-1);
错误: 插入或更新表 "t961" 违反外键约束 "t961_id_fkey"
描述: 键值对(id)=(-1)没有在表"t95"中出现.
时间:0.627 ms
select count(*) from t96;
count
----------
10000000
(1 行记录)
时间:465.094 ms
外键的停用启用选项
- Enable Validate 与 Enable 相同,检查已有记录和新增记录,确保都符合约束;
- Enable Novalidate ,不会对已有的数据进行校验;
- Disable Validate 禁用约束,删除约束上的索引,不允许修改任何被约束的记录;
- Disable Novalidate 与 Disable 相同,禁用约束,删除约束上的索引,且允许修改被约束的记录。
如果对新增数据的关联有效性,有信心或者不关心,可以使用 Enable Novalidate ,节省校验时间。
增加海量数据时长统计表格
约束 | 时长 s | 指数 |
---|---|---|
无约束 | 2.201 | 100 |
NOT NULL | 2.217 | 101 |
CHECK | 2.274 | 103 |
UNIQUE | 7.386 | 336 |
PRIMARY KEY | 7.403 | 336 |
FOREIGN KEY | 52.048 | 2365 |
异步 UNIQUE | 3.799 | 173 |
异步 FOREIGN KEY | 6.783 | 308 |
会话异步 FOREIGN KEY | 8.671 | 394 |
结论
在本文中,了解了如何使用 KingbaseES 的约束来确定哪些特定值对表有效。了列约束和表约束之间的区别之后,介绍了各种类型的约束并演示了如何使用它们来限制表接受的输入类型。
约束是帮助在数据结构中定义期望的众多功能之一。一旦使用了约束,就可以允许 KingbaseES 验证任何输入值,是否符合要求。使用数据库系统强制执行,约束是保证数据保持一致和有意义的一种小方法。
约束是应用开发人员的朋友,也是数据库维护人员的绊脚石。例如数据迁移时,建议停用数据库所有的约束,避免数据导入失败,而且可以大幅提高迁移速度。