Недавно у одного из клиентов развалилась часть таблицы в «Постгресе» — в логах видно, что при чтении не совпадают контрольные суммы. Ничего особо нового — за последние несколько лет встречал эту проблему несколько раз у разных клиентов и даже описал алгоритм восстановления базы из разных источников у себя на сайте.
Там у меня в заметке есть хранимая функция, которая сканирует таблицу и сообщает какие именно строки требуется восстановить. Но в этот раз она мне, как ни странно, не помогла — завершилась, не выдав ничего. Раньше она осечек не давала и я, не поверив себе, даже запустил её дважды.
Долго разбирался в чём дело, разобрался. Принцип работы моей функции такой — она вынимает каждую строку, если получает ошибку, то сообщает об этом. Но оказалось, что если какие-либо разрушенные данные лежат в «тостах» (это механизм автоматического сжатия и выноса больших значений полей в «Постгресе»), то этого недостаточно. Видимо «Постгрес» в реальности не достаёт эти данные, пока они не понадобятся для чего-либо и поэтому ошибки не возникает.
В итоге я переделал хранимку — передаю всё строку целиком в функцию ROW_TO_JSON, чтобы прочитались и значения из «тостов». Это помогло, функция стала работать как и задумывалось.
CREATE OR REPLACE FUNCTION check_table(table_name TEXT)
RETURNS void AS $$
DECLARE
rec RECORD;
row_data RECORD;
BEGIN
FOR rec IN EXECUTE format('SELECT ctid::text AS ctid_str, id FROM %I', table_name)
LOOP
BEGIN
EXECUTE format('SELECT * FROM %I WHERE ctid = %L::tid', table_name, rec.ctid_str)
INTO STRICT row_data;
row_data := ROW(ROW_TO_JSON(row_data)); /* to read the toasts */
EXCEPTION WHEN others THEN
RAISE WARNING 'CTID: %, ID: %, Error: %', rec.ctid_str, rec.id, SQLERRM;
END;
END LOOP;
END;
$$ LANGUAGE plpgsql;
Вместо `digital_sign` должно быть `table_name` ?
Да, всё верно, спасибо!