使用多个where子句阻止全表扫描查询

时间:2010-05-15 00:41:24

标签: sql postgresql query-optimization

不久之前,我发布了一条关于在MySQL中优化查询的消息。我已经将数据和查询移植到PostgreSQL,但现在PostgreSQL有同样的问题。 MySQL中的解决方案是强制优化器不使用STRAIGHT_JOIN进行优化。 PostgreSQL没有这样的选择。

更新修订

我已经隔离了修复问题的查询部分(d.month_ref_id = 1):

select
  d.*
from
  daily d
join month_ref m on m.id = d.month_ref_id 
join year_ref y on y.id = m.year_ref_id
where
  m.category_id = '001' and
  d.month_ref_id = 1 

但是,我不能硬编码一个月的1引用。生成全表扫描的查询是:

select
  d.*
from
  daily d
join month_ref m on m.id = d.month_ref_id 
join year_ref y on y.id = m.year_ref_id
where
  m.category_id = '001'

daily.month_ref_id上的索引是:

CREATE INDEX daily_month_ref_idx
  ON climate.daily
  USING btree
  (month_ref_id);

为什么查询执行全表扫描以及可以采取哪些措施来避免它?

谢谢!

3 个答案:

答案 0 :(得分:3)

  1. 尽管它可能没有太大的性能差异,但我会使用Join子句来连接表而不是交叉连接和Where子句。
  2. 您正在调用Where子句中的函数,该函数将导致系统执行表扫描。你使用什么数据库无关紧要,这是真的。
  3. 为什么左派加入城市?你是否知道给定的Id会存在(在这种情况下是10663?如果是这样,你应该使用内连接。
  4. 你或许可以给出编译器关于如何使用括号表示查询的提示(我不确定Postgres是否会尊重它们)。
  5. Select  avg(d.amount) AS amount,  y.year
    From (station s
            Left Join city c -- You want to cross join on city? Why not use an Inner join?
                On c.id = 10663
                    And 6371.009 
                      * SQRT( 
                            POW(RADIANS(c.latitude_decimal - s.latitude_decimal), 2) 
                            + (
                                COS(RADIANS(c.latitude_decimal + s.latitude_decimal) / 2) 
                                * POW(RADIANS(c.longitude_decimal - s.longitude_decimal), 2)
                                )
                            ) <= 50)
        Join station_district sd
            On sd.Id = s.station_district_id
        Join year_ref y
            On y.station_district_id = sd.id
        Join month_ref m
            On m.year_ref_id = y.id
        Join daily d
            On d.month_ref_id = m.id
    Where s.elevation Between 0 And 2000 
        And y.year Between 1980 And 2000
        And m.month = 12
        And m.category_id = '001'
        And d.daily_flag_id <> 'M'
    Group By y.year

    由于您未在结果中使用station,station_district或city表,因此您可以将它们移动到exists语句中:

    Select  avg(d.amount) AS amount,  y.year
    From year_ref y
        Join month_ref m
            On m.year_ref_id = y.id
        Join daily d
            On d.month_ref_id = m.id
    Where y.year Between 1980 And 2000
        And m.month = 12
        And m.category_id = '001'
        And d.daily_flag_id <> 'M'
        And Exist   (
                    Select 1
                    From station s1
                        Join city c1
                            On c1.id = 10663
                    Where 6371.009 
                          * SQRT( 
                                POW(RADIANS(c1.latitude_decimal - s1.latitude_decimal), 2) 
                                + (
                                    COS(RADIANS(c1.latitude_decimal + s1.latitude_decimal) / 2) 
                                    * POW(RADIANS(c1.longitude_decimal - s1.longitude_decimal), 2)
                                    )
                                ) <= 50
                        And S1.station_district_id = y.station_district_id
                    )
    Group By y.year

答案 1 :(得分:1)

我不知道您尝试过的查询的其他变体,但是城市上的JOIN似乎有点奇怪 - 您是否尝试用WHERE子句替换它?此外,各种表之间的关系当前在WHERE子句中 - 这些可能最好实现为INNER JOIN。

免责声明:我不具体了解PostreSQL。

编辑:这是一个描述将JOERE子句更改为影响连接顺序的JOERE子句的链接,并讨论了join_collapse_limit以强制优化器使用您指定的连接顺序。 http://www.postgresql.org/docs/8.2/static/explicit-joins.html

EDIT2:另一种替代方法是嵌套SELECT语句,这也可能迫使优化器以您指定的(反向)嵌套顺序构造查询。

答案 2 :(得分:0)

我认为由于您对查询/连接进行参数化的方式,FTS正在发生。通过这个,我的意思是你有两个参数,一个是与'daily'表中的一个列进行比较,另一个是与'month-ref'表中的一个列进行比较。但是,这两个值都可用于过滤单个表'month-ref'中的行。将该表作为查询中的主表,并按如下方式重写您的查询:

select
  d.*
from month_ref m
join daily d on d.month_ref_id = m.id
join year_ref y on y.id = m.year_ref_id
where
  m.category_id = '001' and
  m.id = 1 

这样,数据库可以根据输入参数值轻松找到month-ref表中的所有必要行,并且可以使用您描述的索引轻松地将每日表中的行放在指定的连接上。 根据month-ref表中可能找到的行数,以及上面引用的列中的任何一个是否包含不同的值,您可能需要在month-ref表上创建索引。