使用LIMIT,索引扫描无法正常工作

时间:2013-08-02 08:48:48

标签: sql postgresql

我在PostgreSQL 9.1中进行了简单的实验。我创建了test表,如下所示:

CREATE TABLE test
(
  id serial NOT NULL,
  CONSTRAINT id PRIMARY KEY (id )
)
CREATE INDEX id_idx
  ON test
  USING btree
  (id );

然后我添加一些数据:

insert into test values(DEFAULT);
insert into test values(DEFAULT);
insert into test values(DEFAULT);
...many times :)

现在我有test表,其中有10'000行。我的第一个实验是按id排序:

explain select * from test where id = 50;

Index Scan using id_idx on test  (cost=0.00..8.27 rows=1 width=4)
  Index Cond: (id = 50)

好的,这里没什么奇怪的。让我们按值范围进行查询:

explain select * from test where id >= 50;

Seq Scan on test  (cost=0.00..170.00 rows=9951 width=4)
  Filter: (id >= 50)

我们通过顺序扫描获得了9951行,但是如果我只想获得第一行:

explain select * from test where id >= 50 limit 1;

Limit  (cost=0.00..0.02 rows=1 width=4)
  ->  Seq Scan on test  (cost=0.00..170.00 rows=9951 width=4)
        Filter: (id >= 50)

我希望看到使用rows=1进行索引扫描,但我会再次进行顺序扫描(扫描后有限制)。有什么办法可以用SQL查询实现这种行为吗?

注意:我有类似的MongoDB查询:

> db.test.find({'dt':{$gte:ISODate("2013-07-20T00:00:00.00Z")}}).count()
10000
> db.test.find({'dt':{$gte:ISODate("2013-07-20T00:00:00.00Z")}}).limit(1).explain()
{
    "cursor" : "BtreeCursor dt_1",
    "isMultiKey" : false,
    "n" : 1,
    "nscannedObjects" : 1,
    "nscanned" : 1,
    "nscannedObjectsAllPlans" : 1,
    "nscannedAllPlans" : 1,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 13,
    "indexBounds" : {
        "dt" : [
            [
                ISODate("2013-07-20T00:00:00Z"),
                ISODate("0NaN-NaN-NaNTNaN:NaN:NaNZ")
            ]
        ]
    },
    "server" : "******:27017"
}

在这种情况下,MongoDB使用索引扫描仅扫描了1个文档,这很棒。

1 个答案:

答案 0 :(得分:5)

首先要做的事情 - 额外的索引是不必要的

NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "id" for table "test"

第二:总是运行解释分析而不是简单的解释,你得到真正的价值观。在这种情况下,表可能很热(已经在内存中),所以SEQ SCAN真的非常快!

第三:您没有指定任何排序,因此Postgresql可以免费为您提供任意随机排序,因此任意一个随机ID> = 50 。这很可能不是你想要的。 PostgreSQL在一个页面上存储了许多元组;在这种情况下知道数据的分布很可能在第一页上你有一个id> = 50的元组,所以在这种情况下,SEQ SCAN方法是正确的,也是最快的。

第四:在进行任何实际分析之前运行VACUUM ANALYZE

VACUUM ANALYZE使用EXPLAIN ANALYZE后,我得到:

# EXPLAIN ANALYZE SELECT * FROM test WHERE id >= 50 ORDER BY id LIMIT 1;
                                                     QUERY PLAN                                                      
---------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.00..0.03 rows=1 width=4) (actual time=0.028..0.029 rows=1 loops=1)
   ->  Index Scan using id on test  (cost=0.00..416.47 rows=12241 width=4) (actual time=0.027..0.027 rows=1 loops=1)
         Index Cond: (id >= 50)
 Total runtime: 0.059 ms
(4 rows)