【问题标题】:Reduce fetching time in SQL (indexing already done)减少 SQL 中的获取时间(索引已完成)
【发布时间】:2018-01-15 19:30:40
【问题描述】:

我有一个包含几百万行的表,其中的行经常被插入,甚至更频繁地被提取。

行插入的时间并不重要,但提取的时间是因为它服务于一个网站。所以,我created already an index, that helped to fetch much faster

查询非常简单,不包含JOINs。

SELECT 查询会出现问题。一旦用户执行了搜索,相同的SELECT 查询将每隔几秒运行一次以检查新行或更新行。但是,SELECT 查询第一次运行 50 秒,之后运行不到 1 秒,这并不奇怪。

这让我觉得问题不在于SELECT 语句本身,而在于其他问题。

表格是:

CREATE TABLE all_legs (
                carrier TEXT,
                dep_hub TEXT,
                arr_hub TEXT,
                dep_dt TIMESTAMP WITH TIME ZONE,
                arr_dt TIMESTAMP WITH TIME ZONE,
                price_ct INTEGER,
                ... 5 more cols ...,
                PRIMARY KEY (carrier, dep_hub, arr_hub, dep_dt, arr_dt, ...3 other cols...)
                )

索引是:

CREATE INDEX IF NOT EXISTS fetch_index ON all_legs(dep_dt, LEFT(dep_hub::text, 6), LEFT(arr_hub::text, 6));

选择查询:

SELECT * FROM all_legs
                    WHERE dep_dt >= %s
                    AND dep_dt < %s
                    AND (LEFT(dep_hub::text, 6) = %s AND LEFT(arr_hub::text, 6) = %s)

这种情况并不总是发生,因此难以复制。这里有一条来自我本地数据库的EXPLAIN 语句,它的数据比 Heroku 上的少,而且运行速度实际上相当快:

Index Scan using tz_idx on all_legs  (cost=0.41..111184.33 rows=1 width=695) (actual time=128.100..136.690 rows=20 loops=1)
  Index Cond: (("left"(dep_hub, 6) = 'ES-PMI'::text) AND ("left"(arr_hub, 6) = 'ES-MAD'::text))
  Filter: ((dep_dt)::date = '2018-01-19'::date)
  Rows Removed by Filter: 271
Planning time: 3.798 ms
Execution time: 138.525 ms

为什么第一次要慢得多,如何减少第一次查询的运行时间?

【问题讨论】:

  • 我的意思是,我将多次运行同一个查询,因为我正在检查是否有新的或更新的行。所以我第一次运行新查询需要很长时间,以下时间都可以。我更新了问题以澄清它。
  • 谁投票赞成关闭,如果你能解释为什么......
  • 第一次运行查询需要更长的时间是很常见的。最常见的原因是表或索引中必要的数据块没有缓存在内存中(在 Linux 缓存或 pg 共享缓冲区中),必须首先从磁盘读取。当您重复查询时,大部分数据块已经被缓存,因此磁盘 IO 非常小或没有。
  • carrier TEXT, dep_hub TEXT, arr_hub TEXT, 为什么这些PK列都是文本字段?他们的节奏是什么?您能否将它们挤出到单独的表中并通过代理键引用这些表?
  • 为了使第一次查询更快,需要在启动时填充缓存。您可以通过简单地对表运行虚拟查询或使用pgprewarm 扩展来做到这一点

标签: sql postgresql postgresql-performance


【解决方案1】:
  • 挤出单列的示例代码 (dep_hub)
  • 如果您的 {dep_hub,arr_hub} 都指向同一个域,您将不得不稍作改动
  • 您还必须重新定义主键,
  • 并且[也许]在挤出表上添加一些功能索引

    -- [empty] table to contain the "squeezed out" domain
CREATE TABLE dep_hub
    ( id SERIAL NOT NULL PRIMARY KEY
    ,  dep_hub varchar
    , UNIQUE (dep_hub)
    );

   -- This is done in the chained insert/update
-- INSERT INTO dep_hub(dep_hub)
-- SELECT DISTINCT dep_hub
-- FROM all_legs ;


    -- an index may speedup the final update
    -- (the index will be dropped automatically
    -- once the column is dropped)
CREATE INDEX ON all_legs (dep_hub);

    -- The original table needs a "link" to the new table
ALTER TABLE all_legs
    ADD column dep_hub_id INTEGER -- NOT NULL
    REFERENCES dep_hub(id)
    ;

    -- FK constraints are helped a lot by a supportive index.
CREATE INDEX all_legs_dep_hub_fk ON all_legs (dep_hub_id);

    -- Chained query to:
    -- * populate the domain table
    -- * initialize the FK column in the original table
WITH src AS (
    INSERT INTO dep_hub(dep_hub)
    SELECT DISTINCT a.dep_hub
    FROM all_legs a
    RETURNING *
    )
UPDATE all_legs dst
SET  dep_hub_id = src.id
FROM src
WHERE src.dep_hub = dst.dep_hub
    ;

    -- Now that we have the FK pointing to the new table,
    -- we can drop the redundant column.
ALTER TABLE all_legs DROP COLUMN dep_hub;

【讨论】:

  • 谢谢!我有能力删除完整的表并再次创建它。这会简化事情吗? dep_hub 和 arr_hub 将引用同一个域
  • 更好的策略是:就地转换,然后重命名表,并通过select ... from old_table创建新表(省略xxx_hub字段),将PK和FK放在上面,最后放下旧桌子。 (并且:也许用视图替换它,加入 xxx_hub 表两次......)
【解决方案2】:

查询和索引中的LEFT 函数可能会增加不必要的复杂性;您可以使用通配符匹配 LIKE 语句来获得相同的结果。

SELECT * FROM all_legs
WHERE dep_dt >= '2018-01-19'
AND dep_dt < '2018-01-20'
AND dep_hub LIKE 'ES-PMI%'
AND arr_hub LIKE 'ES-MAD%'

在最后两个参数的末尾添加%

这样,您还应该能够通过删除涉及LEFT 函数的索引来加快查询速度,并正常索引列。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-27
    • 1970-01-01
    • 1970-01-01
    • 2013-03-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多