TL;DR: Cách khắc phục nhanh
Đừng hoảng loạn. Nếu các truy vấn của bạn bị lỗi TOAST, có thể bạn đang có một con trỏ bị hỏng trong bộ nhớ. Cách nhanh nhất để ổn định lại là tìm hàng bị lỗi và xóa nó hoặc gán giá trị NULL cho cột bị hỏng. Trước khi xóa bất cứ thứ gì, hãy thử reindex—đây là cách duy nhất để không làm mất dữ liệu.
-- 1. Thử cách an toàn trước: reindex bảng TOAST
REINDEX TABLE pg_toast.pg_toast_67890;
-- 2. Nếu lỗi vẫn còn, bạn cần tìm ID bị hỏng
-- (Xem phần 'Tìm kim đáy bể' bên dưới để lấy script)
Tại sao lỗi này xảy ra
PostgreSQL sử dụng một cơ chế gọi là TOAST để xử lý các trường dữ liệu quá lớn. Khi một hàng vượt quá ngưỡng trang 2KB mặc định, Postgres sẽ chuyển các giá trị lớn như JSONB hoặc TEXT sang một bảng phụ. Nó để lại một 'con trỏ' nhỏ trong bảng chính để biết nơi tìm kiếm sau này.
Lỗi missing chunk number 0 về bản chất là một liên kết bị hỏng. Bảng chính của bạn báo rằng 'dữ liệu ở đằng kia,' nhưng khi Postgres kiểm tra, dữ liệu đã mất hoặc không thể đọc được. Trong 90% trường hợp, điều này xuất phát từ việc khởi động lại đột ngột, lỗi ổ đĩa hoặc lỗi hệ thống tệp xảy ra trong quá trình ghi dữ liệu.
Bước 1: Xác định bảng bị lỗi
Thông báo lỗi cung cấp cho bạn một OID khó hiểu như pg_toast_67890. Bạn cần biết OID này thực sự thuộc về bảng ứng dụng nào trước khi có thể bắt đầu khắc phục.
SELECT
c.relname AS main_table
FROM
pg_class c
JOIN
pg_class toast_c ON c.reltoastrelid = toast_c.oid
WHERE
toast_c.relname = 'pg_toast_67890';
Bước 2: Thử Reindex (Lựa chọn an toàn)
Đôi khi bản thân dữ liệu vẫn hoàn toàn tốt, nhưng chỉ mục (index) được dùng để tìm các đoạn dữ liệu đó đã bị xáo trộn. Reindexing là bước đầu tiên tốt nhất vì nó không gây mất dữ liệu.
REINDEX TABLE pg_toast.pg_toast_67890;
Chạy lại truy vấn bị lỗi. Nếu nó hoạt động, bạn đã xong. Nếu vẫn bị lỗi, tình trạng hỏng dữ liệu nghiêm trọng hơn và bạn cần can thiệp sâu hơn.
Bước 3: Tìm kim đáy bể
Một lệnh SELECT * thông thường sẽ bị sập ngay khi chạm vào hàng bị lỗi. Để tìm chính xác thủ phạm, bạn cần một script kiểm tra từng hàng một. Sử dụng đoạn mã này để xác định các Primary Key gây ra lỗi.
DO $$
DECLARE
rec record;
bad_count int := 0;
BEGIN
-- Thay thế 'my_table' và 'id' bằng tên thực tế của bạn
FOR rec IN SELECT id FROM my_table LOOP
BEGIN
-- Thay thế 'large_column' bằng cột TEXT/JSONB/BYTEA của bạn
PERFORM (SELECT large_column FROM my_table WHERE id = rec.id);
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Tìm thấy hàng bị lỗi tại ID: %', rec.id;
bad_count := bad_count + 1;
END;
END LOOP;
RAISE NOTICE 'Quét hoàn tất. Tổng số hàng bị lỗi: %', bad_count;
END;
$$;
Bước 4: Sửa chữa hư hại
Khi đã có các ID bị lỗi, bạn phải quyết định xem mình có thể chấp nhận mất bao nhiêu dữ liệu. Thông thường bạn có hai lựa chọn:
Tùy chọn A: Gán giá trị NULL cho cột
Sử dụng cách này nếu bạn muốn giữ lại hàng nhưng có thể sống thiếu giá trị của trường dữ liệu lớn đó. Nó sẽ thay thế con trỏ bị hỏng bằng giá trị NULL.
UPDATE my_table
SET large_column = NULL
WHERE id = 'THE_BAD_ID';
Tùy chọn B: Xóa hàng
Nếu hàng đó trở nên vô dụng nếu không có dữ liệu TOAST, hãy xóa toàn bộ bản ghi.
DELETE FROM my_table WHERE id = 'THE_BAD_ID';
Bước 5: Dọn dẹp tàn dư
Đã sửa xong các hàng? Tuyệt vời. Bây giờ bạn cần dọn dẹp các đoạn dữ liệu mồ côi vẫn còn nằm trong bảng TOAST để ngăn chặn lỗi phát sinh trong quá trình bảo trì định kỳ sau này.
VACUUM ANALYZE my_table;
Xác minh việc khắc phục bằng cách chạy lệnh đếm (count) trên cột đó. Nếu nó trả về một con số thay vì báo lỗi, cơ sở dữ liệu của bạn đã khỏe mạnh trở lại.
Làm thế nào để ngăn chặn lỗi lặp lại
Phần mềm không thể ngăn ổ cứng bị hỏng, nhưng bạn có thể phát hiện sớm. Nếu bạn đang thiết lập một cơ sở dữ liệu mới, hãy luôn bật data_checksums. Điều này không ngăn chặn việc hỏng dữ liệu, nhưng nó sẽ cảnh báo ngay khi có một bit bị thay đổi trên đĩa thay vì đợi đến khi truy vấn bị lỗi. Ngoài ra, hãy đảm bảo bạn đang chạy sao lưu logic với pg_dump; chúng đóng vai trò như một 'kiểm tra tính toàn vẹn' ngẫu nhiên vì chúng sẽ thất bại nếu gặp phải dữ liệu bị hỏng.

