1前言
昨天在另外一个群里又双叒叕看到一个”主键重复”的有趣 case,看下现象
表设置了主键,有三个继承表,pg_restore -a 还原后,主键重复了。
继承表增加主键就不能重复导入了,是否可以认为,父表被继承后,父表的主键和继承表的主键是分开的,不影响的?
可以看到,t_part 是父表,通过继承的方式含有三个子表,主键是 id 和 create_time 的复合主键,但是查询 t_part 发现主键确实重复了,这又是什么鬼?
2复现
先简单复现一下这个操作,建个继承表
postgres=# create table t1(id int,info text,create_time timestamp);
CREATE TABLE
postgres=# alter table t1 add primary key(id,create_time);
ALTER TABLE
postgres=# create table t1_inherit1() inherits(t1);
CREATE TABLE
postgres=# create table t1_inherit2() inherits(t1);
CREATE TABLE
postgres=# create table t1_inherit3() inherits(t1);
CREATE TABLE
postgres=# d+ t1
Table "public.t1"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
-------------+-----------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
id | integer | | not null | | plain | | |
info | text | | | | extended | | |
create_time | timestamp without time zone | | not null | | plain | | |
Indexes:
"t1_pkey" PRIMARY KEY, btree (id, create_time)
Child tables: t1_inherit1,
t1_inherit2,
t1_inherit3
Access method: heap
先熟悉一下继承表的基本原理,在官网上有详细的介绍
postgres=# insert into t1 values(1,'hello',now());
INSERT 0 1
postgres=# select * from t1;
id | info | create_time
----+-------+----------------------------
1 | hello | 2023-02-07 10:58:24.003579
(1 row)
postgres=# select * from t1_inherit1;
id | info | create_time
----+------+-------------
(0 rows)
往父表中插入数据,子表是看不到的,但是往子表中插入数据,父表是可以看到的
postgres=# insert into t1_inherit1 values(2,'world',now());
INSERT 0 1
postgres=# select * from t1;
id | info | create_time
----+-------+----------------------------
1 | hello | 2023-02-07 10:58:24.003579
2 | world | 2023-02-07 10:58:57.427457
(2 rows)
postgres=# select * from t1*; ---*显式指定包括所有继承表
id | info | create_time
----+-------+----------------------------
1 | hello | 2023-02-07 10:58:24.003579
2 | world | 2023-02-07 10:58:57.427457
(2 rows)
postgres=# select * from t1_inherit1;
id | info | create_time
----+-------+----------------------------
2 | world | 2023-02-07 10:58:57.427457
(1 row)
其中在第 2 条 SQL 里面我还特意指定了 *,表示显式指定包括所有继承表,不过这个是默认的行为,因此不指定也可以,行为等价与第一条 SQL。另外如官网所写
All check constraints and not-null constraints on a parent table are automatically inherited by its children, unless explicitly specified otherwise with NO INHERIT clauses. Other types of constraints (unique, primary key, and foreign key constraints) are not inherited.
除非使用 NO INHERIT 子句明确指定,否则父表上的所有检查约束和非空约束都会自动由其子表继承。不继承其他类型的约束 (唯一约束,主键和外键约束) 。
所以可以看到子表是没有自动继承父表的主键约束的
postgres=# d+ t1_inherit1
Table "public.t1_inherit1"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
-------------+-----------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
id | integer | | not null | | plain | | |
info | text | | | | extended | | |
create_time | timestamp without time zone | | not null | | plain | | |
Inherits: t1
Access method: heap
这个操作就有点像分区表,没有全局索引
分区表的唯一约束(也就是主键)必须包括所有的分区键列。存在这个限制是因为构成约束的各个索引只能在它们自己的分区中直接执行唯一性;因此,分区结构本身必须保证在不同的分区中不存在重复。
其实到这里,各位应该就能搞明白原因了。让我们还是按照他的步骤操作一下
[postgres@xiongcc ~]$ pg_dump -Fp -t "t1_inherit1" -a > t1_inherit1.sql
[postgres@xiongcc ~]$ psql -f t1_inherit1.sql -p 5432 > /dev/null
[postgres@xiongcc ~]$ psql -f t1_inherit1.sql -p 5432 > /dev/null
[postgres@xiongcc ~]$ psql
psql (15.1, server 14.6)
Type "help" for help.
postgres=# select * from t1;
id | info | create_time
----+-------+----------------------------
1 | hello | 2023-02-07 10:58:24.003579
2 | world | 2023-02-07 10:58:57.427457 ---主键"重复"
2 | world | 2023-02-07 10:58:57.427457 ---主键"重复"
2 | world | 2023-02-07 10:58:57.427457 ---主键"重复"
(4 rows)
postgres=# select * from t1_inherit1;
id | info | create_time
----+-------+----------------------------
2 | world | 2023-02-07 10:58:57.427457
2 | world | 2023-02-07 10:58:57.427457
2 | world | 2023-02-07 10:58:57.427457
(3 rows)
可以看到,主键看着好像又”重复”了!其实按照前面的分析,各位应该已经知晓原因了,因为继承表里的数据自身就是重复的(因为没有主键又多次导入了),而查询父表的时候会默认把子表数据一起查出来,给人一个主键”重复”的错觉,pageinspect 也能证明底层数据库就一条数据
postgres=# select * from only t1;
id | info | create_time
----+-------+----------------------------
1 | hello | 2023-02-07 10:58:24.003579
(1 row)
postgres=# SELECT * FROM heap_page_items(get_raw_page('t1', 0));
-[ RECORD 1 ]---------------------------------------------------
lp | 1
lp_off | 8144
lp_flags | 1
lp_len | 48
t_xmin | 225376
t_xmax | 0
t_field3 | 0
t_ctid | (0,1)
t_infomask2 | 3
t_infomask | 2306
t_hoff | 24
t_bits |
t_oid |
t_data | x010000000d68656c6c6f00000000000055dad6dd18970200
不过值得注意的是,即使在子表添加了主键,也可能会有主键重复的错觉
postgres=# truncate table t1;
TRUNCATE TABLE
postgres=# insert into t1 values(1,'hello',now());
INSERT 0 1
postgres=# insert into t1_inherit1 values(2,'world',now());
INSERT 0 1
postgres=# alter table t1_inherit1 add primary key(id,create_time);
ALTER TABLE
postgres=# d+ t1_inherit1
Table "public.t1_inherit1"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
-------------+-----------------------------+-----------+----------+---------+----------+-------------+--------------+-------------
id | integer | | not null | | plain | | |
info | text | | | | extended | | |
create_time | timestamp without time zone | | not null | | plain | | |
Indexes:
"t1_inherit1_pkey" PRIMARY KEY, btree (id, create_time)
Inherits: t1
Access method: heap
postgres=# insert into t1_inherit1 values(1,'hello','2023-02-07 11:03:30.275925');
INSERT 0 1
postgres=# select * from t1;
id | info | create_time
----+-------+----------------------------
1 | hello | 2023-02-07 11:03:30.275925
2 | world | 2023-02-07 11:03:38.9071
1 | hello | 2023-02-07 11:03:30.275925
(3 rows)
没错,我手动插入重复的数据,也能给人主键重复的错觉(因为对于子表,数据是不重复的)。
3小结
又是一起有趣的主键重复案例,加上这一起,总共三次主键重复了,不过此次案例并非真正的重复,只是继承表所带来的的错觉,另外两次是实打实的主键重复,详细戳
、
总之要小心继承表,会让系统变得复杂,注意事项就参照官网吧,很清晰了。
4参考
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,一年会员只需98元,全站资源免费下载 点击查看详情
站 长 微 信: lzxmw777