Tình huống thực tế
Tuần trước, tôi đang tích hợp một nhà cung cấp OAuth bên thứ ba vào một ứng dụng production. Mọi thứ đều vượt qua các bài kiểm tra local bằng cách sử dụng các mock token ngắn. Tuy nhiên, ngay khi chúng tôi triển khai thực tế, một người dùng với payload đặc biệt nặng đã gây ra lỗi crash. Các bản log hiển thị một dòng thông báo lỗi quen thuộc và đầy khó chịu:
ERROR: value too long for type character varying(255)
Lỗi này xảy ra khi bạn cố gắng chèn một chuỗi vượt quá giới hạn được thiết lập cứng của cột VARCHAR(n). Trong trường hợp của tôi, một URL theo dõi do hệ thống tạo ra đã đạt tới 312 ký tự, nhưng cơ sở dữ liệu lại bị giới hạn nghiêm ngặt ở mức 255.
Phân tích: Tại sao PostgreSQL lại nghiêm ngặt như vậy
PostgreSQL ưu tiên tính toàn vẹn của dữ liệu hơn là sự tiện lợi. Không giống như một số cơ sở dữ liệu cũ có thể âm thầm cắt bớt dữ liệu để cho vừa, Postgres từ chối đoán xem phần dữ liệu nào của bạn là có thể bỏ đi. Nếu bạn định nghĩa một cột là character varying(255), cơ sở dữ liệu đảm bảo rằng không có hàng nào vượt quá giới hạn đó.
Bạn có khả năng sẽ gặp phải nút thắt cổ chai này khi xử lý:
- Dữ liệu API bên ngoài: Webhooks hoặc các JSON blobs phình to khi nhà cung cấp thêm các tính năng mới.
- Văn bản dạng dài: Tiểu sử người dùng hoặc các bình luận mà giới hạn '255' chỉ là một con số dự đoán cảm tính.
- Các chuỗi kỹ thuật: Token đã mã hóa, session ID, hoặc các đường dẫn tệp tin phân cấp sâu.
- Di chuyển dữ liệu cũ (Legacy Migrations): Chuyển dữ liệu từ các hệ thống có tính năng 'tự động cắt bớt' sang môi trường Postgres nghiêm ngặt hơn.
Bước 1: Xác định nguyên nhân
Nếu log ứng dụng của bạn không rõ ràng, bạn cần kiểm tra trực tiếp cấu trúc bảng. Chạy lệnh này trong terminal psql hoặc bất kỳ GUI cơ sở dữ liệu nào như DBeaver:
\d table_name;
Hãy tìm cột Type. Nếu bạn thấy character varying(255), đó chính là nút thắt cổ chai của bạn. Để có cái nhìn chi tiết hơn trên toàn bộ cơ sở dữ liệu, bạn có thể truy vấn information schema:
SELECT column_name, character_maximum_length
FROM information_schema.columns
WHERE table_name = 'your_table_name';
Bước 2: Cách khắc phục nhanh (Tăng giới hạn)
Nếu bạn biết chính xác mình cần bao nhiêu dung lượng—ví dụ: 500 ký tự thay vì 255—bạn chỉ cần nâng giới hạn lên. Một trong những điểm mạnh của PostgreSQL là việc tăng độ dài của cột VARCHAR thường chỉ là một thay đổi về metadata. Nó không yêu cầu ghi lại toàn bộ bảng một cách chậm chạp.
ALTER TABLE users
ALTER COLUMN email TYPE varchar(500);
Lưu ý: Mặc dù thao tác này nhanh, nó vẫn yêu cầu một khóa ACCESS EXCLUSIVE. Trên một bảng có hàng triệu dòng, khóa này chỉ kéo dài vài mili giây, nhưng nó sẽ tạm dừng các truy vấn khác đang gửi đến trong giây lát.
Bước 3: Cách làm tốt nhất (Chuyển sang TEXT)
Đừng mải mê chạy theo các giới hạn nữa. Nếu bạn thấy mình liên tục phải nâng từ 255 lên 500, rồi lên 1000, bạn nên chuyển sang TEXT. Trong PostgreSQL, VARCHAR(n), VARCHAR, và TEXT sử dụng cùng một định dạng lưu trữ bên dưới. Không có bất kỳ sự giảm sút hiệu năng nào khi sử dụng TEXT.
Hiện tại tôi sử dụng TEXT cho hầu hết mọi thứ không phải là mã có độ dài cố định. Nó hỗ trợ các chuỗi có kích thước lên đến 1GB.
ALTER TABLE users
ALTER COLUMN bio TYPE TEXT;
Bằng cách chuyển sang TEXT, bạn chuyển việc kiểm tra tính hợp lệ (validation) sang mã nguồn ứng dụng. Việc này dễ cập nhật hơn nhiều so với schema cơ sở dữ liệu và ngăn chặn cơ sở dữ liệu trở thành điểm gây lỗi khi lưu lượng truy cập tăng đột biến.
Bước 4: Bản vá khẩn cấp (Cắt bớt dữ liệu)
Đôi khi bạn không thể chạy migration ngay lập tức vì môi trường production đang bị đóng băng. Trong những trường hợp đó, bạn phải cắt bớt dữ liệu trước khi nó được gửi đến cơ sở dữ liệu. Việc này ngăn chặn lỗi 500 nhưng dẫn đến mất dữ liệu, vì vậy hãy sử dụng cẩn thận.
Ví dụ trong Python:
# Cắt bớt còn 255 ký tự để thỏa mãn ràng buộc của DB
safe_description = raw_input[:255]
cursor.execute("INSERT INTO items (description) VALUES (%s)", (safe_description,))
Xác minh: Xác nhận việc khắc phục
Sau khi chạy lệnh ALTER TABLE, hãy kiểm tra lại schema để đảm bảo thay đổi đã được áp dụng:
\d users;
Cột bây giờ sẽ hiển thị là text hoặc giới hạn character varying(500) mới của bạn. Cuối cùng, hãy kiểm tra với một chuỗi thực tế từng gây lỗi trước đó:
-- Lệnh này bây giờ sẽ thành công
INSERT INTO users (email)
VALUES ('test-' || repeat('a', 300) || '@example.com');
Danh sách tổng kết
- Các giới hạn cụ thể: Sử dụng
ALTER COLUMN TYPE varchar(new_size)cho những điều chỉnh nhỏ. - Đảm bảo cho tương lai: Sử dụng
TEXTđể không bao giờ phải đối mặt với lỗi này nữa. - Hiệu năng: Đừng lo lắng—
TEXTcũng nhanh tương đươngVARCHARtrong Postgres. - Trải nghiệm người dùng: Luôn kiểm tra độ dài chuỗi ở frontend hoặc API để hiển thị các lỗi hữu ích thay vì để cơ sở dữ liệu bị crash trực tiếp.

