2013-07-22 5 views
4

я в настоящее время работаю над сложной проблемой сортировки в Postgres 9.2 Вы можете найти исходный код, используемые в этом вопросе (упрощенном) здесь: http://sqlfiddle.com/#!12/9857e/11Postgresql Сортировка присоединяемой таблицы с индексом

У меня есть огромный (>> 20Mio rows), содержащую различные столбцы разных типов.

CREATE TABLE data_table 
(
    id bigserial PRIMARY KEY, 
    column_a character(1), 
    column_b integer 
    -- ~100 more columns 
); 

Допустим, я хочу рода эту таблицу более 2 Столбцы (ASC). Но я не хочу делать это с помощью простого порядка By, потому что позже мне может понадобиться вставить строки в отсортированный результат, и пользователь, вероятно, только хочет видеть 100 строк сразу (из отсортированного вывода) ,

Для достижения этих целей я сделать следующее:

CREATE TABLE meta_table 
(
id bigserial PRIMARY KEY, 
id_data bigint NOT NULL -- refers to the data_table 
); 

--Function to get the Column A of the current row 
CREATE OR REPLACE FUNCTION get_column_a(bigint) 
RETURNS character AS 
'SELECT column_a FROM data_table WHERE id=$1' 
LANGUAGE sql IMMUTABLE STRICT; 

--Function to get the Column B of the current row 
CREATE OR REPLACE FUNCTION get_column_b(bigint) 
RETURNS integer AS 
'SELECT column_b FROM data_table WHERE id=$1' 
LANGUAGE sql IMMUTABLE STRICT; 

--Creating a index on expression: 
CREATE INDEX meta_sort_index 
ON meta_table 
USING btree 
(get_column_a(id_data), get_column_b(id_data), id_data); 

А потом скопировать корочки data_table к meta_table:

INSERT INTO meta_table(id_data) (SELECT id FROM data_table); 

Позже можно добавить дополнительные строки в таблицу с аналогичная простая вставка.
Чтобы получить Ряды 900000 - 900099 (100 строк) Теперь я могу использовать:

SELECT get_column_a(id_data), get_column_b(id_data), id_data 
FROM meta_table 
ORDER BY 1,2,3 OFFSET 900000 LIMIT 100; 

(. С дополнительным INNER JOIN на data_table, если я хочу, чтобы все данные)
Результирующий План:

Limit (cost=498956.59..499012.03 rows=100 width=8) 
-> Index Only Scan using meta_sort_index on meta_table (cost=0.00..554396.21 rows=1000000 width=8) 

Это довольно эффективный план (только для сканирования только в Postgres 9.2).
Но что, если я хочу получить строки 20'000'000 - 20'000'099 (100 строк)? Тот же план, гораздо более длительное время исполнения. Ну, чтобы улучшить производительность смещения (Improving OFFSET performance in PostgreSQL), я могу сделать следующее (предположим, я сохранил каждые 100'000 рядов в другую таблицу).

SELECT get_column_a(id_data), get_column_b(id_data), id_data 
FROM meta_table 
WHERE (get_column_a(id_data), get_column_b(id_data), id_data) >= (get_column_a(587857), get_column_b(587857), 587857) 
ORDER BY 1,2,3 LIMIT 100; 

Это работает намного быстрее. Результирующий план:

Limit (cost=0.51..61.13 rows=100 width=8) 
-> Index Only Scan using meta_sort_index on meta_table (cost=0.51..193379.65 rows=318954 width=8) 
Index Cond: (ROW((get_column_a(id_data)), (get_column_b(id_data)), id_data) >= ROW('Z'::bpchar, 27857, 587857)) 

До сих пор все работает отлично, и postgres отлично справляется!

Предположим, я хочу изменить порядок 2-й колонны на DESC.
Но тогда мне пришлось бы изменить свое предложение WHERE, потому что оператор> Оператор сравнивает обе колонки ASC. Тот же самый запрос, как описаны выше (ASC Упорядочение) также можно записать в виде:

SELECT get_column_a(id_data), get_column_b(id_data), id_data 
FROM meta_table 
WHERE 
    (get_column_a(id_data) > get_column_a(587857)) 
OR (get_column_a(id_data) = get_column_a(587857) AND ((get_column_b(id_data) > get_column_b(587857)) 
OR (             (get_column_b(id_data) = get_column_b(587857)) AND (id_data >= 587857)))) 
ORDER BY 1,2,3 LIMIT 100; 

Теперь план изменения и запросы становятся медленными:

Limit (cost=0.00..1095.94 rows=100 width=8) 
-> Index Only Scan using meta_sort_index on meta_table (cost=0.00..1117877.41 rows=102002 width=8) 
Filter: (((get_column_a(id_data)) > 'Z'::bpchar) OR (((get_column_a(id_data)) = 'Z'::bpchar) AND (((get_column_b(id_data)) > 27857) OR (((get_column_b(id_data)) = 27857) AND (id_data >= 587857))))) 

Как я могу использовать эффективный старший план с DESC- Заказ?
Есть ли у вас лучшие идеи, как решить проблему?

(я уже пытался объявить собственный тип с собственными классами оператора, но это слишком медленно)

+0

Благодаря http://stackoverflow.com/questions/1677538/advanced-indexing-involving-or-ed-conditions-pgsql Я попробовал UNIONS. Это немного лучше, чем последний план, но этого все еще недостаточно. http://sqlfiddle.com/#!12/9857e/28/3 – Dreamcooled

ответ

4

Вы должны пересмотреть свой подход. С чего начать? Это явный пример, в основном, ограничений, по производительности, такого рода функциональный подход, который вы используете для SQL. Функции в основном не прозрачны, и вы вынуждаете два разных поиска на data_table для каждой полученной строки, потому что планы хранимой процедуры не могут быть свернуты вместе.

Теперь, что еще хуже, вы индексируете одну таблицу на основе данных в другой. Это может работать только для работы с приложениями (вставки разрешены, но без обновлений), но он будет не работать, если в data_table могут быть применены обновления. Если данные в data_table когда-либо будут изменены, у вас будет указатель возврата неверных результатов.

В этих случаях вы находитесь почти всегда лучше писать в объединении в качестве явного и позволять планировщику лучше всего извлекать данные.

Теперь ваша проблема заключается в том, что ваш индекс становится намного менее полезным (и намного более интенсивным дисковым вводом-выводом) при изменении порядка второго столбца. С другой стороны, если у вас было два разных индекса на data_table и было явное соединение, PostgreSQL мог бы более легко справиться с этим.

Смежные вопросы