postgres如何决定是使用索引扫描还是seq扫描?

时间:2017-04-15 13:00:10

标签: postgresql sql-execution-plan explain

explain analyze显示postgres会对我的查询使用索引扫描来获取行并按日期执行过滤(即2017-04-14 05:27:51.039):

explain analyze select * from tbl t where updated > '2017-04-14 05:27:51.039';
                                                          QUERY PLAN                                                          
 -----------------------------------------------------------------------------------------------------------------------------
  Index Scan using updated on tbl t  (cost=0.43..7317.12 rows=10418 width=93) (actual time=0.011..0.515 rows=1179 loops=1)
    Index Cond: (updated > '2017-04-14 05:27:51.039'::timestamp without time zone)
  Planning time: 0.102 ms
  Execution time: 0.720 ms

然而运行相同的查询,但使用不同的日期过滤器'2016-04-14 05:27:51.039'显示postgres将使用seq扫描运行查询:

explain analyze select * from tbl t where updated > '2016-04-14 05:27:51.039';
                                                      QUERY PLAN                                                       
-----------------------------------------------------------------------------------------------------------------------
 Seq Scan on tbl t  (cost=0.00..176103.94 rows=5936959 width=93) (actual time=0.008..2005.455 rows=5871963 loops=1)
   Filter: (updated > '2016-04-14 05:27:51.039'::timestamp without time zone)
   Rows Removed by Filter: 947
 Planning time: 0.100 ms
 Execution time: 2910.086 ms

postgres如何决定使用什么,特别是在按日期执行过滤时?

2 个答案:

答案 0 :(得分:2)

Postgres查询计划器的决策基于成本估算和表统计。表统计信息由public void Nieuweontvangst (DateTime datumIN, string leverancier, string levlotnr, double hoeveelheidIN, string eenheidIN, string grondstofIN, string stockplaatsIN, int lotnrlevID, int lotnrINID) { var manager = new LotnummersDBManager(); using (var conLotnummers = manager.Getconnection()) { conLotnummers.Open(); using (var traNieuweOntvangst = conLotnummers.BeginTransaction(IsolationLevel.ReadCommitted)) {//begin traNieuweOntvangst //first transaction: the output parameter @newinsertedlevID //just needs to be used in the second transaction, but not be displayed //------------------ using (var comlevlotnrs = conLotnummers.CreateCommand()) { comlevlotnrs.Transaction = traNieuweOntvangst; comlevlotnrs.CommandType = CommandType.StoredProcedure; comlevlotnrs.CommandText = "Levlotnr"; //name of first stored procedure var parlotleverancier = comlevlotnrs.CreateParameter(); parlotleverancier.ParameterName = "@Lotleverancier"; parlotleverancier.Value = levlotnr; comlevlotnrs.Parameters.Add(parlotleverancier); var parleverancier = comlevlotnrs.CreateParameter(); parleverancier.ParameterName = "@leverancier"; parleverancier.Value = leverancier; comlevlotnrs.Parameters.Add(parleverancier); var parlotlevID = comlevlotnrs.CreateParameter(); parlotlevID.ParameterName = "@newinsertedlevID"; parlotlevID.DbType = DbType.Int32; parlotlevID.Direction = ParameterDirection.Output; comlevlotnrs.Parameters.Add(parlotlevID); } using (var comLotnrsIN = conLotnummers.CreateCommand()) {// second transaction= output parameter @newinsertedlevID // should be used here where now stands @lotnrlevIN. // THIS IS WHERE I STRUGGLE // also here I get an output parameter // @newinsertedLotnrINID only to be used in the 3rd // transaction, not to be displayed. comLotnrsIN.Transaction = traNieuweOntvangst; comLotnrsIN.CommandType = CommandType.StoredProcedure; comLotnrsIN.CommandText = "LotnrIN"; var pardatumIN = comLotnrsIN.CreateParameter(); pardatumIN.ParameterName = "@datumIN"; pardatumIN.Value = datumIN; comLotnrsIN.Parameters.Add(pardatumIN); var parhoeveelIN = comLotnrsIN.CreateParameter(); parhoeveelIN.ParameterName = "@hoeveelIN"; parhoeveelIN.Value = hoeveelheidIN; comLotnrsIN.Parameters.Add(parhoeveelIN); var pargrondstofIN = comLotnrsIN.CreateParameter(); pargrondstofIN.ParameterName = "@grondstofIN"; pargrondstofIN.Value = grondstofIN; comLotnrsIN.Parameters.Add(pargrondstofIN); var parlotnrlevIN = comLotnrsIN.CreateParameter(); parlotnrlevIN.ParameterName = "@lotnrlevIN"; parlotnrlevIN.Value = lotnrlevID; comLotnrsIN.Parameters.Add(parlotnrlevIN); var parLotIN = comLotnrsIN.CreateParameter(); parLotIN.ParameterName = "@newinsertedLotnrINID"; parLotIN.DbType = DbType.Int32; parLotIN.Direction = ParameterDirection.Output; comLotnrsIN.Parameters.Add(parLotIN); } using (var comStockeren = conLotnummers.CreateCommand()) { //Third transaction // I need to use the output parameter from 2nd transaction // @newinsertedLotnrINID where you see now @lotnrINID. //THIS IS THE SAME STRUGGLE AS 2ND TRANSACTION comStockeren.Transaction = traNieuweOntvangst; comStockeren.CommandType = CommandType.StoredProcedure; comStockeren.CommandText = "StockIN"; var parlotIN = comStockeren.CreateParameter(); parlotIN.ParameterName = "@lotnrINID"; parlotIN.Value = lotnrINID; var paromschrStockIN = comStockeren.CreateParameter(); paromschrStockIN.ParameterName = "@omschrstockIN"; paromschrStockIN.Value = stockplaatsIN; comStockeren.Parameters.Add(paromschrStockIN); } traNieuweOntvangst.Commit(); } } } 收集,并由其他一些实用程序命令机会性地收集。当autovacuum打开时(默认情况下),这一切都会自动发生。

The manual:

  

由于,大多数查询只检索表中的一小部分行   限制要检查的行的ANALYZE子句。这样的策划者   需要估计WHERE条款的选择性   是,WHERE中与每个条件匹配的行的比例   条款。用于此任务的信息存储在   WHERE系统目录。 pg_statistic中的条目已更新   pg_statisticANALYZE命令,并且始终是近似值   即使是刚刚更新的。

有一个行计数(在VACUUM ANALYZE中),一个最常见的值列表等。

Postgres希望找到的行越多,它就越有可能转换为顺序扫描,这对于检索表的大部分来说更便宜。

一般来说,它的索引扫描 - >位图索引扫描 - >顺序扫描,预计将检索的行数越多。

对于您的特定示例,重要统计信息为pg_class,这使Postgres大致了解有多少行的值大于给定值。对于人眼来说,有更方便的视图histogram_bounds

pg_stats

有一个dedicated chapter explaining row estimation in the manual.

答案 1 :(得分:1)

显然,优化查询很棘手。这个答案并不是为了深入了解Postgres优化器的细节。相反,它旨在为您提供有关如何决定使用索引的背景信息。

您的第一个查询估计会返回10,418行。使用索引时,会发生以下操作:

  • 引擎使用索引查找符合条件的第一个值。
  • 然后引擎循环显示值,当条件不再为真时结束。
  • 对于索引中的每个值,引擎然后在数据页面上查找数据。

换句话说,使用索引时会有一些开销 - 初始化索引然后单独查找每个数据页。

当引擎进行全表扫描时:

  • 从第一页上的第一条记录开始
  • 比较并接受或拒绝记录
  • 按顺序继续浏览所有数据页

没有额外的开销。此外,发动机可以预先加载"处理当前页面时要扫描的下一页。 I / O和处理的重叠是一个巨大的胜利。

我想说的是,在这两者之间取得平衡可能会非常棘手。在10,418和5,936,959之间,Postgres决定索引开销(以及随机获取页面)的成本不仅仅是扫描整个表格。