Postgres 8.4.4 (x32 на Win7 x64) очень медленное ОБНОВЛЕНИЕ на маленькой таблице

У меня очень простой оператор обновления:

UPDATE W SET state='thing'
WHERE state NOT IN ('this','that') AND losttime < CURRENT_TIMESTAMP;

Таблица W имеет только 90 строк, хотя столбцы потерянного времени и состояния для каждой строки обновляются каждый примерно каждые ~ 10 секунд. Есть индексы состояния и потерянного времени (а также первичный индекс).

Я заметил, что с большими базами данных (т.е. в других таблицах много записей, а не в таблице W) в течение определенного периода времени запрос становится все медленнее, медленнее и медленнее. После работы в течение 48 часов я замерил время, запустив его в окне запроса PqAdminIII, и его выполнение заняло 17 минут!

У меня есть аналогичный запрос в другой таблице, которая показывает ту же проблему:

UPDATE H SET release='1' 
WHERE a NOT IN (SELECT id from A WHERE state!='done') AND release!='1';

H не имеет никаких индексов, но я пробовал добавлять и удалять индексы в H (релиз) без каких-либо изменений в поведении. Этот запрос, после того как база данных была в рабочем состоянии в течение 48 часов, а таблица H имеет ~ 100 тыс. строк, занимает 27 минут. Сервер Postgres будет иметь поток, полностью привязанный (100% загрузка ЦП) на время выполнения запроса, поэтому не похоже, что существует какая-либо конкуренция за сеть, диск и т. д.

Таким образом, в общих чертах, поведение, которое я вижу, заключается в том, что моя база данных работает, как и ожидалось, в течение примерно 5 минут, затем постепенно все останавливается, поскольку основные команды UPDATE, связанные с обслуживанием, начинают выполняться все дольше и дольше. Ко второму дню требуется час, чтобы выполнить простой цикл обслуживания (несколько ОБНОВЛЕНИЙ), который в начале выполнялся ~ 100 мс. Мне кажется очевидным, что снижение производительности суперлинейно в зависимости от количества информации в базе данных - может быть, N ^ 2 или что-то в этом роде.

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

Вот ломаю голову. Я не вижу никаких исправлений ошибок, которые кажутся актуальными в примечаниях к выпуску 9.0.1 и 9.0.2. Может ли кто-нибудь помочь мне понять, что происходит? Спасибо, М

-x-x-x-x-

Итак, у меня могут быть две проблемы.

Первое обновление теперь работает быстро. Не уверен, что произошло, поэтому я продолжу с предположения, что мне нужно запускать VACUUM / ANALYZE или какую-то комбинацию чаще — скажем, каждую минуту или около того. Я действительно хотел бы знать, почему autovacuum не делает этого для меня.

Второе обновление продолжает работать медленно. План запроса предполагает, что индексы используются неэффективно и что происходит пересечение 80k * 30k, что может объяснить сверхлинейное время выполнения, которое я, кажется, наблюдаю. (Все ли согласны с такой интерпретацией плана?)

Я могу преобразовать UPDATE в SELECT:

SELECT * from H
where a not in (SELECT id from A where state='done') AND release!='1';

с аналогичным временем выполнения (27 минут).

Если я не доверяю оптимизатору postgres и делаю это:

WITH r as (select id from A where state='done')
SELECT a from H 
JOIN on H.a=r.id 
WHERE H.released='0';

затем запрос выполняется через ~ 500 мс.

Как перевести эти знания обратно в ОБНОВЛЕНИЕ, работающее с приемлемой скоростью? Моя попытка:

UPDATE H SET release='1'
FROM A
where A.state!='done' AND release!='1' AND A.id=H.a;

выполняется примерно за 140 секунд, что быстрее, но все же очень-очень медленно.

Куда я могу пойти отсюда?

-x-x-x-x-

VACUUM ANALYZE был добавлен как часть «планового обслуживания», когда приложение будет запускать его примерно раз в минуту или около того независимо от любой запущенной автоматической очистки.

Кроме того, переписали второй запрос, убрав заведомо медленное предложение NOT IN, заменив его на «левое антиполусоединение» (да?)

UPDATE H SET release='1' 
WHERE release='0' AND NOT EXISTS (SELECT * FROM A WHERE id=H.a AND state!='done');

person Mayur Patel    schedule 24.12.2010    source источник
comment
как насчет того, чтобы не обновлять таблицы? просто просмотрите информацию, которую вам нужно получить в таблицах W и H.   -  person Hao    schedule 25.12.2010
comment
Пожалуйста, опубликуйте планы для обоих обновлений.   -  person Quassnoi    schedule 25.12.2010
comment
@Hao: меня смущает это предложение. Это не операция чтения, я фактически изменяю содержимое каждой таблицы.   -  person Mayur Patel    schedule 29.12.2010
comment
@Quassnoi: первый план ОБНОВЛЕНИЯ: последовательное сканирование на W (стоимость = 0,00..2,40 строк = 1 ширина = 52) Фильтр: ((состояние ‹› ВСЕ ('{это, это}'::Wstate[])) И (losttime ‹ now())) Второй план UPDATE: Seq Scan on H (cost=2706.66..129428126.50 rows=83308 width=22) Filter: ((release ‹› 1::smallint) AND (NOT (SubPlan 1) )) SubPlan 1 -> Материализация (стоимость = 2706,66..3735,35 строк = 73969 ширина = 4) -> Seq Scan on A (стоимость = 0,00..2343,69 строк = 73969 ширина = 4) Фильтр: (состояние ‹› 'готово' ::Astate) Меня смущает последовательное сканирование A: у меня есть индекс состояния.   -  person Mayur Patel    schedule 29.12.2010
comment
пожалуйста, не оставляйте код в комментариях. Вместо этого обновите свой собственный пост.   -  person Quassnoi    schedule 29.12.2010


Ответы (3)


PostgreSQL реализует MVCC.

Это означает, что каждый раз, когда вы делаете обновление, создается новая копия строки, а старая помечается как удаленная (но не удаляется физически).

Это замедляет запросы.

Вы должны запускать VACUUM своевременно.

PostgreSQL 8.4.4 для этого запускает демон autovacuum, но при установке могут возникнуть проблемы.

Улучшается ли ситуация, когда вы запускаете VACUUM вручную?

person Quassnoi    schedule 24.12.2010
comment
В инструкции написано, что ВАКУУМ раз в день должно быть достаточно. Я обновляю строки в таблице W с частотой порядка секунд. По умолчанию время автоматического вакуумирования составляет 1 минуту. Математика для меня не складывается, если только автовакуум по какой-то причине просто не работает. Он настроен для запуска в postgresql.conf - person Mayur Patel; 24.12.2010
comment
@Mayur: Интервал между пылесосами может отличаться в зависимости от вашей загрузки. autovacuum_naptime определяет только период, в течение которого демон просыпается и проверяет необходимость очистки, он не обязательно запускается VACUUM один раз в минуту. Вернутся ли запросы к нормальной скорости после того, как вы выполните VACUUM вручную? - person Quassnoi; 24.12.2010
comment
Извините, я думаю, вы, должно быть, отредактировали свой ответ после того, как я ответил. Я все еще запускаю пару других тестов в поврежденном состоянии, прежде чем попытаться запустить VACUUM. Поскольку для того, чтобы проблема стала очевидной, требуется некоторое время, сейчас я пытаюсь провести как можно больше анализа. Сообщит, работает ли ВАКУУМ. (Но я скажу, что вчера я запустил один из pgAdminIII и не заметил улучшения поведения.) - person Mayur Patel; 24.12.2010
comment
Я запустил VACUUM из окна запроса pqAdminIII. Для запуска потребовалось ~ 8 секунд, но второе ОБНОВЛЕНИЕ, которое я цитировал, все еще выполняется через 4 минуты, поэтому я думаю, что этого достаточно, чтобы сказать, что оно не помогло. Если мне нужно запустить VACUUM FULL, это может стать препятствием для сделки, потому что эта база данных должна быть высокодоступной. Не будет простоев, когда столы могут быть полностью заблокированы на длительное время. (вздох) Я рад попробовать другие предложения... - person Mayur Patel; 24.12.2010
comment
Я запустил VACUUM FULL, выполнение которого заняло 18 минут. Вторая команда UPDATE, которую я процитировал, все еще работает после 60-х.... - person Mayur Patel; 24.12.2010
comment
Никогда не используйте VACUUM FULL. Возможно, вы захотите попробовать CLUSTER на этой таблице. Но я почти уверен, что вам нужно сделать автоочистку более агрессивной для этой таблицы. - person a_horse_with_no_name; 30.12.2010
comment
@a_horse_with_no_name: почему тогда это реализовано? :) - person Quassnoi; 30.12.2010
comment
@Quassnoi: то, как это работает, было фактически изменено в 9.0 из-за всех проблем, которые у него были. Я думаю, что это в основном историческая причина. Но если вы следите за списком рассылки, вы заметите, что люди, которые знают о PG гораздо больше, чем я, регулярно отговаривают от использования VACUUM FULL. - person a_horse_with_no_name; 30.12.2010
comment
@a_horse_with_no_name: VACUUM FULL сжимает таблицу и возвращает свободное место в табличное пространство. Обычный VACUUM только помечает удаленные записи как свободные, что делает их доступными только для текущей таблицы (а не для любой таблицы). Это не уменьшает таблицу: она просто больше не растет. CLUSTER восстановить физический порядок страниц таблицы в соответствии с логическим порядком индекса, на котором она кластеризуется. Эта операция также блокирует таблицу в монопольном режиме и занимает даже немного больше времени, чем VACUUM FULL (поскольку последняя не упорядочивает). - person Quassnoi; 30.12.2010
comment
@a_horse_with_no_name: и из документов: Обычный VACUUM может быть неудовлетворительным, если таблица содержит большое количество мертвых версий строк в результате массового обновления или удаления. Если у вас есть такая таблица и вам нужно освободить лишнее место на диске, которое она занимает, вам нужно будет использовать VACUUM FULL или альтернативно CLUSTER или один из вариантов перезаписи таблицы ALTER TABLE. Другими словами, никогда не следует используется немного преувеличение. - person Quassnoi; 30.12.2010
comment
Я собираюсь отметить это как ответ, хотя мне еще нужно выполнить некоторую оптимизацию запросов, чтобы сделать второе ОБНОВЛЕНИЕ более эффективным. Я думаю, что полный ответ заключается в том, чтобы сделать свой собственный VACUUM ANALYZE и не доверять автовакууму, чтобы не отставать от вас. - person Mayur Patel; 30.12.2010
comment
@Quasssnoi: я понимаю, как работает VACUUM FULL, но это может привести к раздуванию индекса и обычно не рекомендуется командой Postgres. Вот почему его внутренняя работа была полностью переписана для версии 9.0. - person a_horse_with_no_name; 30.12.2010
comment
@a_horse_with_no_name: VACUUM FULL не может привести к раздуванию индекса: он разработан, чтобы справиться с раздуванием индекса :) - person Quassnoi; 30.12.2010
comment
@Quassnoi: Извините, но вы ошибаетесь. До версии 9.0 это действительно вызывало раздувание индекса. См. это обсуждение в Postgres Wiki: wiki.postgresql.org/wiki/VACUUM_FULL (одна цитата отсюда: Не используйте его для оптимизации таблиц или периодического обслуживания, так как это контрпродуктивно.) - person a_horse_with_no_name; 30.12.2010
comment
@a_horse_with_no_name: извините, но это не так. Не VACUUM FULL вызывает раздувание индекса: экстенсивный DML вызывает раздувание индекса. VACUUM FULL не уменьшает индексы, но предотвращает их увеличение (для индексов ведет себя как обычный VACUUM). Тот факт, что он не лечит раздувание индекса, не означает, что он вызывает его. Что касается цитаты, базы данных не то место, где работают проверенные авторитетом. - person Quassnoi; 30.12.2010
comment
@Quassnoi: я, например, поверю тому, что команда Postgres пишет в руководстве (и в Wiki). В руководстве по версии 8.4 четко указано, что перемещение строки требует временного создания для нее повторяющихся записей индекса (...); поэтому перемещение большого количества строк таким образом приводит к серьезному раздуванию индекса. Перемещение строк — это точно то, что делает VACUUM FULL. - person a_horse_with_no_name; 30.12.2010
comment
@a_horse_with_no_name: никто не может отнять у тебя право верить. Однако, если вы читали немного раньше, вы увидите, что VACUUM помечает мертвые строки в индексах как доступные, и именно туда попадают новые версии записей индекса, указывающие на перемещенные записи. В крайних случаях (по иронии судьбы это означает, что таблицы почти заполняют свое пространство) движок не найдет достаточно свободного места для перемещения новой записи, и VACUUM FULL действительно выделит некоторое новое пространство. Но если таблица содержит много мертвых записей, то не проблема найти место для индексных записей в переработанном пространстве. - person Quassnoi; 30.12.2010
comment
@a_horse_with_no_name: это означает, что нет необходимости выполнять VACUUM FULL на регулярной основе (в этом вы и документация были правы), но OK нужно выполнять VACUUM FULL для сжатия уже раздутой таблицы на месте. - person Quassnoi; 30.12.2010

Проверьте с помощью pg_total_relation_size('tablename'), не слишком ли раздуты ваши таблицы. В этом случае вам может потребоваться настроить автоочистку.

Другая альтернатива заключается в том, что столы заблокированы. Загляните в pg_stat_activity или pg_locks, чтобы узнать.

person Peter Eisentraut    schedule 24.12.2010
comment
Да, похоже на закрытые записи. - person Frank Heikens; 25.12.2010
comment
pg_total_relation_size == 750 КБ - person Mayur Patel; 29.12.2010
comment
Посмотрел представления pg_stat_activity и pg_locks. Ничто не бросилось мне в глаза, пара записей в каждом представлении, но ничего, что заставило бы меня думать, что огромное количество строк остается заблокированным. (Я не уверен, почему ЦП будет привязываться к заблокированным строкам, я ожидал, что в этом случае загрузка ЦП будет очень низкой.) Есть ли что-то конкретное, на что я должен обратить внимание в этих представлениях? Спасибо - person Mayur Patel; 29.12.2010

Я думаю, что вы не правильно закрываете сделки.

person GolezTrol    schedule 24.12.2010
comment
Я получаю доступ к postgres через адаптер python, psycopg. Я включаю автоматическую фиксацию при создании подключения, потому что каждое из этих обновлений может выполняться автономно, они не должны быть составной атомарной операцией. Можете ли вы помочь мне понять, почему вы подозреваете, что транзакции не закрываются? Почему это может привести к медленным запросам и почему я не получаю ошибок в то время, когда курсор/соединение (возможно, неправильно) уничтожается? Данные в базе действительны, поэтому я не вижу там сбоя. Поподробнее о ходе ваших мыслей, пожалуйста? - person Mayur Patel; 24.12.2010
comment
Ну, я не уверен, но я только недавно наблюдал такое снижение производительности в аналогичной ситуации, когда проблема заключалась в том, чтобы не закрывать транзакции. Если вы уверены, что все транзакции совершены, я должен ошибаться, и это должно быть что-то еще. - person GolezTrol; 24.12.2010
comment
У меня произошел сбой сервера postgres, когда я убил клиента с открытым соединением, но в остальном данные в базе данных выглядят правильно. Буду следить за проблемами с подключением. Спасибо. - person Mayur Patel; 29.12.2010