Câu hỏi này xuất hiện trong mọi lần review schema: dùng text hay varchar(255)? Tôi nạp một triệu chuỗi 10 ký tự vào bốn kiểu rồi đo.

Bốn kiểu chuỗi: kích thước, ngữ nghĩa, và chi phí đổi kiểu

Ba kiểu đầu giống nhau tới từng byte

Kiểu Một giá trị Cả bảng Byte/dòng Lọc một dòng
text 14 byte 42 MB 44 14 ms
varchar(20) 14 byte 42 MB 44 14 ms
varchar 14 byte 42 MB 44 14 ms
char(20) 24 byte 50 MB 52 15 ms

text, varcharvarchar(n) không khác nhau ở bất cứ con số nào. Trong PostgreSQL chúng dùng chung một biểu diễn lưu trữ; varchar(n) chỉ thêm một ràng buộc kiểm tra độ dài.

Và ràng buộc đó gần như miễn phí. Chèn 500.000 dòng:

Hai lần chạy
text 0,32 s / 0,33 s
varchar(50) 0,30 s / 0,30 s

Không đo được chênh lệch. Nên lập luận "dùng text cho nhanh" không có cơ sở, mà lập luận "varchar(n) tốn thêm để kiểm tra" cũng không.

char(20) thì tốn thêm 19% dung lượng vì nó đệm khoảng trắng cho đủ 20 ký tự — 24 byte cho một chuỗi 10 ký tự.

Cái làm char(n) nguy hiểm không phải kích thước

SELECT 'ab   '::char(5) = 'ab'::char(5);   -- true
SELECT 'ab   '::text    = 'ab'::text;      -- false

char(n) bỏ qua khoảng trắng cuối khi so sánh. Đó là quy định của chuẩn SQL, không phải lỗi PostgreSQL. Nhưng nó nghĩa là:

SELECT length('ab'::char(5));        -- 2
SELECT octet_length('ab'::char(5));  -- 5

Chuỗi dài 2 theo length nhưng chiếm 5 byte theo octet_length. Hai hàm nói hai chuyện khác nhau về cùng một giá trị.

Hệ quả thực dụng: đổi một cột từ char(n) sang text làm đổi kết quả truy vấn, không chỉ đổi kích thước. Những dòng trước đây khớp nhau vì hệ thống bỏ qua khoảng trắng cuối, sau khi đổi sẽ không khớp nữa. Nếu bạn có cột char(n) chứa mã sản phẩm hay mã vùng, kiểm tra kỹ trước khi đổi.

Và ngược lại, dữ liệu nhập từ hệ thống cũ có khoảng trắng thừa sẽ trông như sạch trong char(n) rồi lộ ra khi bạn chuyển sang text.

Đổi kiểu về sau: cái nào khoá bảng

filenode đổi nghĩa là PostgreSQL đã viết lại toàn bộ bảng — và trong suốt thời gian đó bảng bị khoá ở mức ACCESS EXCLUSIVE, không ai đọc được.

Đổi Thời gian Viết lại?
varchar(20)varchar(30) 0,07 s không
varchar(30)text 0,07 s không
textvarchar(15) 0,39 s có, cả bảng

Nới rộng giới hạn thì miễn phí. Từ PostgreSQL 9.2, tăng n của varchar(n), hoặc bỏ giới hạn để thành text, chỉ sửa siêu dữ liệu — dữ liệu trên đĩa không đụng tới.

Thu hẹp thì phải viết lại, vì PostgreSQL cần kiểm mọi dòng. Trên bảng một triệu dòng của tôi là 0,39 giây; trên bảng trăm triệu dòng đó là hàng phút khoá bảng trong giờ làm việc.

Đây là lập luận thực tế nhất cho text: nó là hướng rộng nhất, nên bạn không bao giờ phải đi ngược.

Vậy chọn cái nào

Dùng text theo mặc định. Không tốn hơn, không chậm hơn, và không bao giờ bắt bạn viết lại bảng để nới giới hạn.

Dùng varchar(n) khi n là một quy tắc nghiệp vụ thật, ví dụ mã bưu chính đúng 5 ký tự, mã tiền tệ ISO đúng 3. Khi đó ràng buộc đó có ý nghĩa và bạn muốn CSDL bắt lỗi giúp.

Đừng dùng char(n). Nó tốn thêm chỗ, và ngữ nghĩa so sánh khác biệt của nó là loại bất ngờ xuất hiện sau nhiều tháng. Ngay cả với dữ liệu thật sự cố định độ dài, text với một CHECK rõ ràng hơn:

ma_tien_te text CHECK (length(ma_tien_te) = 3)

Một lưu ý về con số varchar(255) mà mọi người hay gõ theo phản xạ: nó đến từ MySQL, nơi 255 là ngưỡng để độ dài vừa trong một byte. PostgreSQL không có ngưỡng đó — 255 không có ý nghĩa gì đặc biệt ở đây.

Thử ba mươi giây

Tìm mọi cột char(n) trong CSDL của bạn:

SELECT table_name, column_name, character_maximum_length
FROM information_schema.columns
WHERE data_type = 'character'
ORDER BY table_name;

Mỗi dòng in ra là một cột đang đệm khoảng trắng và đang so sánh theo quy tắc khác với phần còn lại của schema.

Và kiểm xem đổi kiểu có viết lại bảng không, trước khi chạy trên máy thật:

SELECT pg_relation_filenode('ten_bang');
ALTER TABLE ten_bang ALTER COLUMN cot TYPE text;
SELECT pg_relation_filenode('ten_bang');

Hai con số giống nhau là an toàn chạy bất cứ lúc nào. Khác nhau nghĩa là bảng vừa bị viết lại — hãy hẹn giờ bảo trì.

Phần sau đo numericfloat: sai số của từng kiểu, và cái giá phải trả cho độ chính xác.