Почему ОБНОВЛЕНИЕ Postgres заняло 39 часов?

17

У меня есть таблица Postgres с ~ 2,1 миллиона строк. Я запустил обновление ниже:

WITH stops AS (
    SELECT id,
           rank() OVER (ORDER BY offense_timestamp,
                     defendant_dl,
                     offense_street_number,
                     offense_street_name) AS stop
    FROM   consistent.master
    WHERE  citing_jurisdiction=1
)

UPDATE consistent.master
SET arrest_id=stops.stop
FROM stops
WHERE master.id = stops.id;

Этот запрос занял 39 часов. Я использую это на 4-х (физическом) процессоре для ноутбука i7 Q720, много оперативной памяти, больше ничего не работает в большинстве случаев. Нет места на жестком диске. Стол был недавно очищен, проанализирован и переиндексирован.

Все время выполнения запроса, по крайней мере, после первоначального WITHзавершения, загрузка ЦП обычно была низкой, а жесткий диск использовался на 100%. Жесткий диск использовался так сильно, что любое другое приложение работало значительно медленнее, чем обычно.

Настройки питания ноутбука были на высокой производительности (Windows 7 x64).

Вот ОБЪЯСНЕНИЕ:

Update on master  (cost=822243.22..1021456.89 rows=2060910 width=312)
  CTE stops
    ->  WindowAgg  (cost=529826.95..581349.70 rows=2060910 width=33)
          ->  Sort  (cost=529826.95..534979.23 rows=2060910 width=33)
                Sort Key: consistent.master.offense_timestamp, consistent.master.defendant_dl, consistent.master.offense_street_number, consistent.master.offense_street_name
                ->  Seq Scan on master  (cost=0.00..144630.06 rows=2060910 width=33)
                      Filter: (citing_jurisdiction = 1)
  ->  Hash Join  (cost=240893.51..440107.19 rows=2060910 width=312)
        Hash Cond: (stops.id = consistent.master.id)
        ->  CTE Scan on stops  (cost=0.00..41218.20 rows=2060910 width=48)
        ->  Hash  (cost=139413.45..139413.45 rows=2086645 width=268)
              ->  Seq Scan on master  (cost=0.00..139413.45 rows=2086645 width=268)

citing_jurisdiction=1исключает только несколько десятков тысяч строк. Даже с этим WHEREусловием я все еще работаю над более чем 2 миллионами строк.

Жесткий диск полностью зашифрован с помощью TrueCrypt 7.1a. То , что замедляет немного, но не достаточно , чтобы вызвать запрос , чтобы принять , что много часов.

WITHЧасть занимает около 3 минут для запуска.

arrest_idПоле не было никакого индекса внешнего ключа. В этой таблице 8 индексов и 2 внешних ключа. Все остальные поля в запросе индексируются.

arrest_idПоле не было никаких ограничений , кроме NOT NULL.

Всего в таблице 32 столбца.

arrest_idимеет тип символов, меняющихся (20) . Я понимаю, что rank()производит числовое значение, но я должен использовать переменный символ (20), потому что у меня есть другие строки, где citing_jurisdiction<>1используются не числовые данные для этого поля.

arrest_idПоле было пустым для всех строк с citing_jurisdiction=1.

Это персональный, высококлассный (по состоянию на 1 год назад) ноутбук. Я единственный пользователь. Другие запросы или операции не выполнялись. Блокировка кажется маловероятной.

В этой таблице или в другом месте базы данных нет триггеров.

Другие операции с этой базой данных никогда не занимают много времени. При правильной индексации SELECTзапросы обычно выполняются довольно быстро.

Арен Камбре
источник
Это Seq Scanнемного страшно ...
rogerdpack

Ответы:

18

Недавно у меня произошло нечто подобное с таблицей из 3,5 миллионов строк. Мое обновление никогда не закончится. После долгих экспериментов и разочарований я наконец нашел виновника. Оказалось, что это индексы на обновляемой таблице.

Решение состояло в том, чтобы отбросить все индексы в обновляемой таблице перед выполнением оператора update. Как только я это сделал, обновление закончилось через несколько минут. После завершения обновления я заново создал индексы и вернулся к работе. Это, вероятно, не поможет вам на данный момент, но это может кто-то другой ищет ответы.

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

JC Avena
источник
3
Я переключаю лучший ответ для вас. С тех пор как я опубликовал это, я столкнулся с другими ситуациями, когда проблема заключается в индексах, даже если обновляемый столбец уже имеет значение и не имеет индекса (!). Кажется, у Postgres есть проблема с тем, как он управляет индексами в других столбцах. Нет никаких причин для того, чтобы эти другие индексы увеличивали время запроса на обновление, когда единственное изменение в таблице - это обновление неиндексированного столбца, а вы не увеличиваете выделенное пространство для какой-либо строки этого столбца.
Арен Камбре
1
Благодарность! Надеюсь, это поможет другим. Это спасло бы меня от головной боли из-за чего-то очень простого.
JC Avena
5
@ArenCambre - есть причина: PostgreSQL копирует всю строку в другое место и помечает старую версию как удаленную. Именно так в PostgreSQL реализовано управление несколькими версиями параллелизма (MVCC).
Петр Финдейзен
Мой вопрос ... почему это преступник? См. Также stackoverflow.com/a/35660593/32453
rogerdpack
15

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

TrueCrypt замедляет процесс записи больше, чем «немного». Чтение будет достаточно быстрым, но при записи RAID 5 будет выглядеть быстро. Запуск БД на томе TrueCrypt будет пыткой для записи, особенно случайной записи.

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

BEGIN;
SELECT ... INTO TEMPORARY TABLE master_tmp ;
TRUNCATE TABLE consistent.master;
-- Now DROP all constraints on consistent.master, then:
INSERT INTO consistent.master SELECT * FROM master_tmp;
-- ... and re-create any constraints.

Я подозреваю, что это будет быстрее, чем просто удаление и повторное создание только ограничений, потому что ОБНОВЛЕНИЕ будет иметь довольно случайные шаблоны записи, которые убьют ваше хранилище. Две массовые вставки, одна в незафиксированную таблицу, а другая в таблицу с протоколом WAL без ограничений, вероятно, будут быстрее.

Если у вас есть абсолютно обновленные резервные копии и вы не возражаете против восстановления базы данных из резервных копий, вы также можете перезапустить PostgreSQL с fsync=offпараметром и full_page_writes=off временно для этой массовой операции. Любая непредвиденная проблема, такая как потеря питания или сбой ОС, оставит вашу базу данных без возможности восстановления fsync=off.

POSTGreSQL, эквивалентный «no logging», - это использование незарегистрированных таблиц. Эти незагруженные таблицы усекаются, если БД нечисто выключается, пока они грязные. Использование незарегистрированных таблиц, по крайней мере, вдвое уменьшит нагрузку на запись и сократит количество запросов, поэтому они могут быть намного быстрее.

Как и в Oracle, может быть хорошей идеей удалить индекс, а затем заново создать его после большого пакетного обновления. Планировщик PostgreSQL не может понять, что происходит большое обновление, приостановить обновления индекса, а затем перестроить индекс в конце; даже если бы это было возможно, ему было бы очень трудно понять, в какой момент это стоит делать, особенно заранее.

Крейг Рингер
источник
Этот ответ очевиден благодаря большому количеству записей и ужасной скорости шифрования, а также медленной работе ноутбука. Я также хотел бы отметить, что наличие 8 индексов создает много дополнительных записей и отрицательно влияет на применимость обновлений строк в блоке HOT , поэтому удаление индексов и использование меньшего коэффициента заполнения в таблице может предотвратить тонну миграции строк
dbenhur
1
Хороший вызов для повышения шансов HOTS с помощью фактора заполнения - хотя с помощью циклов чтения-перезаписи блоков TrueCrypt в огромных блоках я не уверен, что это сильно поможет; Миграция строк может быть даже быстрее, потому что при увеличении таблицы, по крайней мере, выполняются линейные записи.
Крейг Рингер
Спустя 2,5 года я делаю что-то похожее, но за большим столом. Просто для уверенности, стоит ли удалять все индексы, даже если один столбец, который я обновляю, не проиндексирован?
Арен Камбре
1
@ArenCambre В таком случае ... ну, это сложно. Если большинство ваших обновлений будут соответствовать требованиям HOT, лучше оставить индексы на месте. Если нет, то вы, вероятно, захотите удалить и заново создать. Столбец не индексируется, но для того, чтобы можно было выполнить ГОРЯЧЕЕ обновление, на той же странице также должно быть свободное место, так что это немного зависит от того, сколько мертвого пространства есть в таблице. Если это в основном для записи, я бы сказал, отказаться от всех индексов. Если он обновлен, у него могут быть дыры, и вы можете быть в порядке. Инструменты как pageinspectи pg_freespacemapмогут помочь определить это.
Крейг Рингер
Благодарю. В данном случае это логический столбец, в котором уже есть запись в каждой строке. Я менял запись в некоторых строках. Я только подтвердил: обновление заняло всего 2 часа после сброса всех индексов. Предварительно я должен был остановить обновление через 18 часов, потому что это занимало слишком много времени. И это несмотря на то, что обновляемый столбец определенно не был проиндексирован.
Арен Камбре
2

Кто-то даст лучший ответ для Postgres, но вот несколько замечаний с точки зрения Oracle, которые могут применяться (и комментарии слишком длинны для поля комментариев).

Моя первая задача - попытаться обновить 2 миллиона строк за одну транзакцию. В Oracle вы будете записывать предшествующий образ каждого обновляемого блока, чтобы другие сеансы все еще имели согласованное чтение, не читая измененные блоки, и у вас была возможность отката. Это долгий откат. Обычно лучше делать транзакции небольшими порциями. Скажи 1000 записей одновременно.

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

Oracle позволяет «не регистрировать» подсказки для операторов, чтобы остановить ведение журнала. Это значительно ускоряет утверждения, но оставляет вашу базу данных в «неустранимой» ситуации. Таким образом, вы хотели бы сделать резервную копию до, и резервное копирование сразу же после этого. Я не знаю, есть ли у Postgres похожие варианты.

Гленн
источник
PostgreSQL не имеет проблем с длительным откатом, не существует. ROLBACK очень быстр в PostgreSQL, независимо от того, насколько велика ваша транзакция. Oracle! = PostgreSQL
Фрэнк Хейкенс,
@FrankHeikens Спасибо, это интересно. Я должен прочитать о том, как работает журналирование на Postgres. Чтобы заставить работать всю концепцию транзакций, необходимо как-то поддерживать две разные версии данных во время транзакции: изображение до и после, и это механизм, о котором я говорю. Так или иначе, я бы предположил, что существует порог, после которого ресурсы для обслуживания транзакции будут слишком дорогими.
Гленн
2
@Glenn Postgres хранит версию строки в самой таблице - см здесь для объяснения. Компромисс состоит в том, что вы получаете «мертвые» кортежи, которые очищаются асинхронно с помощью так называемого «вакуума» в postgres (Oracle не нуждается в вакууме, потому что у него никогда не бывает «мертвых» строк в самой таблице),
говорит Джек. попробуйте topanswers.xyz
Пожалуйста
@Glenn Каноническим документом для управления параллелизмом версий строк в PostgreSQL является postgresql.org/docs/current/static/mvcc-intro.html , который стоит прочитать. Смотрите также wiki.postgresql.org/wiki/MVCC . Обратите внимание, что MVCC с мертвыми строками и VACUUMявляется только половиной ответа; PostgreSQL также использует так называемый «журнал записи вперед» (фактически журнал) для обеспечения атомарных фиксаций и защиты от частичной записи и т. Д. См. Postgresql.org/docs/current/static/wal-intro.html
Крейг Рингер,