Appearance
PostgreSQL 表继承详解
概述
PostgreSQL 的表继承功能允许一个表从另一个表继承结构和数据,这为数据库设计提供了强大的建模能力。继承机制类似于面向对象编程中的类继承概念,子表自动获得父表的所有列和约束。
什么是表继承?
表继承是 PostgreSQL 特有的功能,它允许你创建一个表(子表)来"继承"另一个表(父表)的列结构。子表会自动包含父表的所有列,同时可以添加自己特有的列。
基本语法与概念
继承表的创建语法
sql
-- 创建父表
CREATE TABLE parent_table (
column1 datatype,
column2 datatype,
...
);
-- 创建继承表
CREATE TABLE child_table (
additional_column datatype
) INHERITS (parent_table);
sql
-- 从多个父表继承
CREATE TABLE child_table (
additional_column datatype
) INHERITS (parent_table1, parent_table2);
继承关系图示
实战示例:城市数据模型
让我们通过一个完整的例子来理解表继承的工作原理。
问题场景
我们需要设计一个城市数据库,包含普通城市和州首府。州首府除了具有普通城市的属性外,还需要记录所属的州信息。
解决方案实现
步骤 1:创建父表
sql
-- 创建城市基础表
CREATE TABLE cities (
name text, -- 城市名称
population float, -- 人口数量
elevation int -- 海拔高度(英尺)
);
解释:
cities
表定义了所有城市的通用属性- 包含名称、人口和海拔三个基本字段
步骤 2:创建继承表
sql
-- 创建州首府表(继承自cities)
CREATE TABLE capitals (
state char(2) -- 所属州的缩写
) INHERITS (cities);
解释:
capitals
表自动继承cities
的所有列- 额外添加了
state
列来标识所属州 - 最终
capitals
表包含:name
,population
,elevation
,state
步骤 3:插入测试数据
sql
-- 向父表插入数据
INSERT INTO cities (name, population, elevation) VALUES
('Las Vegas', 651319, 2174),
('Mariposa', 2173, 1953),
('San Francisco', 883305, 52);
-- 向子表插入数据
INSERT INTO capitals (name, population, elevation, state) VALUES
('Madison', 259680, 845, 'WI'),
('Albany', 97856, 150, 'NY'),
('Sacramento', 524943, 30, 'CA');
数据结构展示:
表名 | name | population | elevation | state |
---|---|---|---|---|
cities | Las Vegas | 651319 | 2174 | - |
cities | Mariposa | 2173 | 1953 | - |
cities | San Francisco | 883305 | 52 | - |
capitals | Madison | 259680 | 845 | WI |
capitals | Albany | 97856 | 150 | NY |
capitals | Sacramento | 524943 | 30 | CA |
查询操作详解
默认继承查询
sql
-- 查询所有城市(包括州首府)
SELECT name, elevation
FROM cities
WHERE elevation > 500;
输出结果:
name | elevation
-----------+-----------
Las Vegas | 2174
Mariposa | 1953
Madison | 845
分析过程:
- 查询
cities
表时,PostgreSQL 默认包含所有继承的子表数据 - 结果同时包含
cities
表和capitals
表中满足条件的记录 - 这种行为使得对"所有城市"的查询变得简单
使用 ONLY 关键字
sql
-- 仅查询父表,不包含子表数据
SELECT name, elevation
FROM ONLY cities
WHERE elevation > 500;
输出结果:
name | elevation
-----------+-----------
Las Vegas | 2174
Mariposa | 1953
分析过程:
ONLY
关键字限制查询范围仅为指定的表- 不包含任何继承的子表数据
- 适用于需要区分表数据来源的场景
明确指定包含继承
sql
-- 使用 * 明确指定包含继承(可选)
SELECT name, elevation
FROM cities*
WHERE elevation > 500;
语法提示
cities*
中的 *
符号是可选的,因为包含继承是默认行为。但在某些旧版本兼容性场景中可能需要明确指定。
系统列与表识别
使用 tableoid 识别数据来源
sql
-- 查询数据的来源表
SELECT c.tableoid, c.name, c.elevation
FROM cities c
WHERE c.elevation > 500;
输出结果:
tableoid | name | elevation
----------+-----------+-----------
139793 | Las Vegas | 2174
139793 | Mariposa | 1953
139798 | Madison | 845
获取表名称
有两种方法可以获取实际的表名:
sql
SELECT p.relname, c.name, c.elevation
FROM cities c, pg_class p
WHERE c.elevation > 500 AND c.tableoid = p.oid;
sql
SELECT c.tableoid::regclass, c.name, c.elevation
FROM cities c
WHERE c.elevation > 500;
输出结果:
relname | name | elevation
----------+-----------+-----------
cities | Las Vegas | 2174
cities | Mariposa | 1953
capitals | Madison | 845
分析过程:
tableoid
是每个表都有的系统列,存储表的 OID- 通过 JOIN
pg_class
或转换为regclass
类型可以获取表名 - 这对于区分数据来源非常有用
数据操作限制
INSERT 操作的限制
sql
-- ❌ 这个操作会失败
INSERT INTO cities (name, population, elevation, state)
VALUES ('Albany', NULL, NULL, 'NY');
错误信息:
ERROR: column "state" of relation "cities" does not exist
原因分析:
INSERT
操作不会自动路由到子表cities
表不包含state
列- 必须直接向目标表插入数据
正确做法:
sql
-- ✅ 直接向子表插入
INSERT INTO capitals (name, population, elevation, state)
VALUES ('Albany', NULL, NULL, 'NY');
数据修改操作
继承对于数据修改操作的影响:
sql
-- UPDATE 和 DELETE 默认作用于整个继承层次
UPDATE cities SET population = population * 1.1; -- 影响所有子表
DELETE FROM cities WHERE population < 1000; -- 从所有表删除
-- 使用 ONLY 限制到单表
UPDATE ONLY cities SET population = population * 1.1; -- 仅影响父表
DELETE FROM ONLY cities WHERE population < 1000; -- 仅从父表删除
约束与继承
自动继承的约束
sql
-- 在父表上定义约束
CREATE TABLE cities_with_constraints (
name text NOT NULL, -- 非空约束
population float CHECK (population > 0), -- 检查约束
elevation int
);
-- 子表自动继承约束
CREATE TABLE capitals_with_constraints (
state char(2) NOT NULL
) INHERITS (cities_with_constraints);
约束继承规则:
约束类型 | 是否继承 | 说明 |
---|---|---|
NOT NULL | ✅ | 自动继承 |
CHECK | ✅ | 自动继承,可用 NO INHERIT 排除 |
UNIQUE | ❌ | 不继承 |
PRIMARY KEY | ❌ | 不继承 |
FOREIGN KEY | ❌ | 不继承 |
约束合并示例
sql
-- 多重继承时的约束合并
CREATE TABLE base1 (
id int NOT NULL,
value text CHECK (length(value) > 0)
);
CREATE TABLE base2 (
id int NOT NULL, -- 相同列名和类型
category text
);
-- 子表继承并合并约束
CREATE TABLE combined () INHERITS (base1, base2);
-- 结果:id 列合并为 NOT NULL,继承 value 的 CHECK 约束
动态继承管理
事后添加继承关系
sql
-- 创建兼容的表结构
CREATE TABLE existing_table (
name text,
population float,
elevation int,
region text -- 额外列
);
-- 添加继承关系
ALTER TABLE existing_table INHERIT cities;
使用 LIKE 创建兼容表
sql
-- 复制表结构
CREATE TABLE new_cities (
LIKE cities INCLUDING CONSTRAINTS, -- 包含约束
region text
);
-- 建立继承关系
ALTER TABLE new_cities INHERIT cities;
移除继承关系
sql
-- 移除继承关系
ALTER TABLE capitals NO INHERIT cities;
继承的高级特性
多重继承
sql
-- 创建多个父表
CREATE TABLE geographical_entities (
latitude decimal(10,8),
longitude decimal(11,8)
);
CREATE TABLE administrative_units (
admin_code varchar(10),
admin_level int
);
-- 多重继承
CREATE TABLE major_cities (
tourism_rating int
) INHERITS (cities, geographical_entities, administrative_units);
继承关系图:
权限与安全
sql
-- 在父表上授权
GRANT SELECT ON cities TO public;
GRANT UPDATE ON cities TO city_admin;
-- 查询继承数据时的权限检查
SELECT * FROM cities; -- 检查 cities 表权限,可访问所有继承数据
-- 直接访问子表需要额外权限
SELECT * FROM capitals; -- 需要 capitals 表的权限
权限继承规则:
权限说明
- 通过父表查询时,只检查父表权限
- 直接访问子表时,需要子表权限
- 行级安全策略遵循相同规则
重要限制与注意事项
1. 索引和唯一约束限制
sql
-- ❌ 唯一约束不能跨继承层次工作
CREATE TABLE cities_unique (
name text UNIQUE, -- 仅在单表内唯一
population float,
elevation int
);
CREATE TABLE capitals_unique (
state char(2)
) INHERITS (cities_unique);
-- 可能存在重复数据的情况
INSERT INTO cities_unique VALUES ('New York', 8400000, 33);
INSERT INTO capitals_unique VALUES ('New York', 8400000, 33, 'NY');
-- 两条记录都会被接受!
2. 外键约束限制
sql
-- ❌ 外键不能引用继承层次
CREATE TABLE countries (
code char(2) PRIMARY KEY,
name text
);
-- 这个外键只能引用 capitals 表,不包括其他继承表
ALTER TABLE capitals
ADD CONSTRAINT fk_state
FOREIGN KEY (state) REFERENCES countries(code);
3. 命令支持情况
命令类型 | 继承支持 | ONLY 支持 | 说明 |
---|---|---|---|
SELECT | ✅ | ✅ | 默认包含继承,支持 ONLY |
UPDATE | ✅ | ✅ | 默认影响继承层次 |
DELETE | ✅ | ✅ | 默认影响继承层次 |
INSERT | ❌ | N/A | 不自动路由到子表 |
VACUUM | ❌ | N/A | 仅作用于单表 |
REINDEX | ❌ | N/A | 仅作用于单表 |
实际应用场景
场景 1:产品分类系统
sql
-- 基础产品表
CREATE TABLE products (
id serial PRIMARY KEY,
name text NOT NULL,
price decimal(10,2),
description text,
created_at timestamp DEFAULT NOW()
);
-- 电子产品表
CREATE TABLE electronics (
warranty_period int, -- 保修期(月)
brand text,
model text
) INHERITS (products);
-- 图书表
CREATE TABLE books (
isbn varchar(13),
author text,
publisher text,
pages int
) INHERITS (products);
-- 查询所有产品
SELECT name, price FROM products ORDER BY price DESC;
-- 查询特定类型产品
SELECT name, brand, warranty_period
FROM electronics
WHERE warranty_period > 12;
场景 2:日志系统
sql
-- 基础日志表
CREATE TABLE base_logs (
id bigserial PRIMARY KEY,
timestamp timestamp DEFAULT NOW(),
level varchar(10),
message text,
source text
);
-- 应用日志
CREATE TABLE app_logs (
user_id int,
session_id varchar(32),
action varchar(50)
) INHERITS (base_logs);
-- 系统日志
CREATE TABLE system_logs (
server_name varchar(50),
process_id int,
cpu_usage decimal(5,2),
memory_usage decimal(5,2)
) INHERITS (base_logs);
-- 按时间查询所有日志
SELECT timestamp, level, message, source
FROM base_logs
WHERE timestamp > NOW() - INTERVAL '1 hour'
ORDER BY timestamp DESC;
场景 3:地理数据模型
sql
-- 地理实体基表
CREATE TABLE geo_entities (
id serial PRIMARY KEY,
name text NOT NULL,
area_km2 decimal(10,2),
population bigint
);
-- 国家表
CREATE TABLE countries (
iso_code char(2) UNIQUE,
continent varchar(20),
capital_city text
) INHERITS (geo_entities);
-- 省/州表
CREATE TABLE states (
country_code char(2),
state_code varchar(10),
governor text
) INHERITS (geo_entities);
-- 城市表
CREATE TABLE cities_geo (
state_code varchar(10),
mayor text,
is_capital boolean DEFAULT FALSE
) INHERITS (geo_entities);
性能优化建议
1. 分区表 vs 继承
现代替代方案
对于数据分区需求,建议使用声明式分区而不是继承:
sql
-- 使用声明式分区(推荐)
CREATE TABLE sales (
sale_date date,
amount decimal
) PARTITION BY RANGE (sale_date);
CREATE TABLE sales_2023 PARTITION OF sales
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
2. 查询优化
sql
-- 使用约束排除优化查询
ALTER TABLE capitals ADD CONSTRAINT check_state
CHECK (state IS NOT NULL);
-- PostgreSQL 可以在适当时候排除不相关的子表
SELECT * FROM cities WHERE state = 'CA'; -- 可能只扫描 capitals 表
3. 索引策略
sql
-- 在每个相关表上创建相同的索引
CREATE INDEX idx_cities_name ON cities (name);
CREATE INDEX idx_capitals_name ON capitals (name);
CREATE INDEX idx_capitals_state ON capitals (state);
-- 组合索引用于常见查询模式
CREATE INDEX idx_capitals_state_pop ON capitals (state, population);
监控和调试
查看继承关系
sql
-- 查询表的继承关系
SELECT
schemaname,
tablename,
attname,
inherited
FROM pg_stats
WHERE tablename IN ('cities', 'capitals');
-- 查看表的继承层次
SELECT
c.relname as child_table,
p.relname as parent_table
FROM pg_inherits i
JOIN pg_class c ON i.inhrelid = c.oid
JOIN pg_class p ON i.inhparent = p.oid;
分析查询计划
sql
-- 查看查询是否正确使用继承
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM cities WHERE elevation > 1000;
-- 对比 ONLY 查询的执行计划
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM ONLY cities WHERE elevation > 1000;
最佳实践总结
✅ 适合使用继承的场景
- 共享属性的相关实体:如产品分类、用户类型等
- 历史数据归档:保持相同结构的时间分片
- 多态查询需求:需要同时查询多种相关类型的数据
❌ 不适合使用继承的场景
- 需要严格唯一约束:跨表的唯一性无法保证
- 复杂外键关系:外键约束不支持继承层次
- 性能敏感的大表:考虑使用分区表替代
🔧 开发建议
- 谨慎设计:继承关系一旦建立,修改成本较高
- 文档化:清楚记录继承关系和业务逻辑
- 测试完整性:特别注意约束和权限的行为
- 监控性能:定期检查查询计划和性能指标
重要提醒
PostgreSQL 的继承功能虽然强大,但有其局限性。在现代应用中,对于数据分区需求,建议优先考虑声明式分区功能。继承更适合于真正的"is-a"关系建模。
通过本文的学习,你应该能够:
- 理解 PostgreSQL 继承的基本概念和语法
- 掌握继承表的创建、查询和管理
- 了解继承的限制和注意事项
- 在适合的场景中正确应用继承功能