加入表中列的查询排序缓慢

dvd*_*dvd 9 sql postgresql performance collation sql-order-by

在查询中引入ORDER BY子句会增加总时间,因为db必须执行额外的工作才能对结果集进行排序:

  • 将生成的元组复制到一些临时内存中
  • 排序它们(希望在内存中,否则使用磁盘)
  • 将结果传递给客户端

我想念的是为什么只从连接表中添加一列产生如此不同的性能.

查询1

EXPLAIN ANALYZE
SELECT p.*
FROM product_product p
JOIN django_site d ON (p.site_id = d.id)
WHERE (p.active = true  AND p.site_id = 1 )
ORDER BY d.domain, p.ordering, p.name
Run Code Online (Sandbox Code Playgroud)

查询计划

Sort  (cost=3909.83..3952.21 rows=16954 width=1086) (actual time=1120.618..1143.922 rows=16946 loops=1)
   Sort Key: django_site.domain, product_product.ordering, product_product.name
   Sort Method:  quicksort  Memory: 25517kB
   ->  Nested Loop  (cost=0.00..2718.86 rows=16954 width=1086) (actual time=0.053..87.396 rows=16946 loops=1)
         ->  Seq Scan on django_site  (cost=0.00..1.01 rows=1 width=24) (actual time=0.010..0.012 rows=1 loops=1)
               Filter: (id = 1)
         ->  Seq Scan on product_product  (cost=0.00..2548.31 rows=16954 width=1066) (actual time=0.036..44.138 rows=16946 loops=1)
               Filter: (product_product.active AND (product_product.site_id = 1))
 Total runtime: 1182.515 ms
Run Code Online (Sandbox Code Playgroud)

查询2

与上面相同,但没有排序 django_site.domain

查询计划

 Sort  (cost=3909.83..3952.21 rows=16954 width=1066) (actual time=257.094..278.905 rows=16946 loops=1)
   Sort Key: product_product.ordering, product_product.name
   Sort Method:  quicksort  Memory: 25161kB
   ->  Nested Loop  (cost=0.00..2718.86 rows=16954 width=1066) (actual time=0.075..86.120 rows=16946 loops=1)
         ->  Seq Scan on django_site  (cost=0.00..1.01 rows=1 width=4) (actual time=0.015..0.017 rows=1 loops=1)
               Filter: (id = 1)
         ->  Seq Scan on product_product  (cost=0.00..2548.31 rows=16954 width=1066) (actual time=0.052..44.024 rows=16946 loops=1)
               Filter: (product_product.active AND (product_product.site_id = 1))
 Total runtime: 305.392 ms
Run Code Online (Sandbox Code Playgroud)

这个问题可能有关系.

编辑:添加了更多细节

           Table "public.product_product"
 Column       |          Type          |  
 -------------+------------------------+---------
 id                | integer                | not null default nextval('product_product_id_seq'::regclass)
 site_id           | integer                | not null
 name              | character varying(255) | not null
 slug              | character varying(255) | not null
 sku               | character varying(255) | 
 ordering          | integer                | not null
 [snip some columns ]

 Indexes:
    "product_product_pkey" PRIMARY KEY, btree (id)
    "product_product_site_id_key" UNIQUE, btree (site_id, sku)
    "product_product_site_id_key1" UNIQUE, btree (site_id, slug)
    "product_product_site_id" btree (site_id)
    "product_product_slug" btree (slug)
    "product_product_slug_like" btree (slug varchar_pattern_ops)


                  Table "public.django_site"
 Column |          Type          | 
--------+------------------------+----------
 id     | integer                | not null default nextval('django_site_id_seq'::regclass)
 domain | character varying(100) | not null
 name   | character varying(50)  | not null
Indexes:
    "django_site_pkey" PRIMARY KEY, btree (id)
Run Code Online (Sandbox Code Playgroud)

Postgres版本是8.4

一些表统计:

# select count(*) from django_site;
 count 
-------
     1

# select count(*) from product_product;
 count 
-------
 17540

# select active, count(*) from product_product group by active;
 active | count 
--------+-------
 f      |   591
 t      | 16949

# select site_id, count(*) from product_product group by site_id;
 site_id | count 
---------+-------
       1 | 17540
Run Code Online (Sandbox Code Playgroud)

Erw*_*ter 7

测试用例

PostgreSQL 9.1.使用有限的资源测试数据库,但这种小案例的方式足够了.整理的区域设置将是相关的:

SHOW LC_COLLATE;

 de_AT.UTF-8
Run Code Online (Sandbox Code Playgroud)

步骤1)重建原始测试环境

-- DROP TABLE x;
CREATE SCHEMA x;  -- test schema

-- DROP TABLE x.django_site;
CREATE TABLE x.django_site (
id serial primary key
,domain character varying(100) not null
,int_col int not null
);
INSERT INTO x.django_site values (1,'www.testsite.com/foodir/', 3);

-- DROP TABLE x.product;
CREATE TABLE x.product (
 id serial primary key
,site_id integer not null
,name character varying(255) not null
,slug character varying(255) not null
,sku character varying(255) 
,ordering integer not null
,active boolean not null
);

INSERT INTO x.product (site_id, name, slug, sku, ordering, active)
SELECT 1
    ,repeat(chr((random() * 255)::int + 32), (random()*255)::int)
    ,repeat(chr((random() * 255)::int + 32), (random()*255)::int)
    ,repeat(chr((random() * 255)::int + 32), (random()*255)::int)
    ,i -- ordering in sequence
    ,NOT (random()* 0.5174346569119122)::int::bool
FROM generate_series(1, 17540) AS x(i);
-- SELECT ((591::float8 / 17540)* 0.5) / (1 - (591::float8 / 17540))
-- = 0.5174346569119122

CREATE INDEX product_site_id on x.product(site_id);
Run Code Online (Sandbox Code Playgroud)

步骤2)分析

    ANALYZE x.product;
    ANALYZE x.django_site;
Run Code Online (Sandbox Code Playgroud)

步骤3)随机重新排序()

-- DROP TABLE x.p;
CREATE TABLE x.p AS
SELECT *
FROM   x.product
ORDER  BY random();

ANALYZE x.p;
Run Code Online (Sandbox Code Playgroud)

结果

EXPLAIN ANALYZE
    SELECT p.*
    FROM   x.p
    JOIN   x.django_site d ON (p.site_id = d.id)
    WHERE  p.active
    AND    p.site_id = 1
--    ORDER  BY d.domain, p.ordering, p.name
--    ORDER  BY p.ordering, p.name
--    ORDER  BY d.id, p.ordering, p.name
--    ORDER  BY d.int_col, p.ordering, p.name
--    ORDER  BY p.name COLLATE "C"
--    ORDER  BY d.domain COLLATE "C", p.ordering, p.name -- dvd's final solution
Run Code Online (Sandbox Code Playgroud)

1)预分析( - >位图索引扫描)
2)分析后( - > seq扫描)
3)通过random(),ANALYZE重新排序

ORDER  BY d.domain, p.ordering, p.name
Run Code Online (Sandbox Code Playgroud)

1)总运行时间:1253.543 ms
2)总运行时间:1250.351 ms
3)总运行时间:1283.111 ms

ORDER  BY p.ordering, p.name
Run Code Online (Sandbox Code Playgroud)

1)总运行时间:177.266 ms
2)总运行时间:174.556 ms
3)总运行时间:177.797 ms

ORDER  BY d.id, p.ordering, p.name
Run Code Online (Sandbox Code Playgroud)

1)总运行时间:176.628 ms
2)总运行时间:176.811 ms
3)总运行时间:178.150 ms
计划器明显因素d.id取决于功能.

ORDER  BY d.int_col, p.ordering, p.name -- integer column in other table

1)总运行时间:242.218毫秒 - !!
2)总运行时间:245.234 ms
3)总运行时间:254.581 ms
规划器显然错过了d.int_col(NOT NULL)与功能相关.但是按整数列排序很便宜.

ORDER  BY p.name -- varchar(255) in same table

1)总运行时间:2259.171 ms - !!
2)总运行时间:2257.650 ms
3)总运行时间:2258.282 ms
按(长)varchartext列排序很昂贵......

ORDER  BY p.name COLLATE "C"

1)总运行时间:327.516 ms - !!
2)总运行时间:325.103 ms
3)总运行时间:327.206 ms
...但如果没有语言环境,则不会那么昂贵.

将语言环境排除在外,按varchar列排序并不是很快,而是几乎一样快.Locale "C"实际上是"没有语言环境,只是按字节值排序".我引用手册:

如果您希望系统的行为就像它没有语言环境支持一样,请使用特殊的语言环境名称C或等效的POSIX.


把它们放在一起,@ dvd选择:

ORDER  BY d.domain COLLATE "C", p.ordering, p.name
Run Code Online (Sandbox Code Playgroud)

... 3)总运行时间:275.854 ms
应该这样做.


Erw*_*ter 2

EXPLAIN ANALYZE 的输出在排序操作之前都是相同的,因此排序会产生差异。

在这两个查询中,您返回 的所有行product_product,但在第一种情况下,您按 的列排序django_site,因此django_site.domain必须另外检索,这会产生额外费用。但无法解释其中的巨大差异。

很有可能中 行的物理顺序product_product已经根据 列ordering,这使得情况 2 中的排序非常便宜,而情况 1 中的排序则昂贵。


在“添加更多详细信息”之后:按列排序比按列排序
昂贵得多。除了整数要小得多之外,排序规则支持也会减慢您的速度。要进行验证,请尝试使用 订购。请阅读手册中有关排序规则支持的更多信息。如果您运行的PostgreSQL 9.1。我现在看到,您有 PostgreSQL 8.4。character varying(100)integerCOLLATE "C"

显然,查询输出中的所有行都具有与django_site.domain您筛选时相同的值p.site_id = 1。如果查询规划器更聪明,它可能会跳过第一列开始排序。

您运行 PostgreSQL 8.4。9.1 的查询规划器变得更加智能。升级可能会改变这种情况,但我不能肯定。


为了验证我关于物理排序的理论,您可以尝试制作大表的副本,其中以随机顺序插入行,然后再次运行查询。像这样:

CREATE TABLE p AS
SELECT *
FROM   public.product_product
ORDER  BY random();
Run Code Online (Sandbox Code Playgroud)

进而:

EXPLAIN ANALYZE
SELECT p.*
FROM   p
JOIN   django_site d ON (p.site_id = d.id)
WHERE  p.active
AND    p.site_id = 1
ORDER  BY d.domain, p.ordering, p.name;
Run Code Online (Sandbox Code Playgroud)

有什么区别吗?--> 显然这并不能解释它......


好的,为了测试是否varchar(100)有影响,我重新创建了您的场景。请参阅带有详细测试用例和基准测试的单独答案。这个答案已经超载了。

总结一下:
事实证明,我的其他解释是合适的。速度减慢的主要原因显然是varchar(100)根据locale ( LC_COLLATE)按列排序。

我添加了一些解释和测试用例的链接。结果不言自明。