francs=> /dt+ test_count List of relations Schema | Name | Type | Owner | Size | Description --------+-------------------+-------+--------+-------+------------- francs | test_count | table | francs | 56 MB | (1 row)
francs=> /dt+ test List of relations Schema | Name | Type | Owner | Size | Description --------+-------------+-------+--------+---------+------------- francs | test | table | francs | 8920 kB | (1 row)
francs=> select count(*) from test_count; count -------- 500455 (1 row)
francs=> select count(*) from test; count -------- 101118 (1 row)
备注:两个表都不大。
优化前的SQL和执行计划
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
francs=> explain analyze SELECT vac.skyid, vac.username, va.answer FROM test_count vac, test va WHERE vac.questid = '20110224123544' and vac.answerid = va.id ::character varying and vac.flag = 0 ORDER BY vac.create_time DESC LIMIT 3; QUERY PLAN
-------------------------------------------------------------------------------------------------------------- Limit (cost=0.00..494.32rows=3width=69) (actual time=81.274..420.706rows=3loops=1) -> Nested Loop (cost=0.00..85352.00rows=518width=69) (actual time=81.273..420.702rows=3loops=1) Join Filter: ((vac.answerid)::text = ((va.id)::character varying)::text) -> Index Scan Backward using idx_test_count_ctime on test_count vac (cost=0.00..20208.38rows=24width=56) (actual time=80.522..263.236rows=3loops=1) Filter: (((questid)::text = '20110224123544'::text) AND (flag = 0)) -> Materialize (cost=0.00..3220.77rows=101118width=22) (actual time=0.026..31.220rows=67673loops=3) -> Seq Scan on test va (cost=0.00..2122.18rows=101118width=22) (actual time=0.009..28.288rows=101118loops=1) Total runtime: 537.650 ms
francs=> selectcount(distinct answerid),count(*) from test_count; count | count -------+-------- 32847 | 500455 (1 row) francs=> createindex idx_test_count_answeid on test_count using btree ( answerid ); CREATEINDEX
francs=> analyze test_count; ANALYZE
备注:answerid 字段 选择性不错,可以创建索引。
查看PLAN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
francs=> explain analyze SELECT vac.skyid, vac.username, va.answer FROM test_count vac, test va WHERE vac.questid = '20110224123544' and vac.answerid = va.id ::character varying and vac.flag = 0 ORDER BY vac.create_time DESC LIMIT 3; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------- Limit (cost=0.00..489.59rows=3width=69) (actual time=81.897..422.056rows=3loops=1) -> Nested Loop (cost=0.00..85352.00rows=523width=69) (actual time=81.895..422.052rows=3loops=1) Join Filter: ((vac.answerid)::text = ((va.id)::character varying)::text) -> Index Scan Backward using idx_test_count_ctime on test_count vac (cost=0.00..20208.38rows=24width=56) (actual time=81.131..264.123rows=3loops=1) Filter: (((questid)::text = '20110224123544'::text) AND (flag = 0)) -> Materialize (cost=0.00..3220.77rows=101118width=22) (actual time=0.027..31.352rows=67673loops=3) -> Seq Scan on test va (cost=0.00..2122.18rows=101118width=22) (actual time=0.009..28.355rows=101118loops=1) Total runtime: 529.655 ms
备注:还是走了表 test_count 上的时间索引 idx_test_count_ctime,并且 test 走了全表扫描由于表关联字段类型不一致,va.id 为 integer ,vac.answerid 为 character varying,这时是用不到表 test 的主键的,这里需要在表 test 上创建函数索引。
创建函数索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
francs=> create index idx_test_id on test using btree (( id::character varying)); CREATE INDEX francs=> /d test Table "francs.test" Column | Type | Modifiers -------------+-----------------------------+--------------- answer | character varying | create_time | timestamp without time zone | default now() questid | character varying(20) | id | integer | notnull answercount | bigint | default 0 Indexes: "pk_ans_id" PRIMARY KEY, btree (id) "idx_test_ctime" btree (create_time) "idx_test_id" btree ((id::character varying))
创建函数索引后再次查看PALN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
francs=> explain analyze SELECT vac.skyid, vac.username, va.answer FROM test_count vac, test va WHERE vac.questid = '20110224123544' and vac.answerid = va.id ::character varying and vac.flag = 0 ORDER BY vac.create_time DESC LIMIT 3; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------- Limit (cost=0.00..125.03rows=3width=69) (actual time=87.517..269.151rows=3loops=1) -> Nested Loop (cost=0.00..21795.61rows=523width=69) (actual time=87.514..269.146rows=3loops=1) -> Index Scan Backward using idx_test_count_ctime on test_count vac (cost=0.00..20208.38rows=24width=56) (actual time=87.478..269.049rows=3loops=1) Filter: (((questid)::text = '20110224123544'::text) AND (flag = 0)) -> Index Scan using idx_test_id on test va (cost=0.00..57.28rows=506width=22) (actual time=0.019..0.019rows=1loops=3) Index Cond: (((id)::character varying)::text = (vac.answerid)::text) Total runtime: 269.228 ms (7 rows)
备注:这时表 test 已经走索引了,此时语句已优化到了只需要 200多 毫秒,看看是否还有优化空间。根据上面 PLAN ,时间主要花在 idx_test_count_ctime 扫描上,花费了182 ( 269 -87 ) 毫秒 。
francs=> explain analyze SELECT vac.skyid, vac.username, va.answer FROM test_count vac, test va WHERE vac.questid = '20110224123544' and vac.answerid = va.id ::character varying and vac.flag = 0 ORDER BY vac.create_time DESC LIMIT 3; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------- Limit (cost=0.00..9.36rows=3width=69) (actual time=0.038..0.057rows=3loops=1) -> Nested Loop (cost=0.00..1609.55rows=516width=69) (actual time=0.037..0.056rows=3loops=1) -> Index Scan using idx_test_count_questid_ctime on test_count vac (cost=0.00..22.32rows=24width=56) (actual time=0.028..0.033rows=3loops=1) Index Cond: ((questid)::text = '20110224123544'::text) Filter: (flag = 0) -> Index Scan using idx_test_id on test va (cost=0.00..57.28rows=506width=22) (actual time=0.006..0.006rows=1loops=3) Index Cond: (((id)::character varying)::text = (vac.answerid)::text) Total runtime: 0.104 ms (8 rows) 备注,现在仅花费了 0.104 ms,走了新建的索引 idx_test_count_questid_ctime,比最初的 500 多毫秒,优化了近 5000 倍!
总结
上面仅是在测试库上做的测试,优化后的 SQL从原来的 537.650 ms 优化到 0.104 ms ,优化了近 5000倍, 但上生产前还需要和开发人员沟通,看看索引是否会影响到其它 SQL。