使用 PostgreSQL 继承和分区方案的实现建议
1、按时间范围分区方案(一)
创建父表(保持不变)
CREATE TABLE "tcsf"."tc_heartbeat_records" ("id" int8 NOT NULL,"type" varchar(50) COLLATE "pg_catalog"."default","mode" int4 DEFAULT 0,"proto_ver" varchar(20) COLLATE "pg_catalog"."default","interval" int4 DEFAULT 60,"park_id" varchar(100) COLLATE "pg_catalog"."default","cam_id" varchar(100) COLLATE "pg_catalog"."default","cam_ip" varchar(50) COLLATE "pg_catalog"."default","gmt_create" timestamp(6) DEFAULT CURRENT_TIMESTAMP,"gmt_modified" timestamp(6) DEFAULT CURRENT_TIMESTAMP,"del_flag" int4 DEFAULT 0,"message" varchar(4000) COLLATE "pg_catalog"."default",CONSTRAINT "tc_heartbeat_records_pkey" PRIMARY KEY ("id", "gmt_create")
) PARTITION BY RANGE ("gmt_create");
2、创建月度分区表
-- 2024年1月分区
CREATE TABLE "tcsf"."tc_heartbeat_records_202401"
PARTITION OF "tcsf"."tc_heartbeat_records"
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');-- 2024年2月分区
CREATE TABLE "tcsf"."tc_heartbeat_records_202402"
PARTITION OF "tcsf"."tc_heartbeat_records"
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');-- 为每个分区创建独立索引
CREATE INDEX "idx_heartbeat_202401_cam_time" ON "tcsf"."tc_heartbeat_records_202401" ("cam_id", "gmt_create");
CREATE INDEX "idx_heartbeat_202401_create" ON "tcsf"."tc_heartbeat_records_202401" ("gmt_create");
CREATE INDEX "idx_heartbeat_202401_park_cam" ON "tcsf"."tc_heartbeat_records_202401" ("park_id", "cam_id");
2. 按停车场ID哈希分区方案(二)
-- 按停车场ID哈希分区
CREATE TABLE "tcsf"."tc_heartbeat_records_by_park"
PARTITION BY HASH ("park_id");-- 创建4个哈希分区
CREATE TABLE "tcsf"."tc_heartbeat_records_park0"
PARTITION OF "tcsf"."tc_heartbeat_records_by_park"
FOR VALUES WITH (MODULUS 4, REMAINDER 0);CREATE TABLE "tcsf"."tc_heartbeat_records_park1"
PARTITION OF "tcsf"."tc_heartbeat_records_by_park"
FOR VALUES WITH (MODULUS 4, REMAINDER 1);CREATE TABLE "tcsf"."tc_heartbeat_records_park2"
PARTITION OF "tcsf"."tc_heartbeat_records_by_park"
FOR VALUES WITH (MODULUS 4, REMAINDER 2);CREATE TABLE "tcsf"."tc_heartbeat_records_park3"
PARTITION OF "tcsf"."tc_heartbeat_records_by_park"
FOR VALUES WITH (MODULUS 4, REMAINDER 3);
- 自动分区管理函数
-- 自动创建下个月分区的函数
CREATE OR REPLACE FUNCTION tcsf.create_heartbeat_partition()
RETURNS void AS $$
DECLAREnext_month text;partition_name text;start_date date;end_date date;
BEGINnext_month := to_char(CURRENT_DATE + INTERVAL '1 month', 'YYYYMM');partition_name := 'tc_heartbeat_records_' || next_month;start_date := date_trunc('month', CURRENT_DATE + INTERVAL '1 month');end_date := date_trunc('month', CURRENT_DATE + INTERVAL '2 months');IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'tcsf' AND tablename = partition_name) THENEXECUTE format('CREATE TABLE tcsf.%I PARTITION OF tcsf.tc_heartbeat_recordsFOR VALUES FROM (%L) TO (%L)',partition_name, start_date, end_date);-- 为新分区创建索引EXECUTE format('CREATE INDEX %I ON tcsf.%I (cam_id, gmt_create)','idx_heartbeat_' || next_month || '_cam_time',partition_name);EXECUTE format('CREATE INDEX %I ON tcsf.%I (gmt_create)','idx_heartbeat_' || next_month || '_create',partition_name);RAISE NOTICE '分区 % 创建成功', partition_name;END IF;
END;
$$ LANGUAGE plpgsql;
- 表继承方案(传统继承)
-- 创建基表(不直接存储数据)
CREATE TABLE "tcsf"."tc_heartbeat_records_base" ("id" int8 NOT NULL,"type" varchar(50),"mode" int4 DEFAULT 0,"proto_ver" varchar(20),"interval" int4 DEFAULT 60,"park_id" varchar(100),"cam_id" varchar(100),"cam_ip" varchar(50),"gmt_create" timestamp(6) DEFAULT CURRENT_TIMESTAMP,"gmt_modified" timestamp(6) DEFAULT CURRENT_TIMESTAMP,"del_flag" int4 DEFAULT 0,"message" varchar(4000)
);-- 创建继承表(按季度)
CREATE TABLE "tcsf"."tc_heartbeat_records_q1" (CONSTRAINT "pk_heartbeat_q1" PRIMARY KEY ("id"),CONSTRAINT "chk_heartbeat_q1_time" CHECK (gmt_create >= '2024-01-01' AND gmt_create < '2024-04-01')
) INHERITS ("tcsf"."tc_heartbeat_records_base");CREATE TABLE "tcsf"."tc_heartbeat_records_q2" (CONSTRAINT "pk_heartbeat_q2" PRIMARY KEY ("id"),CONSTRAINT "chk_heartbeat_q2_time" CHECK (gmt_create >= '2024-04-01' AND gmt_create < '2024-07-01')
) INHERITS ("tcsf"."tc_heartbeat_records_base");
- 分区管理自动化
-- 创建分区管理定时任务
CREATE OR REPLACE FUNCTION tcsf.manage_heartbeat_partitions()
RETURNS void AS $$
BEGIN-- 创建未来分区PERFORM tcsf.create_heartbeat_partition();-- 删除过期分区(保留最近12个月)PERFORM tcsf.drop_old_heartbeat_partitions();
END;
$$ LANGUAGE plpgsql;-- 删除旧分区函数
CREATE OR REPLACE FUNCTION tcsf.drop_old_heartbeat_partitions()
RETURNS void AS $$
DECLAREold_partition_name text;cutoff_date date;
BEGINcutoff_date := date_trunc('month', CURRENT_DATE - INTERVAL '12 months');FOR old_partition_name INSELECT tablename FROM pg_tables WHERE schemaname = 'tcsf' AND tablename LIKE 'tc_heartbeat_records_%'AND tablename < 'tc_heartbeat_records_' || to_char(cutoff_date, 'YYYYMM')LOOPEXECUTE format('DROP TABLE tcsf.%I', old_partition_name);RAISE NOTICE '分区 % 已删除', old_partition_name;END LOOP;
END;
$$ LANGUAGE plpgsql;
- 查询优化建议
-- 启用分区修剪
SET enable_partition_pruning = on;-- 按时间范围查询(会自动定位到对应分区)
EXPLAIN ANALYZE
SELECT * FROM tcsf.tc_heartbeat_records
WHERE gmt_create >= '2024-01-01' AND gmt_create < '2024-02-01'AND cam_id = 'camera_001';-- 跨分区聚合查询
SELECT date_trunc('day', gmt_create) as day,COUNT(*) as heartbeat_count,COUNT(DISTINCT cam_id) as active_cameras
FROM tcsf.tc_heartbeat_records
WHERE gmt_create >= CURRENT_DATE - INTERVAL '7 days'AND del_flag = 0
GROUP BY day
ORDER BY day;
使用继承版方案四(关键INHERITS)
1. 创建父表(通用心跳记录)
sql
-- 创建父表:通用心跳记录信息
CREATE TABLE "tcsf"."tc_heartbeat_records_base" ("id" int8 NOT NULL,"type" varchar(50) COLLATE "pg_catalog"."default","mode" int4 DEFAULT 0,"proto_ver" varchar(20) COLLATE "pg_catalog"."default","interval" int4 DEFAULT 60,"park_id" varchar(100) COLLATE "pg_catalog"."default","cam_id" varchar(100) COLLATE "pg_catalog"."default","cam_ip" varchar(50) COLLATE "pg_catalog"."default","gmt_create" timestamp(6) DEFAULT CURRENT_TIMESTAMP,"gmt_modified" timestamp(6) DEFAULT CURRENT_TIMESTAMP,"del_flag" int4 DEFAULT 0,"message" varchar(4000) COLLATE "pg_catalog"."default",CONSTRAINT "tc_heartbeat_records_base_pkey" PRIMARY KEY ("id")
);COMMENT ON TABLE "tcsf"."tc_heartbeat_records_base" IS '心跳记录基表(所有类型心跳通用字段)';
- 创建不同类型摄像头的子表
2.1 出入口摄像头心跳记录
-- 创建出入口摄像头子表:继承基础心跳记录 + 出入口特定字段
CREATE TABLE "tcsf"."entrance_heartbeat_records" ("lane_number" varchar(20), -- 车道编号"direction" varchar(10), -- 方向:entry/exit"vehicle_count" int4 DEFAULT 0, -- 车辆计数"last_plate" varchar(20), -- 最后识别车牌"gate_status" varchar(20) DEFAULT 'closed' -- 道闸状态
) INHERITS ("tcsf"."tc_heartbeat_records_base");COMMENT ON TABLE "tcsf"."entrance_heartbeat_records" IS '出入口摄像头心跳记录表';
COMMENT ON COLUMN "tcsf"."entrance_heartbeat_records"."lane_number" IS '车道编号';
COMMENT ON COLUMN "tcsf"."entrance_heartbeat_records"."direction" IS '方向:entry/exit';
COMMENT ON COLUMN "tcsf"."entrance_heartbeat_records"."vehicle_count" IS '车辆计数';
COMMENT ON COLUMN "tcsf"."entrance_heartbeat_records"."last_plate" IS '最后识别车牌';
COMMENT ON COLUMN "tcsf"."entrance_heartbeat_records"."gate_status" IS '道闸状态';
2.2 车位摄像头心跳记录
-- 创建车位摄像头子表:继承基础心跳记录 + 车位监控特定字段
CREATE TABLE "tcsf"."parking_heartbeat_records" ("space_id" varchar(50) NOT NULL, -- 车位编号"occupancy_status" int2 DEFAULT 0, -- 占用状态:0-空闲,1-占用"last_occupancy_change" timestamp(6), -- 最后状态变更时间"detection_confidence" decimal(3,2), -- 检测置信度"lighting_condition" varchar(20) -- 光照条件
) INHERITS ("tcsf"."tc_heartbeat_records_base");COMMENT ON TABLE "tcsf"."parking_heartbeat_records" IS '车位摄像头心跳记录表';
COMMENT ON COLUMN "tcsf"."parking_heartbeat_records"."space_id" IS '车位编号';
COMMENT ON COLUMN "tcsf"."parking_heartbeat_records"."occupancy_status" IS '占用状态';
COMMENT ON COLUMN "tcsf"."parking_heartbeat_records"."last_occupancy_change" IS '最后状态变更时间';
COMMENT ON COLUMN "tcsf"."parking_heartbeat_records"."detection_confidence" IS '检测置信度';
COMMENT ON COLUMN "tcsf"."parking_heartbeat_records"."lighting_condition" IS '光照条件';
2.3 安防摄像头心跳记录
-- 创建安防摄像头子表:继承基础心跳记录 + 安防监控特定字段
CREATE TABLE "tcsf"."security_heartbeat_records" ("camera_position" varchar(100), -- 摄像头位置描述"ptz_status" varchar(50), -- 云台状态"preset_position" varchar(50), -- 预置位"alarm_status" int2 DEFAULT 0, -- 报警状态"video_quality" varchar(20), -- 视频质量"storage_status" varchar(20) -- 存储状态
) INHERITS ("tcsf"."tc_heartbeat_records_base");COMMENT ON TABLE "tcsf"."security_heartbeat_records" IS '安防摄像头心跳记录表';
COMMENT ON COLUMN "tcsf"."security_heartbeat_records"."camera_position" IS '摄像头位置描述';
COMMENT ON COLUMN "tcsf"."security_heartbeat_records"."ptz_status" IS '云台状态';
COMMENT ON COLUMN "tcsf"."security_heartbeat_records"."preset_position" IS '预置位';
COMMENT ON COLUMN "tcsf"."security_heartbeat_records"."alarm_status" IS '报警状态';
COMMENT ON COLUMN "tcsf"."security_heartbeat_records"."video_quality" IS '视频质量';
COMMENT ON COLUMN "tcsf"."security_heartbeat_records"."storage_status" IS '存储状态';
- 为子表创建特定索引
-- 出入口摄像头索引
CREATE INDEX "idx_entrance_lane_direction" ON "tcsf"."entrance_heartbeat_records" ("lane_number", "direction");
CREATE INDEX "idx_entrance_gate_status" ON "tcsf"."entrance_heartbeat_records" ("gate_status");
CREATE INDEX "idx_entrance_vehicle_time" ON "tcsf"."entrance_heartbeat_records" ("vehicle_count", "gmt_create");-- 车位摄像头索引
CREATE INDEX "idx_parking_space_status" ON "tcsf"."parking_heartbeat_records" ("space_id", "occupancy_status");
CREATE INDEX "idx_parking_occupancy_time" ON "tcsf"."parking_heartbeat_records" ("last_occupancy_change");
CREATE INDEX "idx_parking_detection_conf" ON "tcsf"."parking_heartbeat_records" ("detection_confidence");-- 安防摄像头索引
CREATE INDEX "idx_security_alarm_status" ON "tcsf"."security_heartbeat_records" ("alarm_status");
CREATE INDEX "idx_security_ptz_status" ON "tcsf"."security_heartbeat_records" ("ptz_status");
CREATE INDEX "idx_security_position" ON "tcsf"."security_heartbeat_records" ("camera_position");
- 数据操作示例
4.1 插入数据到特定子表
-- 插入出入口摄像头心跳
INSERT INTO "tcsf"."entrance_heartbeat_records" (id, type, park_id, cam_id, cam_ip, lane_number, direction, vehicle_count, last_plate, gate_status
) VALUES (1, 'entrance', 'PARK001', 'CAM_ENTRY_001', '192.168.1.100', 'LANE1', 'entry', 156, '京A12345', 'open'
);-- 插入车位摄像头心跳
INSERT INTO "tcsf"."parking_heartbeat_records" (id, type, park_id, cam_id, space_id, occupancy_status, detection_confidence
) VALUES (2, 'parking', 'PARK001', 'CAM_PARK_001', 'SPACE_A01', 1, 0.95
);
4.2 查询所有类型的心跳记录
-- 查询所有摄像头的心跳记录(包括所有子表)
SELECT * FROM "tcsf"."tc_heartbeat_records_base"
WHERE park_id = 'PARK001' AND gmt_create >= CURRENT_DATE;-- 只查询出入口摄像头的心跳记录
SELECT * FROM "tcsf"."entrance_heartbeat_records"
WHERE direction = 'entry' AND gate_status = 'open';
4.3 统计各类摄像头状态
-- 统计各类摄像头的在线状态
SELECT CASE WHEN tableoid = '"tcsf"."entrance_heartbeat_records"'::regclass THEN 'entrance'WHEN tableoid = '"tcsf"."parking_heartbeat_records"'::regclass THEN 'parking'WHEN tableoid = '"tcsf"."security_heartbeat_records"'::regclass THEN 'security'ELSE 'other'END as camera_type,COUNT(*) as heartbeat_count,COUNT(DISTINCT cam_id) as unique_cameras
FROM "tcsf"."tc_heartbeat_records_base"
WHERE gmt_create >= NOW() - INTERVAL '1 hour'AND del_flag = 0
GROUP BY camera_type;
- 约束检查(可选)
-- 为子表添加约束确保数据完整性
ALTER TABLE "tcsf"."entrance_heartbeat_records"
ADD CONSTRAINT "chk_entrance_direction"
CHECK (direction IN ('entry', 'exit'));ALTER TABLE "tcsf"."parking_heartbeat_records"
ADD CONSTRAINT "chk_occupancy_status"
CHECK (occupancy_status IN (0, 1));ALTER TABLE "tcsf"."security_heartbeat_records"
ADD CONSTRAINT "chk_video_quality"
CHECK (video_quality IN ('excellent', 'good', 'fair', 'poor'));
推荐方案四这种继承方案的优势:
数据隔离:不同类型摄像头的数据物理分离
扩展性:可以轻松添加新的摄像头类型
查询优化:可以针对特定类型优化查询
维护简便:各类摄像头的特定字段独立管理
统一接口:仍然可以通过父表进行统一查询
