pg数据库分区表range使用
docker-compose.yml
# docker-compose.yml
version: '3.8'
services:
django_test_db:
build: .
container_name: pg_partman
environment:
POSTGRES_USER: partman
POSTGRES_PASSWORD: securepass
POSTGRES_DB: parts_db
volumes:
- pgdata:/var/lib/postgresql/data
- ./postgres-init.sql:/docker-entrypoint-initdb.d/init.sql
# 映射到我本地端口是5444,方便本地调试
ports:
- "5444:5432"
volumes:
pgdata: {}
postgres-init.sql
CREATE EXTENSION pg_partman;
models.py
# Create your models here.
# parts/models.py
from django.db import models
from django.utils import timezone
class Part(models.Model):
id = models.AutoField(primary_key=True)
sn = models.CharField(max_length=32)
create_time = models.DateTimeField(default=timezone.now)
status = models.SmallIntegerField(choices=[
(0, 'Pending'),
(1, 'In Progress'),
(2, 'Completed')
])
class Meta:
managed = False # 禁用Django的自动建表
db_table = 'parts'
unique_together = (('id', 'create_time'),)
@classmethod
def create_partition(cls, year: int, month: int):
"""创建分区表的原生SQL操作"""
from django.db import connection
table_name = f'parts_{year}_{month:02d}'
with connection.cursor() as cursor:
cursor.execute(f"""
CREATE TABLE {table_name} PARTITION OF parts
FOR VALUES FROM ('{year}-{month:02d}-01')
TO ('{year}-{month+1:02d}-01');
""")
init_parts_db.py
from django.core.management.base import BaseCommand
from django.db import connection
from parts.models import Part
from datetime import datetime, timedelta
class Command(BaseCommand):
help = 'Initialize the parts database with partitions'
def handle(self, *args, **kwargs):
try:
with connection.cursor() as cursor:
cursor.execute("""
CREATE TABLE IF NOT EXISTS parts (
id SERIAL,
sn VARCHAR(32) NOT NULL,
create_time TIMESTAMPTZ NOT NULL,
status SMALLINT,
PRIMARY KEY (id, create_time)
) PARTITION BY RANGE (create_time);
""")
# 创建未来3个月的分区
now = datetime.now()
for month_offset in range(3):
dt = now + timedelta(days=30*month_offset)
Part.create_partition(dt.year, dt.month)
self.stdout.write(self.style.SUCCESS(f'Created partition for {dt.year}-{dt.month:02d}'))
self.stdout.write(self.style.SUCCESS('Database initialized successfully'))
except Exception as e:
self.stdout.write(self.style.ERROR(f'Error: {str(e)}'))
配置分区策略(替换手动创建分区)
# 修改后的初始化命令(parts/management/commands/init_db.py)
class Command(BaseCommand):
help = 'Initialize partitions with pg_partman'
def handle(self, *args, **kwargs):
try:
with connection.cursor() as cursor:
# 创建父表(如果尚未存在)
cursor.execute("""
CREATE TABLE IF NOT EXISTS parts (
id SERIAL,
sn VARCHAR(32) NOT NULL,
create_time TIMESTAMPTZ NOT NULL,
status SMALLINT,
PRIMARY KEY (id, create_time)
) PARTITION BY RANGE (create_time);
""")
# 配置 pg_partman 自动分区策略
cursor.execute("""
SELECT partman.create_parent(
p_parent_table => 'public.parts',
p_control => 'create_time', -- 分区键
p_type => 'native', -- 使用原生分区
p_interval => '1 month', -- 每月一个分区
p_premake => 3 -- 预创建未来3个月的分区
);
""")
# 设置保留策略(例如保留最近12个月)
cursor.execute("""
UPDATE partman.part_config
SET retention = '12 months',
retention_keep_table = false -- 自动删除旧分区
WHERE parent_table = 'public.parts';
""")
self.stdout.write(self.style.SUCCESS('Partitioning configured'))
except Exception as e:
self.stdout.write(self.style.ERROR(f'Error: {str(e)}'))
启用自动维护
pg_partman 需要定期执行维护任务。你有两种选择:
# 修改后的 Dockerfile
FROM postgres:14
# 安装插件
RUN apt-get update && \
apt-get install -y \
postgresql-14-partman \
postgresql-14-cron \ # 添加 pg_cron
&& rm -rf /var/lib/apt/lists/*
### 允许 pg_cron 在启动时加载
RUN echo "shared_preload_libraries = 'pg_cron'" >> /usr/share/postgresql/postgresql.conf.sample
RUN echo "cron.database_name = 'postgres'" >> /usr/share/postgresql/postgresql.conf.sample
在 PostgreSQL 中启用 pg_cron 并配置定时任务:
CREATE EXTENSION pg_cron;
-- 每天凌晨执行分区维护
SELECT cron.schedule(
'pg_partman_maintenance',
'0 3 * * *', -- Cron 表达式(每天3点)
$$CALL partman.run_maintenance_proc()$$
);
选项 2:手动触发维护
如果不想用 pg_cron,可以定期手动调用:
CALL partman.run_maintenance_proc();
验证配置
检查分区是否自动创建:
-- 查看所有分区
SELECT * FROM partman.show_partitions('public.parts');
-- 查看配置
SELECT * FROM partman.part_config;
关键注意事项
主键约束 确保分区键(create_time)是主键的一部分(你的模型已满足此要求)。 时区问题 使用 TIMESTAMPTZ(带时区的时间戳)而非 TIMESTAMP,避免分区边界混淆。 数据迁移 如果已有数据,需要将数据迁移到分区表中(可通过 pg_partman 的 partition_data_* 函数处理)。