jer*_*ran 6 postgresql performance insert postgresql-9.3 write-ahead-logging postgresql-performance
我有一个大约 310 万行的表,其定义和索引如下:
CREATE TABLE digiroad_liikenne_elementti (
ogc_fid serial NOT NULL,
wkb_geometry geometry(Geometry,4258),
tiee_tila numeric(9,0),
vaylatyypp numeric(9,0),
toiminnall numeric(9,0),
eurooppati character varying(254),
kansalline numeric(9,0),
tyyppi numeric(9,0),
liikennevi numeric(9,0),
ens_talo_o numeric(9,0),
talonumero numeric(9,0),
ens_talo_v numeric(9,0),
oik_puol_t character varying(254),
tieosan_ta numeric(9,0),
viim_talo_ numeric(9,0),
viim_tal_1 numeric(9,0),
vas_puol_t character varying(254),
laut_tyypp numeric(9,0),
lautta_lii numeric(9,0),
inv_paalu_ numeric(19,11),
inv_paal_1 numeric(19,11),
liitalue_o numeric(9,0),
ketju_oid numeric(9,0),
tietojoukk numeric(9,0),
ajoratanum numeric(4,0),
viite_guid character varying(254),
"timestamp" date,
tiee_kunta numeric(9,0),
toissij_ti character varying(254),
viite_oid numeric(9,0),
k_elem_id numeric(9,0),
region character varying(40) DEFAULT 'REGION'::character varying,
CONSTRAINT digiroad_liikenne_elementti_pkey PRIMARY KEY (ogc_fid)
);
CREATE INDEX digiroad_liikenne_elementti_wkb_geometry_geom_idx
ON digiroad_liikenne_elementti USING gist (wkb_geometry);
CREATE INDEX dle_k_elem_id_idx
ON digiroad_liikenne_elementti USING btree (k_elem_id);
CREATE INDEX dle_ogc_fid_idx
ON digiroad_liikenne_elementti USING btree (ogc_fid);
CREATE INDEX dle_region_idx
ON digiroad_liikenne_elementti USING btree (region COLLATE pg_catalog."default");
Run Code Online (Sandbox Code Playgroud)
另一个有 860 万行的表包含第一个表的行的属性,这些表可以用k_elem_idAND 连接region。
CREATE TABLE digiroad_segmentti (
ogc_fid serial NOT NULL,
wkb_geometry geometry(Geometry,4258),
segm_tila numeric(9,0),
tyyppi numeric(9,0),
loppupiste numeric(19,11),
alkupiste numeric(19,11),
vaikutuska numeric(9,0),
vaikutussu numeric(9,0),
vaikutusai character varying(254),
tieosanume numeric(19,11),
tienumero numeric(9,0),
dyn_arvo numeric(9,0),
dyn_tyyppi numeric(9,0),
omistaja_t numeric(9,0),
pysakki_va numeric(9,0),
pysakki_ty numeric(9,0),
pysakki_su numeric(9,0),
pysakki_ka numeric(9,0),
pysakki_yl character varying(254),
palvelu_pa numeric(9,0),
toissijain numeric(9,0),
siltataitu numeric(9,0),
rdtc_tyypp numeric(9,0),
rdtc_alaty numeric(9,0),
rdtc_paikk numeric(19,11),
rdtc_luokk numeric(9,0),
rdtc_liitt character varying(254),
palvelu_ob numeric(9,0),
ketju_oid numeric(9,0),
tietojoukk numeric(9,0),
ajoratanum numeric(4,0),
viite_guid character varying(254),
"timestamp" date,
sivusiirty numeric(19,11),
toissij_ti character varying(254),
viite_oid numeric(9,0),
k_elem_id numeric(9,0),
region character varying(40) DEFAULT 'REGION'::character varying,
CONSTRAINT digiroad_segmentti_pkey PRIMARY KEY (ogc_fid)
);
CREATE INDEX digiroad_segmentti_wkb_geometry_geom_idx
ON digiroad_segmentti USING gist (wkb_geometry);
CREATE INDEX ds_dyn_arvo_idx
ON digiroad_segmentti USING btree (dyn_arvo);
CREATE INDEX ds_dyn_tyyppi_idx
ON digiroad_segmentti USING btree (dyn_tyyppi);
CREATE INDEX ds_k_elem_id_idx
ON digiroad_segmentti USING btree (k_elem_id);
CREATE INDEX ds_ogc_fid_idx
ON digiroad_segmentti USING btree (ogc_fid);
CREATE INDEX ds_region_idx
ON digiroad_segmentti USING btree (region COLLATE pg_catalog."default");
CREATE INDEX ds_tyyppi_idx
ON digiroad_segmentti USING btree (tyyppi);
Run Code Online (Sandbox Code Playgroud)
我正在尝试将第一个表的行(经过一些修改)插入到新表中:
CREATE TABLE edge_table (
id serial NOT NULL,
geom geometry,
source integer,
target integer,
km double precision,
kmh double precision DEFAULT 60,
kmh_winter double precision DEFAULT 50,
cost double precision,
cost_winter double precision,
reverse_cost double precision,
reverse_cost_winter double precision,
x1 double precision,
y1 double precision,
x2 double precision,
y2 double precision,
k_elem_id integer,
region character varying(40),
CONSTRAINT edge_table_pkey PRIMARY KEY (id)
);
Run Code Online (Sandbox Code Playgroud)
由于运行单个插入语句需要很长时间,而且我无法查看该语句是否被卡住或其他原因,因此我决定在函数的循环内以较小的块执行它。
该函数如下所示:
DROP FUNCTION IF EXISTS insert_function();
CREATE OR REPLACE FUNCTION insert_function()
RETURNS VOID AS
$$
DECLARE
const_type_1 CONSTANT int := 5;
const_type_2 CONSTANT int := 11;
i int := 0;
row_count int;
BEGIN
CREATE TABLE IF NOT EXISTS edge_table (
id serial PRIMARY KEY,
geom geometry,
source integer,
target integer,
km double precision,
kmh double precision DEFAULT 60,
kmh_winter double precision DEFAULT 50,
cost double precision,
cost_winter double precision,
reverse_cost double precision,
reverse_cost_winter double precision,
x1 double precision,
y1 double precision,
x2 double precision,
y2 double precision,
k_elem_id integer,
region varchar(40)
);
batch_size := 1000;
SELECT COUNT(*) FROM digiroad_liikenne_elementti INTO row_count;
WHILE i*batch_size < row_count LOOP
RAISE NOTICE 'insert: % / %', i * batch_size, row_count;
INSERT INTO edge_table (kmh, kmh_winter, k_elem_id, region)
SELECT CASE WHEN DS.dyn_arvo IS NULL THEN 60 ELSE DS.dyn_arvo END,
CASE WHEN DS.dyn_Arvo IS NULL THEN 50 ELSE DS.dyn_arvo END,
DR.k_elem_id,
DR.region
FROM (
SELECT DLE.k_elem_id,
DLE.region,
FROM digiroad_liikenne_elementti DLE
WHERE DLE.ogc_fid >= i * batch_size
AND
DLE.ogc_fid <= i * batch_size + batch_size
) AS DR
LEFT JOIN
digiroad_segmentti DS ON
DS.k_elem_id = DR.k_elem_id
AND
DS.region = DR.region
AND
DS.tyyppi = const_type_1
AND
DS.dyn_tyyppi = const_type_2;
i := i + 1;
END LOOP;
END;
$$
LANGUAGE 'plpgsql' VOLATILE STRICT;
Run Code Online (Sandbox Code Playgroud)
问题是它开始非常快地通过循环,但在某些时候会减慢到爬行。当它变慢时,同时我的 Windows 8 任务管理器中的磁盘使用率上升到 99%,所以我怀疑这与问题有关。
INSERT使用一些随机值自行运行语句i执行速度非常快,因此问题似乎仅在函数内的循环中运行时才会出现。这是EXPLAIN (ANALYZE,BUFFERS)来自一个这样的单一执行:
Insert on edge_table (cost=0.86..361121.68 rows=1031 width=23) (actual time=3405.101..3405.101 rows=0 loops=1)
Buffers: shared hit=36251 read=3660 dirtied=14
-> Nested Loop Left Join (cost=0.86..361121.68 rows=1031 width=23) (actual time=61.901..3377.609 rows=986 loops=1)
Buffers: shared hit=32279 read=3646
-> Index Scan using dle_ogc_fid_idx on digiroad_liikenne_elementti dle (cost=0.43..85.12 rows=1031 width=19) (actual time=31.918..57.309 rows=986 loops=1)
Index Cond: ((ogc_fid >= 200000) AND (ogc_fid < 201000))
Buffers: shared hit=27 read=58
-> Index Scan using ds_k_elem_id_idx on digiroad_segmentti ds (cost=0.44..350.16 rows=1 width=23) (actual time=2.861..3.337 rows=0 loops=986)
Index Cond: (k_elem_id = dle.k_elem_id)
Filter: ((tyyppi = 5::numeric) AND (dyn_tyyppi = 11::numeric) AND (vaikutussu = 3::numeric) AND ((region)::text = (dle.region)::text))
Rows Removed by Filter: 73
Buffers: shared hit=31266 read=3588
Total runtime: 3405.270 ms
Run Code Online (Sandbox Code Playgroud)
我的系统在具有 8Gb RAM 的 Windows 8 上运行 PostgreSQL 9.3.5。
我尝试了不同的批量大小,以不同的方式进行查询并在 Postgres 配置中增加内存变量,但似乎没有什么能真正解决这个问题。
已从其默认值更改的配置变量:
shared_buffers = 2048MB
work_mem = 64MB
effective_cache_size = 6000MB
Run Code Online (Sandbox Code Playgroud)
我想找出导致这种情况发生的原因以及可以采取的措施。
当创建一个新表避免写的成本预写日志(WAL) 完全有CREATE TABLE AS。
请参阅@Kassandry 的回答以了解 WAL 如何解决这个问题。
CREATE OR REPLACE FUNCTION insert_function()
RETURNS void AS
$func$
DECLARE
const_type_1 CONSTANT int := 5;
const_type_2 CONSTANT int := 11;
BEGIN
CREATE SEQUENCE edge_table_id_seq;
CREATE TABLE edge_table AS
SELECT nextval('edge_table_id_seq'::regclass)::int AS id
, NULL::geometry AS geom
, NULL::integer AS source
, target::integer AS target
, NULL::float8 AS km
, COALESCE(DS.dyn_arvo::float8, float8 '60') AS kmh
, COALESCE(DS.dyn_Arvo::float8, float8 '50') AS kmh_winter
, NULL::float8 AS cost
, NULL::float8 AS cost_winter
, NULL::float8 AS reverse_cost
, NULL::float8 AS reverse_cost_winter
, NULL::float8 AS x1
, NULL::float8 AS y1
, NULL::float8 AS x2
, NULL::float8 AS y2
, D.k_elem_id::integer AS k_elem_id
, D.region::varchar(40) AS region
FROM digiroad_liikenne_elementti D
LEFT JOIN digiroad_segmentti DS
ON DS.k_elem_id = D.k_elem_id
AND DS.region = D.region
AND DS.tyyppi = const_type_1
AND DS.dyn_tyyppi = const_type_2;
ALTER TABLE edge_table
ADD CONSTRAINT edge_table_pkey PRIMARY KEY(id)
, ALTER COLUMN id SET NOT NULL
, ALTER COLUMN id SET DEFAULT nextval('edge_table_id_seq'::regclass)
, ALTER COLUMN kmh SET DEFAULT 60
, ALTER COLUMN kmh_winter SET DEFAULT 50;
ALTER SEQUENCE edge_table_id_seq OWNED BY edge_table.id;
END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
除了避免存档器或 WAL 发送器处理 WAL 数据的时间之外,这样做实际上会使某些命令更快,因为它们被设计为根本不写入 WAL 如果
wal_level是minimal。(他们可以通过fsync在最后执行 an比编写 WAL更便宜地保证碰撞安全 。)这适用于以下命令:
CREATE TABLE AS SELECT
CREATE INDEX(以及变体,例如ALTER TABLE ADD PRIMARY KEY)
ALTER TABLE SET TABLESPACE
CLUSTER
COPY FROM, 当目标表已在同一事务中较早创建或截断时
CREATE TABLE AS使得无法直接使用伪类型serial。但由于这只是一个“makro”,您可以手动完成所有操作:创建序列,使用它来生成id值。最后,设置列默认值并使列拥有序列。有关的:
plpgsql 函数包装器是可选的(便于重复使用),您可以在事务中运行普通 SQL:BEGIN; ... COMMIT;
在插入数据之后添加也更快,因为在一块中创建(基础)索引比增量添加值更快。PRIMARY KEY
您的分区存在逻辑错误:
WHERE DLE.ogc_fid >= i * batch_size
AND DLE.ogc_fid <= i * batch_size + batch_size
Run Code Online (Sandbox Code Playgroud)
最后一行将与下一个分区重叠,该行将被重复插入,导致 PK 中的唯一违规。使用<而不是<=可以解决这个问题 - 但我完全删除了分区。
如果你运行这个反复,一个多列索引上digiroad_segmentti (k_elem_id, tyyppi, dyn_tyyppi, region)可能需要支付,这取决于数据的分布。
plpgsql名称,它是一个标识符。STRICT。VOLATILE 是默认值,只是噪音。使用COALESCE提供NULL值的默认值。
您的某些double precision( float8) 列可能会更好, integer因为您大部分时间都numeric (9,0)在旧表中,可以用更便宜的普通integer.
该列region varchar(40)看起来像是规范化的候选者(除非区域大多是唯一的?)创建一个区域表并仅用region_id作主表中的 FK 列。
如果您只更改了shared_buffers、work_mem和effective_cache_sizeconfig 变量,那么您可能仍在使用checkpoint_segments=3.
在这种情况下,您只有三个 WAL 段,因此需要不断地回收它们,每次都强制写入数据文件,这会导致大量 I/O 活动,并且肯定会减慢您的机器的速度。您可以通过查看日志并搜索短语来检查检查点行为checkpoints are occurring too frequently。您还可以通过log_checkpoints=on在 postgresql.conf 中启用来查看他们在做什么
我建议将您更改checkpoint_segments为更大的值,例如 40,然后checkpoint_completion_target更改为 0.9,以尝试消除您所描述的行为。
这些设置在 9.3 的 PostgreSQL 文档的预写日志部分中进一步描述。=)