在 Postgres 中按字符串标识符进行动态 table 分区
Dynamic table partitioning by string identifier in Postgres
我想在 Postgres 中按以前未知的值对 table 进行分区。在我的场景中,该值将是 device_id 这是一个字符串。
这是目前的情况:
Table 'device_data' - 存储从设备发送的传感器数据,由 DDL 定义:
CREATE TABLE warehouse.device_data (
id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('device_data_id_seq'::regclass),
device_id TEXT NOT NULL,
device_data BYTEA NOT NULL,
-- contains additional fields which are omitted for brevity
received_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now()
);
Table 目前拥有数百万条记录,查询需要花费大量时间。大多数查询包含 WHERE device_id='something'
子句。
我想到的解决方案是为每个 device_id
.
创建 table 个分区
在 Postgres 中是否可以为每个 device_id
创建 table 个分区?
我浏览了 Postgres 文档和发现的几个示例,但它们都使用固定边界来创建分区。我的解决方案需要:
- 当新的
device_id
是第一个时,即时创建新的 table 分区
遇到
- 存储到现有分区,如果
device_id
已知并且分区device_id
已经存在
我希望使用 table 分区来完成此操作,因为它允许跨多个 device_id
进行查询。
我喜欢动态分区的想法。我不知道它会如何影响性能,因为我从未使用过它。
将 id
的类型更改为 int default 0
并手动创建序列以避免对单个插入进行多次 nextval()
调用:
create table device_data (
id int primary key default 0,
device_id text not null,
device_data text not null, -- changed for tests
received_at timestamp without time zone default now()
);
create sequence device_data_seq owned by device_data.id;
在触发函数中使用动态sql:
create or replace function before_insert_on_device_data()
returns trigger language plpgsql as $$
begin
execute format(
$f$
create table if not exists %I (
check (device_id = %L)
) inherits (device_data)
$f$,
concat('device_data_', new.device_id),
new.device_id);
execute format(
$f$
insert into %I
values (nextval('device_data_seq'), %L, %L, default)
$f$,
concat('device_data_', new.device_id),
new.device_id,
new.device_data);
return null;
end $$;
create trigger before_insert_on_device_data
before insert on device_data
for each row execute procedure before_insert_on_device_data();
测试:
insert into device_data (device_id, device_data) values
('first', 'data 1'),
('second', 'data 1'),
('first', 'data 2'),
('second', 'data 2');
select * from device_data_first;
id | device_id | device_data | received_at
----+-----------+-------------+----------------------------
1 | first | data 1 | 2016-10-18 19:50:40.179955
3 | first | data 2 | 2016-10-18 19:50:40.179955
(2 rows)
select * from device_data_second;
id | device_id | device_data | received_at
----+-----------+-------------+----------------------------
2 | second | data 1 | 2016-10-18 19:50:40.179955
4 | second | data 2 | 2016-10-18 19:50:40.179955
(2 rows)
我想在 Postgres 中按以前未知的值对 table 进行分区。在我的场景中,该值将是 device_id 这是一个字符串。
这是目前的情况:
Table 'device_data' - 存储从设备发送的传感器数据,由 DDL 定义:
CREATE TABLE warehouse.device_data (
id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('device_data_id_seq'::regclass),
device_id TEXT NOT NULL,
device_data BYTEA NOT NULL,
-- contains additional fields which are omitted for brevity
received_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now()
);
Table 目前拥有数百万条记录,查询需要花费大量时间。大多数查询包含 WHERE device_id='something'
子句。
我想到的解决方案是为每个 device_id
.
在 Postgres 中是否可以为每个 device_id
创建 table 个分区?
我浏览了 Postgres 文档和发现的几个示例,但它们都使用固定边界来创建分区。我的解决方案需要:
- 当新的
device_id
是第一个时,即时创建新的 table 分区 遇到 - 存储到现有分区,如果
device_id
已知并且分区device_id
已经存在
我希望使用 table 分区来完成此操作,因为它允许跨多个 device_id
进行查询。
我喜欢动态分区的想法。我不知道它会如何影响性能,因为我从未使用过它。
将 id
的类型更改为 int default 0
并手动创建序列以避免对单个插入进行多次 nextval()
调用:
create table device_data (
id int primary key default 0,
device_id text not null,
device_data text not null, -- changed for tests
received_at timestamp without time zone default now()
);
create sequence device_data_seq owned by device_data.id;
在触发函数中使用动态sql:
create or replace function before_insert_on_device_data()
returns trigger language plpgsql as $$
begin
execute format(
$f$
create table if not exists %I (
check (device_id = %L)
) inherits (device_data)
$f$,
concat('device_data_', new.device_id),
new.device_id);
execute format(
$f$
insert into %I
values (nextval('device_data_seq'), %L, %L, default)
$f$,
concat('device_data_', new.device_id),
new.device_id,
new.device_data);
return null;
end $$;
create trigger before_insert_on_device_data
before insert on device_data
for each row execute procedure before_insert_on_device_data();
测试:
insert into device_data (device_id, device_data) values
('first', 'data 1'),
('second', 'data 1'),
('first', 'data 2'),
('second', 'data 2');
select * from device_data_first;
id | device_id | device_data | received_at
----+-----------+-------------+----------------------------
1 | first | data 1 | 2016-10-18 19:50:40.179955
3 | first | data 2 | 2016-10-18 19:50:40.179955
(2 rows)
select * from device_data_second;
id | device_id | device_data | received_at
----+-----------+-------------+----------------------------
2 | second | data 1 | 2016-10-18 19:50:40.179955
4 | second | data 2 | 2016-10-18 19:50:40.179955
(2 rows)