PGSQL的优化建议
避免在WHERE子句中使用in,not in,or
避免对索引字段进行计算操作
避免在索引字段上使用not,<>,!=
避免在索引列上使用IS NULL和IS NOT NULL
避免在索引列上出现数据类型转换
避免在索引字段上使用函数
避免建立索引的列中使用空值
????????????????????????????????
(1)由应用程序建立到PostgreSQL服务器的连接。应用服务器发送查询请求至PostgresSQL服务器并从PostgreSQL服务器接收查询结果。
(2)查询引擎将查询语句依据所定义的词法规则和语法规则构建原始查询语法树。
(3)查询分析阶段,查询引擎将原始语法树转换为查询树。
(4)查询改写阶段,查询引擎将查询树依据系统中预先定义的规则对查询树进行转换。
(5)优化器(Planner)接收改写后的查询树并依据该查询树完成查询逻辑优化。
(6)优化器(Planner)继续对已完成逻辑优化的查询树进行查询物理优化并求解最优查询访问路径。
(7)执行器(Executor)依据最优查询访问路径进行表扫描操作并将获取的数据按一定格式创建返回值,然后将结果返回应用程序。
磁盘扫描的三种方式:seq scan 、 bitmap index scan 、 index scan
seq scan——全表扫描,在数据量少的情况下,全表扫描优于索引扫描,因为index scan 至少要发生两次I/O,一次是 读取索引块, 一次是读取数据块,再进行条件过滤,数据量在3万以下索引体现不出明显的优势。
index scan——索引扫描,在数据量大的情况下,良好的索引条件,有助于减少不必要的I/O,缩小进一步条件过滤的范围,以及进一步JOIN的循环次数
Tips: 1. 过滤条件中包含多个单索引,一般只使用其中一个,其余作为普通过滤条件(具体由优化器选择一个最优的)
2. 单索引随着时间的推移,数据分布容易发生倾斜(例:主订单表里 inn_id, order_status 字段都有建有单索引,条件inn_id = 3306会索引出19530条记录,条件order_status = 1会索引出几百万条记录)
3. 当一个查询条件中最优的单索引也会索引出几万条记录的时候,就需要考虑建立多字段的联合索引
4. 先扫描索引块,一次只读一条索引项,一个 PAGE面有可能被多次访问
bitmap index scan——索引扫描,使用bitmap index scan是因为数据在磁盘中的位置过于分散,使用index scan不划算
Tips: 1. bitmap index scan与bitmap heap scan总是成对出现
2.输出的是索引条目,交给bitmap heap scan,bitmap heap scan对索引条目进行一系列的处理,组成结果
3. 一次性将满足条件的索引项全部取出,然后交给bitmap heap scan节点
例子:
select p.*
from tomato_order_person p
where p.phone = ''
----------------------------------
Seq Scan,Actual-Rows: 1477342
select p.*
from tomato_order_person p
where p.phone = '1'
----------------------------------
Index Scan,Actual-Rows: 23
select p.*
from tomato_order_person p
where p.id > 1928564
----------------------------------
Bitmap Index Scan,Actual-Rows: 1178134
当索引出的数据量超过整个表的40%左右时,不会使用索引, select p.* from tomato_order_person p where p.id > 0 不管数据量为多少,永远只会使用Seq Scan
select m.*
from tomato_inn_main_order m
where m.id = 5000 or m.id = 5001
select m.*
from tomato_inn_main_order m
where m.id IN (5000, 5001, 5002, 5003)
select m.*
from tomato_inn_main_order m
where m.id = 5000 or m.contact_user = '111'
select m.*
from tomato_inn_main_order m
where m.id = 5000 or m.inn_id = 2069
select m.*
from tomato_inn_main_order m
where m.inn_id is NULL
select m.*
from tomato_inn_main_order m
where m.inn_id != 2069
select m.*
from tomato_inn_main_order m
where m.inn_id + 1 = 2069
select m.*
from tomato_inn_main_order m
where m.inn_id::varchar = '2069'
select m.*
from tomato_inn_main_order m
inner join tomato_inn_sub_order_price p on p.main_id = m.id
----------------------------------
Hash Join,Actual-Rows: 3800910;tomato_inn_main_order 做为Hash表 , tomato_inn_sub_order_price 作为主表
因为tomato_inn_sub_order_price 的数据量远大于tomato_inn_main_order,这是PostgreSQL优化后的结果
select m.*
from tomato_inn_main_order m
inner join tomato_inn_sub_order_price p on p.main_id = m.id
where m.inn_id = 3307
----------------------------------
Nested Loop , Actual-Rows: 25851, Actual-loops: 1
Index Scan tomato_inn_main_order as m , Actual-Rows: 8664 , Actual-loops: 1 , Index-Cond: (inn_id = 3307)
Index Scan tomato_inn_sub_order_price as p , Actual-Rows: 3, Actual-loops: 8664 , Index-Cond: (main_id = m.id)
由于tomato_inn_main_order通过inn_id字段的单索引大大减少了所需处理的数据量,所以以tomato_inn_main_order的索引结果作为主表,
循环遍历tomato_inn_sub_order_price表(main_id字段有索引,因此不是全表扫描,而是8664次Index Scan)
Nested Loop——嵌套循环,适用于主表(驱动表)记录较小(< 10000为佳)时
传说中PostgreSQL除了Hash Join、Nested Loop之外,还有一种级联方式——Sort Merge join(本人从来没有写出过,执行计划里级联方式是Sort Merge join)