2012-06-18 2 views
7

У меня есть следующий UPDATE запроса:Как ускорить медленный UPDATE запрос

UPDATE Indexer.Pages SET LastError=NULL where LastError is not null; 

Сейчас этот запрос занимает около 93 минут. Я хотел бы найти способы сделать это немного быстрее.

Таблица Indexer.Pages имеет около 506 000 строк, и около 490 000 из них содержат значение для LastError, поэтому я сомневаюсь, что я могу использовать любые индексы здесь.

Таблица (при несжатом состоянии) содержит около 46 гигабайт данных, однако большинство этих данных находится в текстовом поле html. Я считаю, что просто загрузка и разгрузка приводит к замедлению многих страниц. Одна из идей заключалась бы в том, чтобы создать новый стол с толькоId и поле html, и держать Indexer.Pages как можно меньше. Однако тестирование этой теории было бы достойной работой, поскольку на самом деле у меня нет места на жестком диске для создания копии таблицы. Мне пришлось бы скопировать его на другую машину, вынуть таблицу, а затем скопировать данные, которые, вероятно, будут проходить весь вечер.

Идеи? Я использую Postgres 9.0.0.

UPDATE:

Вот схема:

CREATE TABLE indexer.pages 
(
    id uuid NOT NULL, 
    url character varying(1024) NOT NULL, 
    firstcrawled timestamp with time zone NOT NULL, 
    lastcrawled timestamp with time zone NOT NULL, 
    recipeid uuid, 
    html text NOT NULL, 
    lasterror character varying(1024), 
    missingings smallint, 
    CONSTRAINT pages_pkey PRIMARY KEY (id), 
    CONSTRAINT indexer_pages_uniqueurl UNIQUE (url) 
); 

У меня также есть два индекса:

CREATE INDEX idx_indexer_pages_missingings 
    ON indexer.pages 
    USING btree 
    (missingings) 
    WHERE missingings > 0; 

и

CREATE INDEX idx_indexer_pages_null 
    ON indexer.pages 
    USING btree 
    (recipeid) 
    WHERE NULL::boolean; 

В этой таблице нет триггеров, и есть одна другая таблица, которая имеет ограничение FK на Pages.PageId.

+0

Обновление 500 000 строк не должно занять 93 минуты. Я предполагаю, что есть что-то другое. Можете ли вы показать нам определение таблицы? Также копирование 500 000 строк должно выполняться через пару минут (если не секунды, используя 'COPY'), а не« весь вечер ». –

+3

Если html обычно невелик, он автоматически будет храниться в отдельной таблице TOAST за кулисами и не будет значительным в этом обновлении. Любые триггеры или определения внешнего ключа могут быть очень значительными - есть ли какие-либо? Существуют ли какие-либо индексы, которые ссылаются на столбец LastError? Если это так, они, вероятно, будут проблемой. Если вы можете организовать обновление в меньших партиях (например, диапазонов клавиш, например) и VACUUM между партиями, вы избежите раздувания таблицы. Наконец, обновите: http://www.postgresql.org/support/versioning/. Выпуск X.Y.0 не является хорошим местом. – kgrittn

+0

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

ответ

6

Какой @kgrittn posted as comment - лучший ответ.Я просто заполняю детали.

Перед тем, как сделать что-нибудь еще, вы должны upgrade PostgreSQL to a current version, по крайней мере, до последнего выпуска безопасности вашей основной версии. See guidelines on the project.

Я также хочу подчеркнуть, что Кевин упомянул о индексов с участием колонки LastError. Обычно ГОРЯЧИЕ обновления могут перерабатывать мертвые строки на странице данных и делать ОБНОВЛЕНИЯ намного быстрее - эффективно удаляя (большую часть) необходимость в вакуумировании. Связанный:

Если столбец используется в любом индексе в любом случае, HOT обновление отключено, так как это нарушило бы индекс (ы). Если это так, вы должны усовершенствовать запрос много, удалив все эти индексы перед вами UPDATE и заново создайте их позже.

В этом контексте было бы помочь запустить несколько небольших обновлений: Если ...
... обновленный столбец не участвует в каких-либо индексов (Благоприятная ЛЮБИМЫЕ обновления). ... UPDATE легко делится на несколько патчей в несколько транзакций. ... строки в этих патчах распределены по таблице (физически, а не логически). ... нет других параллельных транзакций, в которых сохраняются мертвые кортежи.

Тогда вам не нужно VACCUUM в между несколькими участками, так ГОРЯЧЕЕ обновление может повторно использовать мертвые кортежи напрямую - только мертвые кортежи из предыдущих сделок, а не из одних и тех же или одновременно из них. Возможно, вам захочется запланировать VACUUM в конце операции или просто позволить авто-пылесосу выполнять свою работу.

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

Кроме того, ваше обновление вряд ли приведет к потере внешних ограничений . Вы можете попытаться удалить &. Это открывает временной интервал, в котором ссылочная целостность не будет применяться. Если целостность нарушена во время UPDATE, вы получаете сообщение об ошибке при попытке воссоздать FK. Если вы все это в один сделки, параллельные транзакции никогда не увидеть сброшенный FK, но вы берете блокировку записи на столе - такой же, как и капельный/воссоздающих индексы или триггеры)

Наконец, disable & enable triggers, которые не требуется для обновления.

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

Итак:

BEGIN; 
ALTER TABLE tbl DISABLE TRIGGER user; -- disable all self-made triggers 
-- DROP indexes (& fk constraints ?) 
-- UPDATE ... 
-- RECREATE indexes (& fk constraints ?) 
ALTER TABLE tbl ENABLE TRIGGER user; 
COMMIT; 

Вы не можете запустить VACUUM внутри блока транзакции. Per documentation:

VACUUM не может быть выполнен внутри блока транзакций.

Вы могли бы разделить вашу работу на несколько больших кусков и запустить между ними:

VACUUM ANALYZE tbl; 

Если вы не имеете дело с параллельными транзакциями вы могли (даже более эффективно):

ALTER TABLE tbl DISABLE TRIGGER user; -- disable all self-made triggers 
-- DROP indexes (& fk constraints ?) 

-- Multiple UPDATEs with logical slices of the table 
-- each slice in its own transaction. 
-- VACUUM ANALYZE tbl; -- optionally in between, or autovacuum kicks in 

-- RECREATE indexes (& fk constraints ?) 
ALTER TABLE tbl ENABLE TRIGGER user; 
+1

Незначительные моменты: Инкрементальные вакуумы будут свободными указателями на прямую линию, которые невозможно обрезать ГОРЯЧЕЙ при нормальном доступе. Это * может * сделать это стоящим между шагами UPDATE. Я рекомендую «VACUUM FREEZE ANALYZE» в таблице в конце процесса, если ожидается, что эти строки останутся (без дополнительных «UPDATE» или «DELETE») в течение длительного времени; в противном случае автовакуумные задания, которые заставляют замораживать кортежи для предотвращения обхода идентификатора транзакции, могут быть болезненными. В этот момент вы можете переписать кортежи с информацией о битве подсказки и замороженными идентификаторами транзакций, сохраняя небольшие накладные расходы. – kgrittn

+1

Кстати, люди в настоящее время сталкиваются с некоторыми из давних проблем с производительностью с внешними ключами, в том числе с гораздо более умными уклонениями от уплаты, если внесенные изменения не могут вызвать проблему ссылочной целостности. Если этот ответ все еще вокруг после выхода 9.3, сброс внешних ключей может быть гораздо менее важным. (И да, я имею в виду 9,3, которые, вероятно, будут выпущены летом 2013 года). – kgrittn

0

Ваша теория, вероятно, правильная. Чтение полной таблицы (и затем выполнение чего-либо), вероятно, вызывает замедление.

Почему бы вам не создать другую таблицу с параметрами PageId и LastError? Инициализируйте это с данными в таблице, которую вы сейчас имеете (что займет менее 93 минут). Затем используйте LastError из новой таблицы.

В свободное время вы можете удалить LastError из существующей таблицы.

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

+0

Отличное представление о создании новой таблицы с меньшим объемом данных, а затем удаление других столбцов из первого. Тогда я мог бы просто переименовать таблицы, когда закончите. Кроме того, в новой таблице могут быть только 'HtmlId' и' Html', а 'Indexer.Pages' может иметь ссылку на' HtmlId' - это может быть немного больше * normal *. –

1
UPDATE Indexer.Pages 
    SET LastError=NULL 
    ; 

где положение не требуется, так как NULL поля уже NULL, так что это не повредит, чтобы установить их снова NULL (я не думаю, что это существенно влияет на производительность).

Учитывая ваш номер_о_о_ = 500 КБ и ваш размер таблицы = 46G, я делаю вывод, что ваш средний строк составляет 90 КБ. Это огромно. Может быть, вы могли бы переместить {неиспользуемые, разреженные} столбцы таблицы в другие таблицы?

+0

Я, как правило, на той же странице, что и вы, однако @kgrittn упоминается, поскольку эти строки тосты с главной таблицы, это не должно влиять на обновление perf, так как я не касаюсь этих столбцов. Мне любопытно, если это правда. К сожалению, я не смогу попробовать это до позднего вечера, когда вернусь домой. –

+1

@MikeChristensen: О, этот совет не годится! Это приведет к пустым UPDATE, замедляющим ваш запрос. Вы должны обязательно включить предложение WHERE. Кроме того, поскольку PostgreSQL использует таблицы TOAST для больших значений, это, вероятно, не поможет разделить вашу таблицу. –

+0

Помимо необоснованной стоимости ненужных обновлений строк, упомянутых в @ErwinBrandstetter, вы излишне раздували бы свою таблицу, в результате чего весь последующий доступ был бы медленным до тех пор, пока вы не начнете агрессивное обслуживание.Обратите внимание, что в PostgreSQL UPDATE создает новую версию строки в некоторой новой позиции, оставляя старую версию строки для последующей очистки вакуумным процессом. UPDATE без предложения WHERE удваивает размер вашей базовой таблицы. Вот почему я рекомендовал несколько меньших UPDATE с VACUUM между ними, поэтому пространство из старых строк можно использовать повторно. – kgrittn

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