1.8Kпросмотров
60.1%от подписчиков
11 декабря 2025 г.
Score: 1.9K
🤬 Чиним поврежденный уникальный индекс в PostgreSQL. Если вы увидели вот такую картину с первичными ключами в таблице, то это следствие повреждения уникального индекса: SELECT id FROM mytable ORDER BY id LIMIT 5; id ---- 1 2 3 3 4 (5 rows) Надо чинить! ⚠️ Перед дальнейшими операциями обязательно сделайте копию таблицы с повреждённым индексом, а лучше всей базы. Уникальный индекс невозможно создать (или пересоздать), пока в таблице есть дублирующиеся строки. Postgres просто откажется это делать, так как это нарушает уникальность. Поэтому мы должны удалить дублирующиеся строки из таблицы. Первое, что нам нужно сделать - отключить использование индексов: set enable_indexscan = 0; set enable_bitmapscan = 0; set enable_indexonlyscan = 0; Повреждённые индексы - основной способ, через который такие плохие строки попадают в базу данных, мы не можем им доверять. Теперь PostgreSQL будет обращаться напрямую к таблице, а не искать данные через индексы. Можем сначала все проверить на тестовой таблице: CREATE TABLE test_mytable AS SELECT FROM mytable WHERE id < 30; CREATE TABLE test_mytable_duperows (LIKE mytable); В нашем случае - на уменьшенной версии реальной таблицы. Поскольку мы знаем, что проблемные строки находятся при id = 3, мы создали новую таблицу, содержащую эти строки. Мы также создаём новую пустую таблицу test_mytable_duperows, которая будет хранить удаляемые дублирующиеся строки. Чтобы нам не мешали удалять строки ни какие триггеры, или внешние ключи нам нужно установить session_replication_role в значение replica - это расширенная (и опасная) команда, которая отключает все триггеры и правила. Кроме того, мы сделаем это как SET LOCAL вместо просто SET, что гарантирует возврат настройки в норму при следующем COMMIT или ROLLBACK. Для тестовой таблицы это не актуально, но на рабочей может пригодиться. BEGIN; SET LOCAL session_replication_role = 'replica'; WITH goodrows AS ( SELECT min(ctid) from TEST_mytable GROUP BY id) ,mydelete AS ( DELETE FROM TEST_mytable WHERE NOT EXISTS (SELECT 1 FROM goodrows WHERE min=ctid) RETURNING ) INSERT INTO TEST_mytable_duperows SELECT FROM mydelete; RESET session_replication_role; Здесь: SELECT min(ctid) FROM TEST_mytable GROUP BY id - каждая строка в Postgres имеет скрытый столбец ctid (column tuple identifier), который является указателем на фактическое физическое местоположение строки и поэтому всегда уникален. Если мы сгруппируем по столбцу id, мы можем получить один ctid для каждого уникального id, запросив «наименьший» ctid (не имеет значения, используем ли мы min() или max() или что-то ещё, главное - выбрать только один). Промежуточный результат сохраняем в список goodrows; DELETE FROM TEST_mytable WHERE NOT EXISTS (SELECT 1 FROM goodrows WHERE min=ctid) RETURNING - удаляем все дублирующиеся строки, которые НЕ входят в наш список goodrows. Таким образом, каждая из дублирующихся строк будет иметь разные ctid, и мы удалим все, кроме одной, для каждого id. Финальная часть RETURNING указывает DELETE вернуть полную информацию о каждой удалённой строке; INSERT INTO test_mytable_duperows_20250317 SELECT FROM mydelete; - наконец, мы берём вывод удаления и сохраняем его в нашей таблице. Таким образом, строки удаляются, но у нас остаётся полный список того, какие строки были удалены, для отладки и анализа. На этом этапе дублирующиеся строки должны быть удалены и находиться в таблице «duperows». Возможно, лучше проверить обе таблицы - и test_mytable, и таблицу с дубликатами, чтобы убедиться, что всё работало как ожидалось. Ну и наконец, перестраиваем индексы таблицы: REINDEX TABLE mytable; Если все ок, то можем провернуть на рабочей таблице. В конце надо не забыть вернуть Postgres к обычным настройкам планирования, установив enable_indexscan, enable_bitmapscan и enable_indexonlyscan обратно в 1. На этом все! До связи! #pgerror
1.8K
просмотров
3888
символов
Да
эмодзи
Нет
медиа

Другие посты @pg_guru

Все посты канала →
🤬 Чиним поврежденный уникальный индекс в PostgreSQL. Если в — @pg_guru | PostSniper