Медленное упорядочение запросов по столбцу в объединенной таблице - PullRequest
9 голосов
/ 27 марта 2012

Введение в запрос предложения ORDER BY увеличивает общее время из-за дополнительной работы, выполняемой БД для сортировки результирующего набора:

  • копирует результирующие кортежи в некоторую временную память
  • сортировка (возможно, в памяти, в противном случае с использованием диска)
  • потоковая передача результата клиенту

Что мне не хватает, так это просто добавление столбца изобъединенная таблица дает совершенно другую производительность.

Query1

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

План запроса

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

Запрос 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

Этот вопрос может быть связан.

Редактировать: Подробнее добавлено

           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)

Версия 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

Ответы [ 3 ]

7 голосов
/ 28 марта 2012

Тестовый кейс

PostgreSQL 9.1. Протестируйте базу данных с ограниченными ресурсами, но достаточно для этого небольшого случая. Язык сопоставления будет иметь отношение:

SHOW LC_COLLATE;

 de_AT.UTF-8

Шаг 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);

Шаг 2) АНАЛИЗ

    ANALYZE x.product;
    ANALYZE x.django_site;

Шаг 3) Изменить порядок в случайном порядке ()

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

ANALYZE x.p;

Результаты

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

1) Предварительный анализ (-> сканирование растрового индекса)
2) Post ANALYZE (-> seq scan)
3) Переупорядочить случайным образом (), АНАЛИЗ

ORDER  BY d.domain, p.ordering, p.name

1) Общее время выполнения: 1253,543 мс
2) Общее время работы: 1250,351 мс
3) Общее время работы: 1283,111 мс

ORDER  BY p.ordering, p.name

1) Общее время выполнения: 177,266 мс
2) Общее время работы: 174,556 мс
3) Общее время работы: 177,797 мс

ORDER  BY d.id, p.ordering, p.name

1) Общее время выполнения: 176,628 мс
2) Общее время работы: 176,811 мс
3) Общее время работы: 178,150 мс
Планировщик, очевидно, учитывает, что d.id является функционально зависимым.

ORDER  BY <b>d.int_col</b>, p.ordering, p.name -- integer column in other table

1) Общее время работы: 242,218 мс - !!
2) Общее время работы: 245,234 мс
3) Общее время работы: 254,581 мс
Планировщик явно упускает из виду, что d.int_col (NOT NULL) так же функционально зависит. Но сортировка по целому столбцу стоит дешево.

ORDER  BY <b>p.name</b> -- varchar(255) in same table

1) Общее время выполнения: 2259,171 мс - !!
2) Общее время работы: 2257,650 мс
3) Общее время работы: 2258,282 мс
Сортировка по (длинному) varchar или text столбцу стоит дорого ...

ORDER  BY p.name <b>COLLATE "C"</b>

1) Общее время выполнения: 327,516 мс - !!
2) Общее время работы: 325,103 мс
3) Общее время выполнения: 327,206 мс
... но не так дорого, если обойтись без языка.

С удалением локали сортировка по столбцу varchar не совсем, но почти такая же быстрая. Локаль "C" по сути является "без локали, просто по байтовому значению". Я цитирую руководство :

Если вы хотите, чтобы система работала так, как если бы она не поддерживала локаль, используйте специальное имя локали C или эквивалентно POSIX.


Собрав все вместе, @dvd выбрал:

ORDER  BY d.domain COLLATE "C", p.ordering, p.name

... 3) Общее время работы: 275,854 мс
Это должно сделать.

2 голосов
/ 27 марта 2012

Вывод EXPLAIN ANALYZE идентичен операции сортировки, поэтому сортировка имеет значение.

В обоих запросах вы возвращаете все строки product_product, но в первом случае вы сортируете по столбцу django_site, поэтому дополнительно нужно получить django_site.domain, что стоит дополнительных затрат. Но не объяснил бы большую разницу.

Существует высокая вероятность того, что физический порядок строк в product_product уже соответствует столбцу ordering, что делает сортировку в случае 2 очень дешевой и сортировку в случае 1 дорогой.


После «добавлено больше подробностей»:
Кроме того, значительно дороже , поэтому сортируйте по character varying(100), чем по столбцу integer. В дополнение к тому, что целое число намного меньше, есть также поддержка сортировки, которая замедляет вас. Чтобы проверить, попробуйте заказать с COLLATE "C". Подробнее о поддержке сортировки см. В руководстве . Если вы работали с PostgreSQL 9.1. Теперь я вижу, что у вас есть PostgreSQL 8.4.

Очевидно, что все строки в выводе запроса имеют одинаковое значение для django_site.domain, как вы фильтруете на p.site_id = 1. Если планировщик запросов был умнее, он мог бы пропустить первый столбец для начала заказа.

Вы запускаете PostgreSQL 8.4. Планировщик запросов из 9.1 стал значительно более интеллектуальным. Обновление может изменить ситуацию, но я не могу сказать наверняка.


Чтобы проверить мою теорию о физическом порядке, вы можете попытаться сделать копию своей большой таблицы со строками, вставленными в случайном порядке, а затем снова выполнить запросы. Как это:

CREATE TABLE p AS
SELECT *
FROM   public.product_product
ORDER  BY random();

А потом:

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;

Какая разница? -> Очевидно, это не объясняет ...


ОК, чтобы проверить, имеет ли значение varchar(100), я пересоздал ваш сценарий. См. отдельный ответ с подробным тестовым примером и тестом . Этот ответ уже перегружен.

Подводя итог:
Оказывается, мое другое объяснение подходит. Основная причина замедления, очевидно, заключается в сортировке по столбцу varchar(100) в соответствии с языком (LC_COLLATE) .

Я добавил некоторые пояснения и ссылки на контрольный пример . Результаты должны говорить сами за себя.

0 голосов
/ 27 марта 2012

Насколько я вижу, вам нужны некоторые индексы

  1. создать индекс product_product_idx01 для product_product (active, site_id);Это, вероятно, ускорит ваш запрос.
  2. почему вы заказываете по доменам, это бессмысленно ваш запрос
...