2015-12-05 2 views
6

У меня есть таблицаОптимизировать запрос с OFFSET на большом столе

create table big_table (
id serial primary key, 
-- other columns here 
vote int 
); 

Эта таблица очень большая, около 70 миллионов строк, мне нужно запросить:

SELECT * FROM big_table 
ORDER BY vote [ASC|DESC], id [ASC|DESC] 
OFFSET x LIMIT n -- I need this for pagination 

Как вы знаете, когда x это большое количество, такие запросы очень медленные.

Для оптимизации производительности я добавил индексы:

create index vote_order_asc on big_table (vote asc, id asc); 

и

create index vote_order_desc on big_table (vote desc, id desc); 

EXPLAIN показывает, что выше SELECT запросов использует эти индексы, но это очень медленно, в любом случае с большим смещением.

Что можно сделать для оптимизации запросов с помощью OFFSET в больших таблицах? Может быть, PostgreSQL 9.5 или даже более новые версии имеют некоторые функции? Я искал, но ничего не нашел.

+0

Как часто таблица обновляется? –

+0

очень часто, в среднем: каждые 10, 20 секунд обновляется по крайней мере 1 строка – RIKI

+0

почему бы не привести в порядок разбиение на страницы? http://blog.jooq.org/2013/10/26/faster-sql-paging-with-jooq-using-the-seek-method/ –

ответ

15

Большое OFFSET всегда будет медленным. Postgres должен заказывать все строки и считать видимыми до вашего смещения. Для того, чтобы пропустить все предыдущие строки непосредственно вы можете добавить индексированную row_number к столу (или создать MATERIALIZED VIEW в том числе сказал row_number) и работать с WHERE row_number > x вместо OFFSET x.

Однако этот подход применим только для данных только для чтения (или в основном). Реализация же для табличных данных, которые могут меняться одновременно является более сложной задачей. Вам необходимо начать с определения желаемого поведения точно.

я предлагаю другой подход к пагинации:

SELECT * 
FROM big_table 
WHERE (vote, id) > (vote_x, id_x) -- ROW values 
ORDER BY vote, id -- needs to be deterministic 
LIMIT n; 

Где vote_x и id_x взяты из последних строки предыдущей страницы (для обоего DESC и ASC). Или от сначала при навигации назад.

Сравнение значений строк поддерживается индексом, который у вас уже есть - функцией, совместимой с ANSI SQL, но не каждая СУБД ее поддерживает.

CREATE INDEX vote_order_asc ON big_table (vote, id); 

Или убывающем порядке:

SELECT * 
FROM big_table 
WHERE (vote, id) < (vote_x, id_x) -- ROW values 
ORDER BY vote DESC, id DESC 
LIMIT n; 

Можно использовать один и тот же индекс.
Я предлагаю вам объявить столбцы NOT NULL или ознакомиться с NULLS FIRST|LAST конструкцией:

Примечание две вещи в частности:

  1. The ROW значения в Предложение WHERE не может быть заменено отдельным d членов. WHERE (vote, id) > (vote_x, id_x)не может быть заменен:

    
                   
        
          WHERE vote >= vote_x AND id > id_x 
        

    Это исключает все строки с id <= id_x, в то время как мы хотим, чтобы сделать это за тот же голос, а не для следующего. Правильный перевод был бы:

    WHERE (vote = vote_x AND id > id_x) OR vote > vote_x 
    

    ... который не играет вместе с индексами, как хорошо, и становится все более сложным для нескольких столбцов.

    Простой для одиночный столбец, очевидно. Это особый случай, о котором я говорил вначале.

  2. Метод не работает для смешанных направлений в ORDER BY как:

    ORDER BY vote ASC, id DESC 
    

    По крайней мере, я не могу думать о родового способ реализации этого максимально эффективно.Если хотя бы один из двух столбцов числового типа, вы могли бы использовать функциональный индекс с перевернутым значением на (vote, (id * -1)) - и использовать то же самое выражение в ORDER BY:

    ORDER BY vote ASC, (id * -1) ASC 
    

Похожие:

Примечание, в частности, презентация Маркуса Winand я связан:

+0

Thank вы так много для своих полезных и информационных ответов. В этом случае я решил загрузить строки «step_by_step». (Как комментарии youtube). Я использую такой код: '(vote> x или (vote = x и id> y))' используя язык программирования, где храню 'vote' и' id' из ранее загруженного результата. печально, что еще нет способа сделать «реальную разбивку на страницы» большими столами, но этот подход «step_by_step» также не так уж плохо – RIKI

+0

Спасибо. Я обнаружил Маркуса Винанда через ваш ответ. Это само по себе удивительно. –

-4

Вы пробовали разделить стол?

Простота управления, улучшенная масштабируемость и доступность, а также уменьшение блокировки - это общие причины для разделения таблиц. Повышение эффективности запросов не является основанием для использования разделения, , хотя в некоторых случаях это может быть полезным побочным эффектом. Что касается производительности , важно убедиться, что в вашем плане реализации представлен обзор производительности запросов. Убедитесь, что ваши индексы продолжают соответствующим образом поддерживать ваши запросы после того, как таблица секционирована и убедитесь, что запросы с использованием кластеризованных и некластеризованных индексов выигрывают от исключения разделов, где применимо .

http://sqlperformance.com/2013/09/sql-indexes/partitioning-benefits

+1

То есть для SQL Server, а не для Postgres –

+0

PostgreSQL поддерживает базовое разбиение таблиц: http://www.postgresql.org/docs/current/interactive/ddl-partitioning.html – thepiyush13

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