1. 首页 > PostgreSQL教程 > 正文

PostgreSQL教程FG282-PG分区表实战:海量数据存储优化

本文档风哥主要介绍PostgreSQL分区表的实战应用,包括分区表的创建、管理、性能优化等内容。风哥教程参考PostgreSQL官方文档Partitioning内容,适合处理海量数据的企业级应用场景。更多视频教程www.fgedu.net.cn

Part01-基础概念与理论知识

1.1 PostgreSQL分区表的概念

PostgreSQL分区表是将一个大表分割成多个较小的子表(称为分区)的技术,每个分区都是一个独立的表,但从外部看仍然是一个单一的表。分区表的主要特点:

  • 将大表逻辑上分割成多个小表
  • 每个分区存储一部分数据
  • 查询时只访问相关分区,提高查询性能
  • 维护操作(如VACUUM)可以针对单个分区进行
  • 支持数据的快速删除和归档
分区表的适用场景:

分区表适用于存储大量数据的表,特别是那些数据可以按照某种规则(如时间、范围、列表等)进行逻辑分割的场景。

1.2 分区表类型

PostgreSQL支持多种分区表类型:

# 分区表类型
1. 范围分区(Range Partitioning):根据指定的范围值进行分区
– 适用于时间序列数据
– 适用于连续范围的数据

2. 列表分区(List Partitioning):根据指定的离散值进行分区
– 适用于类别数据
– 适用于有限的离散值

3. 哈希分区(Hash Partitioning):根据哈希函数的结果进行分区
– 适用于均匀分布数据
– 适用于随机数据

4. 复合分区:结合多种分区类型
– 例如:先按范围分区,再按列表分区
– 适用于复杂的数据分布

# 分区表结构
– 分区表:主表,定义分区策略
– 分区:子表,存储实际数据
– 分区键:用于确定数据应该存储在哪个分区的列
– 分区边界:定义每个分区的范围或值

1.3 分区表的优势

PostgreSQL分区表的优势:

# 性能优势
1. 查询性能:只扫描相关分区,减少I/O
2. 维护性能:维护操作可以针对单个分区
3. 索引性能:每个分区的索引更小,查询更快
4. 写入性能:可以并行写入不同分区

# 管理优势
1. 数据归档:可以轻松删除或归档旧数据
2. 数据加载:可以快速加载大量数据到新分区
3. 备份恢复:可以针对单个分区进行备份和恢复
4. 存储管理:可以将不同分区存储在不同表空间

# 可扩展性优势
1. 水平扩展:可以通过添加新分区来扩展存储
2. 负载均衡:可以将分区分布在不同存储设备上
3. 并行处理:支持分区级并行操作

风哥提示:分区表是处理海量数据的有效方法,但需要根据具体的业务需求和数据特点选择合适的分区策略。在设计分区表时,应考虑数据的分布、查询模式和维护需求。

Part02-生产环境规划与建议

2.1 分区表规划

在实施分区表前,需要进行详细的规划:

# 规划要点
1. 数据量评估:估算表的大小和增长趋势
2. 数据分布:分析数据的分布特点
3. 查询模式:分析常见的查询类型和条件
4. 分区策略:选择合适的分区类型和键
5. 分区数量:确定合适的分区数量
6. 存储规划:规划分区的存储位置

# 数据量估算
– 当前数据量:表的大小和行数
– 数据增长:每年/每月的数据增长速率
– 预期数据量:未来3-5年的预期数据量

# 查询模式分析
– 时间范围查询:如按日期查询
– 范围查询:如按数值范围查询
– 等值查询:如按类别查询
– 连接查询:与其他表的连接操作
– 聚合查询:如统计分析

# 维护需求分析
– 数据归档:旧数据的归档策略
– 数据加载:新数据的加载频率和方式
– 索引维护:索引的重建和优化
– 备份恢复:备份和恢复的策略

2.2 分区策略选择

分区策略选择建议:

# 分区类型选择
– 时间序列数据:范围分区(按日期)
– 类别数据:列表分区(按类别)
– 随机分布数据:哈希分区
– 复杂数据分布:复合分区

# 分区键选择
– 选择查询中最常用的条件列
– 选择数据分布均匀的列
– 选择基数适当的列
– 避免使用频繁更新的列

# 分区粒度选择
– 时间分区:按天、周、月、季度、年
– 范围分区:根据数据分布确定范围大小
– 列表分区:根据类别数量确定分区数量
– 哈希分区:根据数据量和性能需求确定分区数量

# 分区数量建议
– 不要创建过多分区(建议不超过1000个)
– 每个分区的大小适中(建议1-10GB)
– 考虑管理和维护的复杂性

2.3 性能考虑因素

分区表性能考虑因素:

风哥教程针对风哥教程针对风哥教程针对生产环境建议:

  • 存储设备:使用高性能存储,如SSD,特别是对于频繁访问的分区
  • 内存配置:增加shared_buffers以提高分区表的缓存效率
  • 索引策略:为每个分区创建适当的索引
  • 查询优化:确保查询使用分区剪枝(partition pruning)
  • 维护计划:定期对分区进行VACUUM和ANALYZE操作

Part03-生产环境项目实施方案

3.1 范围分区

3.1.1 创建范围分区表

# 创建范围分区表
CREATE TABLE fgedu_fgfgfgsales (
id SERIAL,
sale_date DATE NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (id, sale_date)
) PARTITION BY RANGE (sale_date);

# 创建分区
CREATE TABLE fgedu_fgfgfgsales_2026_q1 PARTITION OF fgedu_fgfgfgsales
FOR VALUES FROM (‘2026-01-01’) TO (‘2026-04-01’);

CREATE TABLE fgedu_fgfgfgsales_2026_q2 PARTITION OF fgedu_fgfgfgsales
FOR VALUES FROM (‘2026-04-01’) TO (‘2026-07-01’);

CREATE TABLE fgedu_fgfgfgsales_2026_q3 PARTITION OF fgedu_fgfgfgsales
FOR VALUES FROM (‘2026-07-01’) TO (‘2026-10-01’);

CREATE TABLE fgedu_fgfgfgsales_2026_q4 PARTITION OF fgedu_fgfgfgsales
FOR VALUES FROM (‘2026-10-01’) TO (‘2027-01-01’);

# 为每个分区创建索引
CREATE INDEX idx_fgedu_fgfgfgsales_2026_q1_product_id ON fgedu_fgfgfgsales_2026_q1 (product_id);
CREATE INDEX idx_fgedu_fgfgfgsales_2026_q2_product_id ON fgedu_fgfgfgsales_2026_q2 (product_id);
CREATE INDEX idx_fgedu_fgfgfgsales_2026_q3_product_id ON fgedu_fgfgfgsales_2026_q3 (product_id);
CREATE INDEX idx_fgedu_fgfgfgsales_2026_q4_product_id ON fgedu_fgfgfgsales_2026_q4 (product_id);

# 插入测试数据
INSERT INTO fgedu_fgfgfgsales (sale_date, product_id, quantity, amount)
VALUES
(‘2026-01-15’, 1, 10, 100.00),
(‘2026-02-20’, 2, 5, 50.00),
(‘2026-04-10’, 1, 15, 150.00),
(‘2026-07-05’, 3, 8, 80.00),
(‘2026-10-20’, 2, 12, 120.00);

# 查询数据
SELECT * FROM fgedu_fgfgfgsales;

# 查看分区情况
SELECT tableoid::regclass AS partition, *
FROM fgedu_fgfgfgsales;

3.1.2 管理范围分区

# 添加新分区
CREATE TABLE fgedu_fgfgfgsales_2027_q1 PARTITION OF fgedu_fgfgfgsales
FOR VALUES FROM (‘2027-01-01’) TO (‘2027-04-01’);

# 为新分区创建索引
CREATE INDEX idx_fgedu_fgfgfgsales_2027_q1_product_id ON fgedu_fgfgfgsales_2027_q1 (product_id);

# 删除旧分区
DROP TABLE fgedu_fgfgfgsales_2026_q1;

# 合并分区
— 先删除现有分区
DROP TABLE fgedu_fgfgfgsales_2026_q2, fgedu_fgfgfgsales_2026_q3;

— 创建合并后的分区
CREATE TABLE fgedu_fgfgfgsales_2026_h2 PARTITION OF fgedu_fgfgfgsales
FOR VALUES FROM (‘2026-04-01’) TO (‘2026-10-01’);

# 拆分分区
— 先删除现有分区
DROP TABLE fgedu_fgfgfgsales_2026_q4;

— 创建拆分后的分区
CREATE TABLE fgedu_fgfgfgsales_2026_oct PARTITION OF fgedu_fgfgfgsales
FOR VALUES FROM (‘2026-10-01’) TO (‘2026-11-01’);

CREATE TABLE fgedu_fgfgfgsales_2026_nov PARTITION OF fgedu_fgfgfgsales
FOR VALUES FROM (‘2026-11-01’) TO (‘2026-12-01’);

CREATE TABLE fgedu_fgfgfgsales_2026_dec PARTITION OF fgedu_fgfgfgsales
FOR VALUES FROM (‘2026-12-01’) TO (‘2027-01-01’);

3.2 列表分区

3.2.1 创建列表分区表

# 创建列表分区表
CREATE TABLE fgedu_orders (
id SERIAL,
order_date DATE NOT NULL,
customer_id INTEGER NOT NULL,
status VARCHAR(20) NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (id, status)
) PARTITION BY LIST (status);

# 创建分区
CREATE TABLE fgedu_orders_pending PARTITION OF fgedu_orders
FOR VALUES IN (‘pending’);

CREATE TABLE fgedu_orders_processing PARTITION OF fgedu_orders
FOR VALUES IN (‘processing’);

CREATE TABLE fgedu_orders_shipped PARTITION OF fgedu_orders
FOR VALUES IN (‘shipped’);

CREATE TABLE fgedu_orders_delivered PARTITION OF fgedu_orders
FOR VALUES IN (‘delivered’);

CREATE TABLE fgedu_orders_cancelled PARTITION OF fgedu_orders
FOR VALUES IN (‘cancelled’);

# 为每个分区创建索引
CREATE INDEX idx_fgedu_orders_pending_customer_id ON fgedu_orders_pending (customer_id);
CREATE INDEX idx_fgedu_orders_processing_customer_id ON fgedu_orders_processing (customer_id);
CREATE INDEX idx_fgedu_orders_shipped_customer_id ON fgedu_orders_shipped (customer_id);
CREATE INDEX idx_fgedu_orders_delivered_customer_id ON fgedu_orders_delivered (customer_id);
CREATE INDEX idx_fgedu_orders_cancelled_customer_id ON fgedu_orders_cancelled (customer_id);

# 插入测试数据
INSERT INTO fgedu_orders (order_date, customer_id, status, total_amount)
VALUES
(‘2026-04-01’, 1, ‘pending’, 100.00),
(‘2026-04-02’, 2, ‘processing’, 150.00),
(‘2026-04-03’, 3, ‘shipped’, 200.00),
(‘2026-04-04’, 1, ‘delivered’, 100.00),
(‘2026-04-05’, 2, ‘cancelled’, 150.00);

# 查询数据
SELECT * FROM fgedu_orders;

# 查看分区情况
SELECT tableoid::regclass AS partition, *
FROM fgedu_orders;

3.2.2 管理列表分区

# 添加新分区
CREATE TABLE fgedu_orders_refunded PARTITION OF fgedu_orders
FOR VALUES IN (‘refunded’);

# 为新分区创建索引
CREATE INDEX idx_fgedu_orders_refunded_customer_id ON fgedu_orders_refunded (customer_id);

# 修改分区
— 先删除现有分区
DROP TABLE fgedu_orders_cancelled;

— 创建新的分区,包含多个值
CREATE TABLE fgedu_orders_cancelled_refunded PARTITION OF fgedu_orders
FOR VALUES IN (‘cancelled’, ‘refunded’);

# 处理未分区的数据
— 创建默认分区
CREATE TABLE fgedu_orders_default PARTITION OF fgedu_orders
DEFAULT;

# 为默认分区创建索引
CREATE INDEX idx_fgedu_orders_default_customer_id ON fgedu_orders_default (customer_id);

# 插入未分区的数据
INSERT INTO fgedu_orders (order_date, customer_id, status, total_amount)
VALUES (‘2026-04-06’, 3, ‘returned’, 200.00);

# 查询默认分区的数据
SELECT * FROM fgedu_orders_default;

3.3 哈希分区

3.3.1 创建哈希分区表

# 创建哈希分区表
CREATE TABLE fgedu_fgedus (
id SERIAL,
fgeduname VARCHAR(50) NOT NULL,
email VARCHAR(255) NOT NULL,
registration_date DATE NOT NULL,
PRIMARY KEY (id)
) PARTITION BY HASH (id);

# 创建分区
CREATE TABLE fgedu_fgedus_part1 PARTITION OF fgedu_fgedus
FOR VALUES WITH (MODULUS 4, REMAINDER 0);

CREATE TABLE fgedu_fgedus_part2 PARTITION OF fgedu_fgedus
FOR VALUES WITH (MODULUS 4, REMAINDER 1);

CREATE TABLE fgedu_fgedus_part3 PARTITION OF fgedu_fgedus
FOR VALUES WITH (MODULUS 4, REMAINDER 2);

CREATE TABLE fgedu_fgedus_part4 PARTITION OF fgedu_fgedus
FOR VALUES WITH (MODULUS 4, REMAINDER 3);

# 为每个分区创建索引
CREATE INDEX idx_fgedu_fgedus_part1_email ON fgedu_fgedus_part1 (email);
CREATE INDEX idx_fgedu_fgedus_part2_email ON fgedu_fgedus_part2 (email);
CREATE INDEX idx_fgedu_fgedus_part3_email ON fgedu_fgedus_part3 (email);
CREATE INDEX idx_fgedu_fgedus_part4_email ON fgedu_fgedus_part4 (email);

# 插入测试数据
INSERT INTO fgedu_fgedus (fgeduname, email, registration_date)
VALUES
(‘fgedu1’, ‘fgedu1@fgedu.net.cn’, ‘2026-04-01’),
(‘fgedu2’, ‘fgedu2@fgedu.net.cn’, ‘2026-04-02’),
(‘fgedu3’, ‘fgedu3@fgedu.net.cn’, ‘2026-04-03’),
(‘fgedu4’, ‘fgedu4@fgedu.net.cn’, ‘2026-04-04’),
(‘fgedu5’, ‘fgedu5@fgedu.net.cn’, ‘2026-04-05’);

# 查询数据
SELECT * FROM fgedu_fgedus;

# 查看分区情况
SELECT tableoid::regclass AS partition, *
FROM fgedu_fgedus;

3.3.2 管理哈希分区

# 增加分区数量
— 注意:哈希分区的数量通常在创建时确定,增加分区需要重新创建表
— 以下是一个重新分区的示例

— 1. 创建新的分区表
CREATE TABLE fgedu_fgedus_new (
id SERIAL,
fgeduname VARCHAR(50) NOT NULL,
email VARCHAR(255) NOT NULL,
registration_date DATE NOT NULL,
PRIMARY KEY (id)
) PARTITION BY HASH (id);

— 2. 创建更多分区
CREATE TABLE fgedu_fgedus_new_part1 PARTITION OF fgedu_fgedus_new
FOR VALUES WITH (MODULUS 8, REMAINDER 0);

CREATE TABLE fgedu_fgedus_new_part2 PARTITION OF fgedu_fgedus_new
FOR VALUES WITH (MODULUS 8, REMAINDER 1);

— 创建其他分区…

— 3. 迁移数据
INSERT INTO fgedu_fgedus_new SELECT * FROM fgedu_fgedus;

— 4. 重命名表
ALTER TABLE fgedu_fgedus RENAME TO fgedu_fgedus_old;
ALTER TABLE fgedu_fgedus_new RENAME TO fgedu_fgedus;

— 5. 为新分区创建索引
CREATE INDEX idx_fgedu_fgedus_new_part1_email ON fgedu_fgedus_new_part1 (email);
— 创建其他索引…

— 6. 删除旧表
DROP TABLE fgedu_fgedus_old;

风哥提示:在管理分区表时,应根据业务需求和数据增长情况,定期添加新分区和清理旧分区。同时,为每个分区创建适当的索引,以提高查询性能。学习交流加群风哥微信: itpux-com

Part04-生产案例与实战讲解

4.1 分区表实战案例

4.1.1 时间序列数据分区

# 场景:电商平台销售数据,需要按月份分区

# 1. 数据库设计
CREATE TABLE fgedu_fgfgfgsales (
id SERIAL,
sale_date TIMESTAMP NOT NULL,
product_id INTEGER NOT NULL,
customer_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (id, sale_date)
) PARTITION BY RANGE (sale_date);

# 2. 创建分区函数(自动创建分区)
CREATE OR REPLACE FUNCTION create_fgfgfgsales_partition(p_year INTEGER, p_month INTEGER)
RETURNS VOID AS $$
DECLARE
v_partition_name TEXT;
v_start_date TIMESTAMP;
v_end_date TIMESTAMP;
BEGIN
v_partition_name := ‘fgedu_fgfgfgsales_’ || p_year || ‘_’ || LPAD(p_month::TEXT, 2, ‘0’);
v_start_date := make_timestamp(p_year, p_month, 1, 0, 0, 0);
v_end_date := v_start_date + INTERVAL ‘1 month’;

EXECUTE format(
‘CREATE TABLE fgedu_IF NOT EXISTS %I PARTITION OF fgedu_fgfgfgsales
FOR VALUES FROM (%L) TO (%L)’,
v_partition_name, v_start_date, v_end_date
);

EXECUTE format(
‘CREATE INDEX IF NOT EXISTS %I ON %I (product_id)’,
v_partition_name || ‘_product_id_idx’, v_partition_name
);

EXECUTE format(
‘CREATE INDEX IF NOT EXISTS %I ON %I (customer_id)’,
v_partition_name || ‘_customer_id_idx’, v_partition_name
);
END;
$$ LANGUAGE plpgsql;

# 3. 创建未来6个月的分区
DO $$
DECLARE
i INTEGER;
BEGIN
FOR i IN 0..5 LOOP
PERFORM create_fgfgfgsales_partition(EXTRACT(YEAR FROM current_date + INTERVAL ‘1 month’ * i)::INTEGER,
EXTRACT(MONTH FROM current_date + INTERVAL ‘1 month’ * i)::INTEGER);
END LOOP;
END;
$$;

# 4. 查看分区
\dt fgedu_fgfgfgsales*

# 5. 插入测试数据
INSERT INTO fgedu_fgfgfgsales (sale_date, product_id, customer_id, quantity, unit_price, total_amount)
VALUES
(‘2026-04-01 10:00:00’, 1, 1, 2, 50.00, 100.00),
(‘2026-04-15 14:30:00’, 2, 2, 1, 100.00, 100.00),
(‘2026-05-01 09:00:00’, 1, 3, 3, 50.00, 150.00),
(‘2026-05-10 11:00:00’, 3, 1, 1, 200.00, 200.00);

# 6. 查询示例
— 查询特定月份的数据
SELECT * FROM fgedu_fgfgfgsales WHERE sale_date BETWEEN ‘2026-04-01’ AND ‘2026-04-30’;

— 查看查询执行计划(验证分区剪枝)
EXPLAIN ANALYZE SELECT * FROM fgedu_fgfgfgsales WHERE sale_date BETWEEN ‘2026-04-01’ AND ‘2026-04-30’;

— 聚合查询
SELECT product_id, SUM(total_amount) AS total_fgfgfgsales
FROM fgedu_fgfgfgsales
WHERE sale_date BETWEEN ‘2026-04-01’ AND ‘2026-05-31’
GROUP BY product_id
ORDER BY total_fgfgfgsales DESC;

# 7. 自动分区维护
— 创建定时任务,每月创建下个月的分区
CREATE OR REPLACE FUNCTION schedule_partition_creation()
RETURNS VOID AS $$
BEGIN
PERFORM create_fgfgfgsales_partition(EXTRACT(YEAR FROM current_date + INTERVAL ‘1 month’)::INTEGER,
EXTRACT(MONTH FROM current_date + INTERVAL ‘1 month’)::INTEGER);
END;
$$ LANGUAGE plpgsql;

— 使用pg_cron扩展设置定时任务
— 每月1日执行
— SELECT cron.schedule(‘0 0 1 * *’, ‘SELECT schedule_partition_creation();’);

4.1.2 日志数据分区

# 场景:应用日志数据,需要按天分区

# 1. 数据库设计
CREATE TABLE fgedu_fgapplication_logs (
id SERIAL,
log_time TIMESTAMP NOT NULL,
level VARCHAR(20) NOT NULL,
message TEXT NOT NULL,
source VARCHAR(100) NOT NULL,
fgedu_id INTEGER,
PRIMARY KEY (id, log_time)
) PARTITION BY RANGE (log_time);

# 2. 创建分区函数
CREATE OR REPLACE FUNCTION create_log_partition(p_date DATE)
RETURNS VOID AS $$
DECLARE
v_partition_name TEXT;
v_start_date TIMESTAMP;
v_end_date TIMESTAMP;
BEGIN
v_partition_name := ‘fgedu_fgapplication_logs_’ || to_char(p_date, ‘YYYYMMDD’);
v_start_date := p_date::TIMESTAMP;
v_end_date := v_start_date + INTERVAL ‘1 day’;

EXECUTE format(
‘CREATE TABLE fgedu_IF NOT EXISTS %I PARTITION OF fgedu_fgapplication_logs
FOR VALUES FROM (%L) TO (%L)’,
v_partition_name, v_start_date, v_end_date
);

EXECUTE format(
‘CREATE INDEX IF NOT EXISTS %I ON %I (level)’,
v_partition_name || ‘_level_idx’, v_partition_name
);

EXECUTE format(
‘CREATE INDEX IF NOT EXISTS %I ON %I (source)’,
v_partition_name || ‘_source_idx’, v_partition_name
);
END;
$$ LANGUAGE plpgsql;

# 3. 创建未来7天的分区
DO $$
DECLARE
i INTEGER;
BEGIN
FOR i IN 0..6 LOOP
PERFORM create_log_partition(current_date + i);
END LOOP;
END;
$$;

# 4. 插入测试数据
INSERT INTO fgedu_fgapplication_logs (log_time, level, message, source, fgedu_id)
VALUES
(‘2026-04-02 10:00:00’, ‘INFO’, ‘User logged in’, ‘auth’, 1),
(‘2026-04-02 10:30:00’, ‘ERROR’, ‘Database connection failed’, ‘db’, NULL),
(‘2026-04-03 09:00:00’, ‘INFO’, ‘Application started’, ‘fgapp’, NULL),
(‘2026-04-03 11:00:00’, ‘WARNING’, ‘Disk space low’, ‘system’, NULL);

# 5. 查询示例
— 查询特定日期的日志
SELECT * FROM fgedu_fgapplication_logs WHERE log_time BETWEEN ‘2026-04-02’ AND ‘2026-04-02 23:59:59’;

— 查询错误级别日志
SELECT * FROM fgedu_fgapplication_logs WHERE level = ‘ERROR’;

— 按来源统计日志数量
SELECT source, COUNT(*) AS log_count
FROM fgedu_fgapplication_logs
WHERE log_time BETWEEN ‘2026-04-01’ AND ‘2026-04-03’
GROUP BY source
ORDER BY log_count DESC;

# 6. 清理旧日志
— 删除7天前的分区
DO $$
DECLARE
v_partition_name TEXT;
v_date DATE;
BEGIN
v_date := current_date – INTERVAL ‘7 days’;
v_partition_name := ‘fgedu_fgapplication_logs_’ || to_char(v_date, ‘YYYYMMDD’);

EXECUTE format(‘DROP TABLE IF EXISTS %I’, v_partition_name);
END;
$$;

4.2 分区表性能优化

4.2.1 查询优化

# 分区表查询优化

# 1. 使用分区剪枝
— 确保查询条件包含分区键
— 好的查询(使用分区剪枝)
EXPLAIN ANALYZE SELECT * FROM fgedu_fgfgfgsales WHERE sale_date BETWEEN ‘2026-04-01’ AND ‘2026-04-30’;

— 不好的查询(不使用分区剪枝)
EXPLAIN ANALYZE SELECT * FROM fgedu_fgfgfgsales WHERE product_id = 1;

# 2. 索引优化
— 为每个分区创建适当的索引
— 为分区键创建索引(如果频繁用于查询)
CREATE INDEX idx_fgedu_fgfgfgsales_sale_date ON fgedu_fgfgfgsales (sale_date);

— 为常用查询列创建索引
CREATE INDEX idx_fgedu_fgfgfgsales_product_id ON fgedu_fgfgfgsales (product_id);

# 3. 聚合查询优化
— 使用并行查询
SET max_parallel_workers_per_gather = 4;

— 优化聚合查询
EXPLAIN ANALYZE SELECT product_id, SUM(total_amount) AS total_fgfgfgsales
FROM fgedu_fgfgfgsales
WHERE sale_date BETWEEN ‘2026-04-01’ AND ‘2026-06-30’
GROUP BY product_id
ORDER BY total_fgfgfgsales DESC;

# 4. 连接查询优化
— 确保连接条件包含分区键
EXPLAIN ANALYZE SELECT s.*, p.name
FROM fgedu_fgfgfgsales s
JOIN fgedu_products p ON s.product_id = p.id
WHERE s.sale_date BETWEEN ‘2026-04-01’ AND ‘2026-04-30’;

# 5. 限制结果集
— 使用LIMIT限制返回结果
SELECT * FROM fgedu_fgfgfgsales WHERE sale_date BETWEEN ‘2026-04-01’ AND ‘2026-04-30’ LIMIT 100;

4.2.2 存储优化

# 分区表存储优化

# 1. 表空间管理
— 为不同分区指定不同表空间
— 创建表空间
CREATE TABLESPACE fast_ssd LOCATION ‘/path/to/ssd’;
CREATE TABLESPACE archive_hdd LOCATION ‘/path/to/hdd’;

— 创建分区时指定表空间
CREATE TABLE fgedu_fgfgfgsales_2026_q1 PARTITION OF fgedu_fgfgfgsales
FOR VALUES FROM (‘2026-01-01’) TO (‘2026-04-01’)
TABLESPACE archive_hdd;

CREATE TABLE fgedu_fgfgfgsales_2026_q2 PARTITION OF fgedu_fgfgfgsales
FOR VALUES FROM (‘2026-04-01’) TO (‘2026-07-01’)
TABLESPACE fast_ssd;

# 2. 压缩存储
— 为分区启用压缩
ALTER TABLE fgedu_fgfgfgsales_2026_q1 SET (autovacuum_enabled = true, toast.compress = ‘pglz’);

# 3. 分区维护
— 定期清理旧分区
DO $$
DECLARE
v_partition_name TEXT;
v_date DATE;
BEGIN
FOR i IN 12..24 LOOP
v_date := current_date – INTERVAL ‘1 month’ * i;
v_partition_name := ‘fgedu_fgfgfgsales_’ || to_char(v_date, ‘YYYY_MM’);
EXECUTE format(‘DROP TABLE IF EXISTS %I’, v_partition_name);
END LOOP;
END;
$$;

# 4. 批量操作
— 批量插入数据
COPY fgedu_fgfgfgsales FROM ‘/path/to/fgfgfgsales_data.csv’ DELIMITER ‘,’ CSV HEADER;

— 批量更新数据
UPDATE fgedu_fgfgfgsales SET total_amount = quantity * unit_price
WHERE sale_date BETWEEN ‘2026-04-01’ AND ‘2026-04-30’;

# 5. 分区合并
— 合并多个小分区
CREATE TABLE fgedu_fgfgfgsales_2025 PARTITION OF fgedu_fgfgfgsales
FOR VALUES FROM (‘2025-01-01’) TO (‘2026-01-01’);

— 迁移数据
INSERT INTO fgedu_fgfgfgsales_2025 SELECT * FROM fgedu_fgfgfgsales WHERE sale_date BETWEEN ‘2025-01-01’ AND ‘2025-12-31’;

— 删除旧分区
DROP TABLE fgedu_fgfgfgsales_2025_q1, fgedu_fgfgfgsales_2025_q2, fgedu_fgfgfgsales_2025_q3, fgedu_fgfgfgsales_2025_q4;

4.2.3 维护优化

# 分区表维护优化

# 1. 定期VACUUM和ANALYZE
— 对所有分区执行VACUUM和ANALYZE
DO $$
DECLARE
v_partition RECORD;
BEGIN
FOR v_partition IN
SELECT inhrelid::regclass AS partition
FROM pg_inherits
WHERE inhparent = ‘fgedu_fgfgfgsales’::regclass
LOOP
EXECUTE format(‘VACUUM ANALYZE %I’, v_partition.partition);
END LOOP;
END;
$$;

# 2. 索引重建
— 定期重建索引
DO $$
DECLARE
v_partition RECORD;
BEGIN
FOR v_partition IN
SELECT inhrelid::regclass AS partition
FROM pg_inherits
WHERE inhparent = ‘fgedu_fgfgfgsales’::regclass
LOOP
EXECUTE format(‘REINDEX TABLE %I’, v_partition.partition);
END LOOP;
END;
$$;

# 3. 统计信息更新
— 手动更新统计信息
ANALYZE fgedu_fgfgfgsales;

# 4. 监控分区大小
— 查看各分区大小
SELECT
inhrelid::regclass AS partition,
pg_size_pretty(pg_relation_size(inhrelid)) AS size
FROM
pg_inherits
WHERE
inhparent = ‘fgedu_fgfgfgsales’::regclass
ORDER BY
size DESC;

# 5. 监控分区使用情况
— 查看各分区的记录数
SELECT
inhrelid::regclass AS partition,
count(*) AS row_count
FROM
pg_inherits
JOIN
pg_class ON inhrelid = oid
JOIN
pg_namespace ON pg_class.relnamespace = pg_namespace.oid
WHERE
inhparent = ‘fgedu_fgfgfgsales’::regclass
GROUP BY
inhrelid
ORDER BY
row_count DESC;

4.3 分区表常见问题处理

4.3.1 常见错误及解决方法

# 1. 分区键更新问题
# 错误信息:ERROR: cannot update partition key column “sale_date”
# 解决方法:
– 避免更新分区键
– 如果需要更新分区键,使用DELETE + INSERT替代

# 2. 分区不存在
# 错误信息:ERROR: no partition of relation “fgedu_fgfgfgsales” found for row
# 解决方法:
– 创建默认分区
– 自动创建分区的机制
– 在插入前检查分区是否存在

# 3. 分区剪枝不生效
# 问题:查询不使用分区剪枝,扫描所有分区
# 解决方法:
– 确保查询条件包含分区键
– 确保分区键的数据类型正确
– 运行ANALYZE更新统计信息

# 4. 分区过多
# 问题:创建了过多分区,导致管理困难
# 解决方法:
– 合并小分区
– 调整分区粒度
– 定期清理旧分区

# 5. 索引膨胀
# 问题:分区表索引占用空间过大
# 解决方法:
– 定期重建索引
– 优化索引策略
– 使用部分索引

# 6. 性能下降
# 问题:分区表查询性能下降
# 解决方法:
– 检查分区剪枝是否生效
– 优化查询语句
– 维护索引和统计信息
– 调整内存配置

4.3.2 分区表监控

# 分区表监控

# 1. 监控分区数量
SELECT
inhparent::regclass AS parent_table,
count(*) AS partition_count
FROM
pg_inherits
GROUP BY
inhparent
ORDER BY
partition_count DESC;

# 2. 监控分区大小
SELECT
inhparent::regclass AS parent_table,
inhrelid::regclass AS partition,
pg_size_pretty(pg_relation_size(inhrelid)) AS size
FROM
pg_inherits
ORDER BY
parent_table, size DESC;

# 3. 监控分区使用情况
SELECT
inhparent::regclass AS parent_table,
inhrelid::regclass AS partition,
count(*) AS row_count
FROM
pg_inherits
JOIN
pg_class ON inhrelid = oid
JOIN
pg_namespace ON pg_class.relnamespace = pg_namespace.oid
GROUP BY
inhparent, inhrelid
ORDER BY
parent_table, row_count DESC;

# 4. 监控分区查询性能
SELECT
query,
calls,
total_exec_time,
mean_exec_time
FROM
pg_stat_statements
WHERE
query LIKE ‘%fgedu_fgfgfgsales%’
ORDER BY
total_exec_time DESC
LIMIT 10;

# 5. 监控分区维护操作
SELECT
usename,
fgapplication_name,
query
FROM
pg_stat_activity
WHERE
query LIKE ‘%VACUUM%’ OR
query LIKE ‘%ANALYZE%’ OR
query LIKE ‘%REINDEX%’;

风哥教程针对风哥教程针对风哥教程针对生产环境建议:定期监控分区表的使用情况,包括分区数量、大小、查询性能等,及时发现并解决问题。同时,建立自动化的分区管理机制,确保分区表的高效运行。更多学习教程公众号风哥教程itpux_com

Part05-风哥经验总结与分享

5.1 分区表最佳实践

PostgreSQL分区表最佳实践:

  • 选择合适的分区策略:根据数据特点和查询模式选择合适的分区类型
  • 合理设计分区键:选择查询频繁、分布均匀的列作为分区键
  • 适当的分区粒度:避免创建过多或过少的分区
  • 自动分区管理:实现自动化的分区创建和清理机制
  • 为每个分区创建索引:根据查询需求为每个分区创建适当的索引
  • 定期维护:定期执行VACUUM、ANALYZE和REINDEX操作
  • 监控分区表:定期监控分区表的使用情况和性能
  • 存储管理:将不同分区存储在不同表空间,优化存储资源

5.2 分区表实施检查清单

# PostgreSQL分区表实施检查清单

## 规划阶段
– [ ] 评估数据量和增长趋势
– [ ] 分析查询模式和数据分布
– [ ] 选择合适的分区类型和键
– [ ] 确定分区粒度和数量
– [ ] 规划存储策略

## 实施阶段
– [ ] 创建分区表和分区
– [ ] 为每个分区创建适当的索引
– [ ] 实现分区创建和管理的自动化
– [ ] 测试分区表的功能和性能
– [ ] 迁移现有数据到分区表

## 优化阶段
– [ ] 优化查询语句,确保使用分区剪枝
– [ ] 调整内存和存储配置
– [ ] 优化索引策略
– [ ] 监控和分析性能指标

## 维护阶段
– [ ] 定期创建新分区
– [ ] 定期清理旧分区
– [ ] 定期执行VACUUM和ANALYZE
– [ ] 定期重建索引
– [ ] 监控分区表的使用情况

## 扩展阶段
– [ ] 考虑分区表的水平扩展
– [ ] 评估分区策略的有效性
– [ ] 调整分区策略以适应业务变化
– [ ] 考虑使用分区表的高级特性

5.3 分区表工具推荐

PostgreSQL分区表相关工具推荐:

  • pg_partman:PostgreSQL分区管理工具,支持自动分区创建和维护
  • pg_cron:PostgreSQL定时任务扩展,用于自动执行分区维护任务
  • pgAdmin:图形化工具,支持分区表的管理和监控
  • PostgreSQL partitioning:内置的分区表功能
  • TimescaleDB:基于PostgreSQL的时序数据库扩展,优化时间序列数据的分区
  • pg_stat_statements:统计查询性能的扩展,用于监控分区表查询性能
风哥提示:分区表是处理海量数据的有效方法,但需要根据具体的业务需求和数据特点进行合理设计和优化。在实施分区表时,应充分考虑数据分布、查询模式和维护需求,以获得最佳的性能和可维护性。from PostgreSQL视频:www.itpux.com

持续改进:分区表的设计和优化是一个持续的过程,需要根据业务需求的变化和数据量的增长,不断调整和优化分区策略、索引设计和维护计划。建议定期回顾分区表的使用情况,评估其性能和可维护性,及时进行调整和优化。

本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html

联系我们

在线咨询:点击这里给我发消息

微信号:itpux-com

工作日:9:30-18:30,节假日休息