最近在使用tdsql pg版时发现一个问题,因为查询语句需要进行分页,所以mybatis会自动进行一次count获取总条数来统计页数等参数,但是进行连表后一个简单的count都需要4-5秒的时间,单表count很快,连表一对一也很快,但是遇到一对多的情况就会很慢。
慢sql分析
select count(1)
from policy_list m
left join igis_yw_gu_policy_risk_relatedparty r
on r.policy_no = m.policy_no and r.sub_policy_no = m.sub_policy_no and
r.endor_seq_no = m.endor_seq_no and r.item_no = m.item_no
WHERE (m.start_date <= '2025-07-14'::date and m.end_date >= '2025-07-14'::date);
其中policy_list一条会对应多条igis_yw_gu_policy_risk_relatedparty 因为一个保单至少会有一个或多个被保人。最终执行时间 5s 83ms 执行计划如下:
我们发现,如果是一对一关联,或者是多对一则不会有merge join的过程 脚本如下:
select count(1)
from policy_list m
left join igis_yw_gu_policy_relatedparty r
on r.policy_no = m.policy_no and
r.endor_seq_no = m.endor_seq_no
WHERE (m.start_date <= '2025-07-14'::date and m.end_date >= '2025-07-14'::date);
由于一个保单只会有一个被保人 而policy_list是以标的纬度为主,所以是policy_list中同一个保单多个标的会对应一个igis_yw_gu_policy_relatedparty表里的数据。最终执行时间:359ms 执行计划如下:
同时我们发现如果直接进行分页查询不使用count进行统计的话也是非常快的,脚本如下:
select m.*
from policy_list m
left join igis_yw_gu_policy_risk_relatedparty r
on r.policy_no = m.policy_no and r.sub_policy_no = m.sub_policy_no and
r.endor_seq_no = m.endor_seq_no and r.item_no = m.item_no
WHERE (m.start_date <= '2025-07-14'::date and m.end_date >= '2025-07-14'::date) limit 15;
执行时间:343ms 执行计划如下:
这里发现其实也进行了连表循环 也就是Nested Loop,但是由于数据量非常小只取了15条所以很快,如果不分页两表连接有28万数据循环就会很慢
优化
由于查询所需条件的字段都有索引,所以无法通过加索引来加速查询,百度也搜索了很多关于pgsql统计慢的帖子 但是都没有好的解决方案,要么是加函数要么是查询pg_class模糊统计,但是都不符合我们想要的效果。
最终我们选择了以空间换时间的方案,将我们接口需要查询和返回的字段都放到同一张表中作为宽表,单独查询一张表数据就会非常快,并且我们的业务数据不会频繁的进行变更,所以我们使用到了postgresql中的一个特性 “物化视图”。
“物化视图”实际上也属于视图的一种,但是视图只存储需要执行的sql,而物化视图则是将sql执行的数据存储起来,相当于一张表,后续查询时直接返回物化视图中存储的数据无需再次访问业务表,并且物化视图支持添加索引,需要刷新数据时仅需使用REFRESH MATERIALIZED VIEW
即可刷新整张表数据,无需像常规业务表一样需要使用 truncate table
或者 delete
先删除数据再使用insert
插入数据,维护起来更为简单。
创建物化视图
我们将所有需要放到where中的条件和需要返回的字段都进行连表查询,最终脚本如下:
create materialized view policy_list_ext as
select m.policy_no,
m.endor_seq_no,
m.sub_policy_no,
m.item_no,
m.address_flag,
m.geographical_coordinates_lon,
m.geographical_coordinates_lat,
m.gemo,
m.sum_insured,
m.sum_pricetax_total,
m.risk_code,
k.riskcname as riskName,
m.class_code,
c.classcname,
m.company_code,
m.start_date,
m.end_date,
m.branch_company_code,
m.branch_company_name,
m.suppor_company_code,
m.suppor_company_name,
m.item_province_code,
m.item_province_name,
m.item_city_code,
m.item_city_name,
m.item_district_code,
m.item_district_name,
m.address,
m.o_item_province_code,
m.o_item_province_name,
m.o_item_city_code,
m.o_item_city_name,
m.o_item_district_code,
m.o_item_district_name,
m.o_address,
m.score,
m.state,
m.valid_ind,
m.create_date,
m.update_date,
m.domestic_foreign_flag,
i.item_country_area,
gc.countrycname,
y.insured_name as appliName,
r.insured_name
from policy_list m
left join igis_yw_gu_policy_relatedparty y
on m.policy_no = y.policy_no and m.endor_seq_no = y.endor_seq_no
left join hx_gg_risk k on k.riskcode = m.risk_code
left join hx_gg_risk_class c on c.riskclass = m.class_code
left join igis_yw_gu_policy_risk_relatedparty r
on m.policy_no = r.policy_no and m.endor_seq_no = r.endor_seq_no and m.sub_policy_no = r.sub_policy_no and
m.item_no = r.item_no
left join igis_yw_gu_policy_item i
on m.policy_no = i.policy_no and m.endor_seq_no = i.endor_seq_no and m.sub_policy_no = i.sub_policy_no and
m.item_no = i.item_no
LEFT JOIN hx_gg_country gc
ON i.item_country_area = gc.countrycode
AND gc.validind = '1';
新增索引
物化视图新增索引与常规表新增索引无异,只需使用create index
语法即可,但是要注意,物化视图中没有主键的概念,如果你需要 也可以创建唯一索引。
create index idx_policy_list_ext_rc
on policy_list_ext (risk_code);
create index policy_list_ext_branch_company_code_index
on policy_list_ext (branch_company_code);
create index policy_list_ext_class_code_index
on policy_list_ext (class_code);
create index policy_list_ext_create_date_index
on policy_list_ext (create_date);
create index policy_list_ext_domestic_foreign_flag_index
on policy_list_ext (domestic_foreign_flag);
create index policy_list_ext_end_date_index
on policy_list_ext (end_date);
create index policy_list_ext_geom
on policy_list_ext using gist(gemo);
create index policy_list_ext_score_index
on policy_list_ext (score);
create index policy_list_ext_start_date_index
on policy_list_ext (start_date);
create index policy_list_ext_state_index
on policy_list_ext (state);
create index policy_list_ext_suppor_company_code_index
on policy_list_ext (suppor_company_code);
create index policy_list_ext_update_date_index
on policy_list_ext (update_date);
create index policy_list_ext_valid_ind_index
on policy_list_ext (valid_ind);
create index policy_list_ext_item_country_area_index
on policy_list_ext (item_country_area);
create index policy_list_ext_company_code_index
on policy_list_ext (company_code);
验证
最后我们将新生成的物化视图连接其他表进行查询验证,脚本如下:
SELECT COUNT(*) AS total
FROM policy_list_ext m
LEFT JOIN igis_yw_gu_item_reference ir ON ir.address_flag = m.address_flag AND ir.policy_no = m.policy_no AND
ir.sub_policy_no = m.sub_policy_no AND ir.item_no = m.item_no AND
ir.endor_seq_no = m.endor_seq_no
LEFT JOIN igis_gg_code g
ON g.codetype = 'MARITTIME_ITEM' AND g.codecode = ir.maritime_item AND g.companycode = '03' AND
g.validind = '1'
WHERE (m.start_date <= '2025-07-14'::date AND m.end_date >= '2025-07-14'::date);
最终执行时间:594ms 执行计划如下:
如果业务数据会频繁发生变动,则需要寻找其他解决方案。