我可以想象按日期(特别是日志)进行表分区是一种广泛使用的方法,但我无法找到解决我的问题的好方法。
我想按周创建一个表分区(记录的数量太大了,不能按月创建)。它每周一次的原因是我需要一个算法的数据,该算法将在过程中寻找日期。
我的问题是我希望它创建考虑周的分区并使用我必须手动创建的“典型”方法。像这样的东西。
CREATE TABLE measurement_y2013w01 (
CHECK ( logdate >= DATE '2013-01-07' AND logdate < DATE '2013-01-14' )
) INHERITS (measurement);
CREATE TABLE measurement_y2006w02 (
CHECK ( logdate >= DATE '2013-01-14' AND logdate < DATE '2013-01-21' )
) INHERITS (measurement);
...
但是我想让它自动生成。我不想每周都创建一个分区。
我的命名规则是分区命名为 yYYYYwWW 或开始数据为 dYYYYMMDD。
我想在使用这样的东西插入时检查分区:
SELECT
nmsp_parent.nspname AS parent_schema,
parent.relname AS parent,
nmsp_child.nspname AS child,
child.relname AS child_schema
FROM pg_inherits
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
JOIN pg_class child ON pg_inherits.inhrelid = child.oid
JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace
JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace
如果分区不存在,则在插入之前创建它,但考虑到插入的记录数,这接缝效率很低。
我的另一个选择是每周运行一个外部进程来创建这个分区,但我试图避免这种情况。
是否有我缺少的更有效的解决方案,例如用于月度检查?
好吧,让我们自己创建一个函数来处理它!
CREATE OR REPLACE FUNCTION create_partition_measurement( DATE, DATE )
returns void AS $$
DECLARE
create_query text;
BEGIN
FOR create_query IN SELECT
'CREATE TABLE measurement_' || TO_CHAR( d, 'YYYY_WW' ) || ' (
CHECK ( EXTRACT(YEAR FROM logdate) = EXTRACT(YEAR FROM TIMESTAMP ''' || d || ''') AND EXTRACT(WEEK FROM logdate) = EXTRACT(WEEK FROM TIMESTAMP ''' || d || ''') )
) INHERITS (measurement);'
FROM generate_series( $1, $2, '1 week' ) AS d LOOP
EXECUTE create_query;
END LOOP;
END;
$$
language plpgsql;
有了这个你现在可以调用类似的东西
SELECT create_partition_measurement ('2015/02/08','2015/03/01');
并创建分区。自动化的第一步,完成。
我使用以下测试表在自己的数据库中测试了所有这些:
CREATE TABLE measurement (id INT NOT NULL PRIMARY KEY, id_user INT NOT NULL, logdate TIMESTAMP NOT NULL);
使用上述函数创建分区后,我能够:
这应该够了=)
现在,关于自动化创建过程。我使用一个简单的 cron 脚本每月为我调用此函数,并使用几个监控脚本来确保一切正常工作。 cron 看起来像这样:
0 0 1 * * /var/lib/postgresql/create_partitions.sh
脚本将使用当前日期和当前日期 + 1 个月运行命令。它看起来像这样:
startDate=`date "+%Y/%m/%d"`
endDate=`date -u -d "+1 month -$(date +%d) days" "+%Y/%m/%d"
psql -U "$dbUser" -w -c "SELECT create_partition_measurement('$startDate','$endDate');"
如果您需要在表中包含索引、PK、FK,或帮助使用触发器来完成所有这些工作,请告诉我。
您可以使用date_trunc函数将数据值四舍五入到一周的第一天。对于分区命名,您可以使用年份中的年份和周数YYWW:
CREATE TABLE measurement_1301 (
CHECK ( date_trunc( 'week', logdate )::date = DATE '2013-01-07') )
INHERITS (measurement);
CREATE TABLE measurement_1302 (
CHECK ( date_trunc( 'week', logdate )::date = DATE '2013-01-14') )
INHERITS (measurement);
CREATE TABLE measurement_1303 (
CHECK ( date_trunc( 'week', logdate )::date = DATE '2013-01-21') )
INHERITS (measurement);
-- Default partition:
CREATE TABLE measurement_default () INHERITS (measurement);
分区名称生成使用
to_char( logdate::date, 'YYWW')
如果你喜欢yYYYYwWW:to_char( logdate::date, '"y"YYYY"w"WW')
要检查现有分区,您可以使用非常简单的查询:
SELECT relname FROM pg_class
WHERE relname ~ '^measurement_[0-9]{4}$'
ORDER BY RIGHT(relname,4) DESC
如果给定周没有分区,数据路由触发器将插入到适当的分区并回退到默认值。
CREATE OR REPLACE FUNCTION measurement_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF to_char( NEW.logdate::date, 'YYWW') = '1301' THEN
INSERT INTO measurement_1301 VALUES (NEW.*);
ELSIF to_char( NEW.logdate::date, 'YYWW') = '1302' THEN
INSERT INTO measurement_1302 VALUES (NEW.*);
ELSIF to_char( NEW.logdate::date, 'YYWW') = '1303' THEN
INSERT INTO measurement_1303 VALUES (NEW.*);
-- ...
ELSE
INSERT INTO measurement_default VALUES (NEW.*);
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER measurement_insert_tr BEFORE INSERT ON measurement
FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger()
您将预先创建所有分区。或者你可以使用默认分区,不时地重新分区存储在那里的数据,创建新分区并调整插入触发器。
PS你可以在这里找到基于触发器的分区解决方案的脚本http://hg.nowitworks.eu/postgresql-triggers-and-partitions
如果有帮助,我写了一个 postgres 触发器来创建一个按天自动分区的表。创建继承表是自动发生的。要按周分区,您必须更改日期->字符串映射,仅此而已。
我改编了一个我现在找不到的另一个响应的代码
您可以创建一个插入触发器:
CREATE OR REPLACE FUNCTION trg_measurement_partition()
RETURNS trigger AS
$func$
DECLARE
_tbl text := to_char(NEW.logdate, '"measurement_y"IYYY_"w"IW');
_min_date date := date_trunc('week', NEW.logdate)::date;
_max_date date := date_trunc('week', NEW.logdate)::date + 7;
_min_live date := date_trunc('week', NEW.logdate)::date - 1;
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public'
AND c.relname = _tbl
AND c.relkind = 'r') THEN
EXECUTE format('CREATE TABLE IF NOT EXISTS %I
(CHECK (logdate::date >= to_date(%L, ''yyyy-mm-dd'') AND
logdate::date < to_date(%L, ''yyyy-mm-dd'')),
LIKE measurement INCLUDING INDEXES )
INHERITS (measurement)'
, _tbl
, to_char(_min_date, 'YYYY-MM-DD')
, to_char(_max_date, 'YYYY-MM-DD')
);
IF (current_date >= _min_date) and (current_date < _max_date) THEN
EXECUTE format('CREATE OR REPLACE VIEW v_measurement AS (
SELECT * FROM measurement
WHERE (logdate::date >= to_date(%L, ''yyyy-mm-dd'') AND
logdate::date < to_date(%L, ''yyyy-mm-dd''))
)'
, to_char(_min_live, 'YYYY-MM-DD')
, to_char(_max_date, 'YYYY-MM-DD')
);
END IF;
END IF;
EXECUTE 'INSERT INTO ' || quote_ident(_tbl) || ' VALUES ($1.*)'
USING NEW;
RETURN NULL;
END
$func$ LANGUAGE plpgsql SET search_path = public;
DROP TRIGGER IF EXISTS ins_measurement on measurement;
CREATE TRIGGER ins_measurement
BEFORE INSERT ON measurement
FOR EACH ROW EXECUTE PROCEDURE trg_measurement_partition();
触发器执行以下任务:
pg_catalog
非常快)。logdate
CREATE TABLE ... LIKE measurement INCLUDING INDEXES INHERITS (measurement)
)答案假设存在一个空的
measurement
表作为模板,并且logdate
字段是timestamp
。在该表中,最好为迄今为止类型转换的 logdate
字段创建一个索引:
drop index if exists measurement_logdate_part_idx;
CREATE UNIQUE INDEX measurement_logdate_part_idx
ON measurement((logdate::date) DESC NULLS LAST, id);