在 PostgreSQL 中,违反唯一性的行也会被写入表中。这听起来很不可思议。实际上,这些行确实会被写入表中,只是不会显示在查询结果中。
我们在PostgreSQL 10 上来验证这一点。
首先,我们在创建表 man,代码如下。其中字段id 上有 主键约束。
CREATE TABLE man
(
id integer NOT NULL,
name character varying(32),
gender int,
birthday date,
CONSTRAINT pk_man PRIMARY KEY (id)
);
好的,现在我们向表中插入一条数据:
insert into man (id,name,gender,birthday) values (1,'Ronaldo', '0', '2000-01-01');
查看这张表的总大小,即表和索引的大小之和:
select pg_total_relation_size('man');
结果如下:
pg_total_relation_size
---------------------
24576
只有一条数据时,它的大小是24576字节。
我们再来从 pg_stat_user_tables 查看这张表的行的统计信息:
select relid, relname, n_tup_ins,n_tup_upd, n_tup_del, n_live_tup, n_dead_tup from pg_stat_user_tables where relname = 'man';
结果如下:
relid | relname | n_tup_ins | n_tup_upd | n_tup_del | n_live_tup | n_dead_tup
-------+---------+-----------+-----------+-----------+------------+-----------
558236 | man | 1 | 0 | 0 | 1 | 0
可以看出,n_live_tup = 1, n_dead_tup = 0,这说明表中有且只有1行活着的元组,没有死亡的元组。
现在,让我们执行下面的sql语句,将这条数据重复插入1000次,看看有什么结果。
do
$$
begin
for i in 1..1000
loop
begin
insert into man (id,name,gender,birthday) values (1,'Ronaldo', '0', '1970-01-01');
exception when unique_violation then
raise notice 'duplicate key value violates unique constraint "pk_man"';
end;
end loop;
end;
$$ language plpgsql;
再来查看表man的大小:
select pg_total_relation_size('man');
结果如下:
pg_total_relation_size
------------------------
98304
它的大小变为了98304字节。
我们看看在pg_stat_user_tables 中,表man 的统计信息的变化:
select relid, relname, n_tup_ins,n_tup_upd, n_tup_del, n_live_tup, n_dead_tup from pg_stat_user_tables where relname = 'man';
结果如下:
relid | relname | n_tup_ins | n_tup_upd | n_tup_del | n_live_tup | n_dead_tup
-------+---------+-----------+-----------+-----------+------------+-----------
558236 | man | 1001 | 0 | 0 | 1 | 1000
可以看出,表中 n_dead_tup=1001,这表示 表中有1000个死亡元组。
上面的实验说明:对PostgreSQL而言,即使向表中插入的数据元组违反了唯一约束,它仍然能插入到数据库中,只不过被标记为“dead”,从而不会展示。但这些死亡的元组仍然占据着磁盘空间,知道它们被自动或者手动清理掉。因此,把主键约束或唯一约束作为防止重复数据的首要的防线,不是明智的做法。我们应该尽量在上层程序中保证数据的唯一性。
附录
- pg_stat_user_table 介绍
pg_stat_user_table显示的是用户表的统计信息。下面是这些它的列及其含义:
列 |
类型 |
描述 |
relid |
oid |
一个表的 OID |
schemaname |
name |
这个表所在的模式的名称 |
relname |
name |
这个表的名称 |
seq_scan |
bigint |
在这个表上发起的顺序扫描的次数 |
seq_tup_read |
bigint |
被顺序扫描取得的活着的行的数量 |
idx_scan |
bigint |
在这个表上发起的索引扫描的次数 |
idx_tup_fetch |
bigint |
被索引扫描取得的活着的行的数量 |
n_tup_ins |
bigint |
被插入的行数 |
n_tup_upd |
bigint |
被更新的行数(包括 HOT 更新的行) |
n_tup_del |
bigint |
被删除的行数 |
n_tup_hot_upd |
bigint |
被更新的 HOT 行数(即不要求独立索引更新的行更新) |
n_live_tup |
bigint |
活着的行的估计数量 |
n_dead_tup |
bigint |
死亡行的估计数量 |
n_mod_since_analyze |
bigint |
从这个表最后一次被分析后备修改的行的估计数量 |
last_vacuum |
timestamp with time zone |
上次这个表被手动清理的时间(不统计VACUUM FULL) |
last_autovacuum |
timestamp with time zone |
上次这个表被自动清理守护进程清理的时间 |
last_analyze |
timestamp with time zone |
上次这个表被手动分析的时间 |
last_autoanalyze |
timestamp with time zone |
上次这个表被自动清理守护进程分析的时间 |
vacuum_count |
bigint |
这个表已被手工清理的次数(不统计VACUUM FULL) |
autovacuum_count |
bigint |
这个表已被自动清理守护进程清理的次数 |
analyze_count |
bigint |
这个表已被手工分析的次数 |
autoanalyze_count |
bigint |
这个表已被自动清理守护进程分析的次数 |