Thiết kế độ dài VARCHAR cơ sở dữ liệu: Thực tiễn tốt nhất cho giới hạn ký tự
Chọn độ dài VARCHAR phù hợp cho cột cơ sở dữ liệu là quyết định thiết kế cơ bản ảnh hưởng đến hiệu quả lưu trữ, hiệu suất truy vấn và tính toàn vẹn dữ liệu, giống như thiết kế độ dài phản hồi API ảnh hưởng đến chất lượng tổng thể của hệ thống. Bài viết này trình bày các hướng dẫn thực tế cho các loại trường phổ biến trên các hệ thống cơ sở dữ liệu chính. Để tìm hiểu sâu hơn về thiết kế schema, hãy tham khảo sách thiết kế cơ sở dữ liệu.
Huyền thoại VARCHAR(255) - Tại sao 255 trở thành mặc định
Mặc định VARCHAR(255) phổ biến bắt nguồn từ giới hạn MySQL cũ. Trước MySQL 5.0, tiền tố độ dài VARCHAR được lưu trữ trong một byte duy nhất, giới hạn tối đa ở 255. MySQL 5.0 chuyển sang tiền tố độ dài 2 byte, cho phép lên đến 65.535 byte - nhưng quy ước "255" vẫn tồn tại như một thói quen lâu sau khi ràng buộc kỹ thuật đã biến mất.
Có một lý do khác khiến quy ước này tồn tại. Trong InnoDB của MySQL, các cột VARCHAR có độ dài khai báo 255 trở xuống sử dụng tiền tố độ dài 1 byte, trong khi các cột có độ dài 256 trở lên sử dụng tiền tố 2 byte. Điều này có nghĩa là có sự chênh lệch 1 byte chi phí trên mỗi hàng giữa VARCHAR(255) và VARCHAR(256). Với bảng có 1 triệu hàng, điều này chỉ tương đương 1 MB - nhưng nhận thức rằng "255 là hiệu quả" đã trở nên phổ biến.
Triển khai nội bộ VARCHAR trên các RDBMS
Ngay cả với cùng khai báo VARCHAR(100), định dạng lưu trữ nội bộ và hành vi cấp phát bộ nhớ khác nhau đáng kể giữa các hệ thống cơ sở dữ liệu. Không hiểu những khác biệt này có thể dẫn đến vấn đề hiệu suất và lưu trữ không mong đợi.
| RDBMS | Độ dài tối đa | Đơn vị | Lưu trữ nội bộ | Cấp phát bộ nhớ |
|---|---|---|---|---|
| MySQL 8.0 (InnoDB) | 65.535 byte (mỗi hàng) | Ký tự | Dữ liệu thực + tiền tố 1-2 byte. Dữ liệu vượt quá 768 byte tràn sang trang ngoài | Bảng tạm cấp phát độ dài khai báo × byte tối đa mỗi ký tự (×4 cho utf8mb4) |
| PostgreSQL | ~1 GB | Ký tự | Cấu trúc varlena. VARCHAR và TEXT sử dụng lưu trữ giống hệt nhau. TOAST tự động nén dữ liệu trên 2 KB | Chỉ độ dài dữ liệu thực. Độ dài khai báo hoạt động như ràng buộc kiểm tra |
| SQL Server | 8.000 byte | Ký tự | Lưu trữ trong hàng. VARCHAR(MAX) sử dụng lưu trữ LOB | Thực thi truy vấn dự trữ độ dài khai báo (Memory Grant) |
| Oracle | 4.000 byte (tiêu chuẩn) / 32.767 byte (mở rộng) | Byte hoặc Ký tự (điều khiển bởi NLS_LENGTH_SEMANTICS) | Lưu trữ trong hàng. Chế độ mở rộng sử dụng SecureFile LOB | PGA cấp phát độ dài khai báo |
| SQLite | Không giới hạn | — | Kiểu động. Khai báo VARCHAR bị bỏ qua; chỉ lưu trữ độ dài dữ liệu thực | Chỉ độ dài dữ liệu thực |
MySQL và SQL Server đáng được chú ý đặc biệt. Trong các hệ thống này, ngay cả khi cột VARCHAR(255) chỉ lưu trữ 10 ký tự, bảng tạm và thao tác sắp xếp vẫn cấp phát 255 × 4 = 1.020 byte bộ nhớ. Với các bảng có nhiều cột, việc cấp phát bộ nhớ quá mức này có thể làm giảm đáng kể hiệu suất truy vấn.
Tác động mã hóa độ dài biến đổi UTF-8 lên VARCHAR(255)
Ngay cả trong RDBMS chỉ định độ dài VARCHAR bằng ký tự, giới hạn byte nội bộ vẫn áp dụng. Hiểu sự khác biệt giữa ký tự và byte là điều cần thiết cho thiết kế schema đúng đắn. UTF-8 là mã hóa có độ dài biến đổi trong đó các loại ký tự khác nhau tiêu thụ số byte khác nhau.
| Loại ký tự | Byte UTF-8 | Ví dụ | Số ký tự tối đa trong VARCHAR(255) (tương đương byte) |
|---|---|---|---|
| Chữ cái và số ASCII | 1 byte | a, Z, 0, @ | 255 ký tự (255 byte) |
| Latin mở rộng / Cyrillic | 2 byte | é, ñ, Д | 255 ký tự (510 byte) |
| Ký tự CJK | 3 byte | 漢, あ, 한 | 255 ký tự (765 byte) |
| Emoji / ký hiệu đặc biệt | 4 byte | 😀, 🎉, 𠮷 | 255 ký tự (1.020 byte) |
Với utf8mb4 của MySQL, cột VARCHAR(255) có thể tiêu thụ tới 1.020 byte trong trường hợp xấu nhất. Giới hạn kích thước hàng của InnoDB là khoảng 8.126 byte (một nửa trang 16 KB trừ header), vì vậy chỉ 8 cột VARCHAR(255) đã có thể vượt quá giới hạn kích thước hàng. Sử dụng Bộ đếm ký tự để xác minh số byte khi thiết kế schema và ngăn chặn việc cắt ngắn dữ liệu không mong muốn.
VARCHAR vs TEXT - Thực tế hiệu suất và lập chỉ mục
Lời khuyên phổ biến "sử dụng TEXT cho chuỗi dài" đơn giản hóa quá mức tình huống. Sự đánh đổi giữa VARCHAR và TEXT khác nhau đáng kể tùy theo RDBMS.
| Khía cạnh | MySQL (InnoDB) | PostgreSQL | SQL Server |
|---|---|---|---|
| Khác biệt lưu trữ | VARCHAR được lưu trong hàng (tối đa 768 byte). TEXT hoạt động tương tự nhưng ở định dạng hàng COMPACT, chỉ 768 byte đầu tiên được giữ trong hàng | Không khác biệt. VARCHAR(n) và TEXT sử dụng cùng cấu trúc varlena | VARCHAR lưu trong hàng. TEXT (VARCHAR(MAX)) sử dụng lưu trữ LOB |
| Lập chỉ mục | VARCHAR: chỉ mục đầy đủ (tối đa 767 byte). TEXT: chỉ chỉ mục tiền tố | Cả hai đều có thể lập chỉ mục như nhau | VARCHAR: chỉ mục đầy đủ. TEXT: chỉ chỉ mục toàn văn |
| Sắp xếp / GROUP BY | VARCHAR: trong bộ nhớ. TEXT: có thể sử dụng bảng tạm trên đĩa | Không khác biệt | VARCHAR: trong bộ nhớ. TEXT: sử dụng tempdb |
| Giá trị mặc định | VARCHAR: hỗ trợ. TEXT: không hỗ trợ (hỗ trợ từ MySQL 8.0.13) | Cả hai đều hỗ trợ | Cả hai đều hỗ trợ |
Trong PostgreSQL, hầu như không có sự khác biệt giữa VARCHAR(n) và TEXT - tài liệu chính thức thậm chí khuyến nghị "sử dụng TEXT hoặc VARCHAR không ràng buộc trừ khi bạn có lý do cụ thể." Tuy nhiên trong MySQL, cột TEXT không thể có chỉ mục đầy đủ, vì vậy nên chọn VARCHAR cho các cột sẽ được tìm kiếm.
Cạm bẫy Emoji (4-Byte UTF-8) với VARCHAR
Ứng dụng hiện đại phải được thiết kế với giả định rằng đầu vào người dùng sẽ chứa emoji. Emoji tiêu thụ 4 byte trong UTF-8, nhưng vấn đề không chỉ dừng lại ở số byte.
- Bẫy utf8 vs utf8mb4 của MySQL:
utf8của MySQL (chính thức là utf8mb3) chỉ hỗ trợ tối đa 3 byte mỗi ký tự và không thể lưu trữ emoji 4 byte. Cố gắng INSERT emoji sẽ gây ra lỗiIncorrect string value. Việc chuyển sangutf8mb4là cần thiết, nhưng thay đổi bộ ký tự của bảng hiện có đòi hỏi xây dựng lại chỉ mục, có thể gây thời gian ngừng hoạt động cho các bảng lớn. - Vấn đề đếm emoji ghép: Emoji gia đình "👨👩👧👦" hiển thị như một ký tự đơn nhưng bên trong bao gồm 7 điểm mã Unicode (4 emoji người + 3 bộ nối ZWJ), tiêu thụ 25 byte trong UTF-8. Liệu nó có vừa trong
VARCHAR(10)hay không phụ thuộc vào cách RDBMS đếm "ký tự."CHAR_LENGTH()của MySQL đếm đây là 7, nên nó vừa trongVARCHAR(10), nhưng nếu không hiểu sự khác biệt giữa ký tự và byte, việc cắt ngắn không mong muốn có thể xảy ra. - Ngữ nghĩa byte của Oracle: Cài đặt mặc định của Oracle (
NLS_LENGTH_SEMANTICS=BYTE) có nghĩa làVARCHAR2(100)cho phép "100 byte." Một emoji đơn tiêu thụ 4 byte, nên văn bản chứa emoji có thể lưu trữ ít ký tự hơn nhiều so với dự kiến. Sử dụngVARCHAR2(100 CHAR)để chỉ định rõ ràng ngữ nghĩa ký tự, hoặc đặtNLS_LENGTH_SEMANTICS=CHARở cấp phiên.
VARCHAR vs. CHAR
Trước khi chọn loại chuỗi, hãy hiểu sự khác biệt cơ bản giữa VARCHAR và CHAR.
| Thuộc tính | CHAR(n) | VARCHAR(n) |
|---|---|---|
| Lưu trữ | Độ dài cố định (đệm bằng khoảng trắng) | Độ dài biến đổi (chỉ dữ liệu thực) |
| Sử dụng đĩa | Luôn n byte | Dữ liệu thực + 1-2 byte chi phí |
| Phù hợp nhất cho | Dữ liệu độ dài cố định (mã quốc gia, mã bưu chính) | Dữ liệu độ dài biến đổi (tên, email) |
| Tốc độ tìm kiếm | Nhanh hơn một chút nhờ độ dài cố định | Chi phí nhỏ từ độ dài biến đổi |
VARCHAR là lựa chọn đúng cho đại đa số trường hợp sử dụng. Chỉ dùng CHAR cho dữ liệu thực sự có độ dài cố định như mã quốc gia ISO (CHAR(2)) hoặc mã tiền tệ (CHAR(3)). Lưu ý rằng trong InnoDB của MySQL, cột CHAR cũng được lưu trữ dưới dạng độ dài biến đổi (khoảng trắng cuối bị loại bỏ), nên sự khác biệt lưu trữ là tối thiểu.
Lỗi thiết kế VARCHAR phổ biến và chi phí sửa chữa
- Mặc định mọi cột thành
VARCHAR(255): Mặc định lười biếng này làm yếu xác thực ở tầng ứng dụng và có nguy cơ lưu trữ dữ liệu dài bất ngờ. Trong InnoDB của MySQL, giới hạn kích thước hàng là khoảng 8.126 byte, nên chỉ 8 cột utf8mb4VARCHAR(255)đã có thể vượt quá giới hạn này. Sửa lỗi này đòi hỏi ALTER TABLE trên mọi cột - với bảng 100 GB, điều này có thể mất vài giờ khóa bảng. - Nhầm lẫn ký tự với byte:
VARCHAR(100)có nghĩa là "100 ký tự" trong MySQL, nhưng trong cấu hình mặc định của Oracle (NLS_LENGTH_SEMANTICS=BYTE) nó có nghĩa là "100 byte." Vì một ký tự CJK đơn tiêu thụ 3 byte trong UTF-8,VARCHAR2(100)của Oracle chỉ có thể lưu trữ khoảng 33 ký tự CJK. - Đặt VARCHAR quá ngắn: Thiết kế cột tên là
VARCHAR(20)với giả định "20 ký tự là đủ" thường thất bại khi gặp tên nước ngoài dài hoặc tên có tên đệm. Mở rộng độ dài VARCHAR sau đó qua ALTER TABLE có thể kích hoạt sao chép bảng nội bộ trong DDL trực tuyến của MySQL, gây ra hàng giờ ngừng hoạt động cho các bảng lớn. - Không khớp xác thực API: Nếu API cho phép 500 ký tự nhưng cột DB là
VARCHAR(200), thao tác INSERT sẽ hoặc cắt ngắn dữ liệu âm thầm hoặc ném lỗi (trong chế độ strict của MySQL). Luôn đồng bộ thiết kế độ dài phản hồi API với độ dài cột cơ sở dữ liệu.
Thay đổi độ dài VARCHAR khi migration - Rủi ro và quy trình an toàn
Thao tác ALTER TABLE để thay đổi độ dài VARCHAR trong production hoạt động rất khác nhau giữa các RDBMS. Hiểu cơ chế nội bộ là điều cần thiết để thực thi an toàn.
| RDBMS | Tăng độ dài (vd: 100→200) | Giảm độ dài (vd: 200→100) | Ghi chú |
|---|---|---|---|
| MySQL (InnoDB) | ≤255→≤255: chỉ metadata (tức thì). ≤255→≥256: cần xây dựng lại bảng | Cần xây dựng lại bảng. Lỗi nếu dữ liệu hiện có vượt quá độ dài mới | Sử dụng pt-online-schema-change hoặc gh-ost cho bảng lớn |
| PostgreSQL | Chỉ metadata (tức thì). Không cần khóa bảng | Cần xác thực dữ liệu. Lỗi khi vi phạm ràng buộc | Thay đổi độ dài VARCHAR luôn nhẹ trong PostgreSQL |
| SQL Server | Chỉ metadata (tức thì) | Xác thực dữ liệu rồi thay đổi metadata | VARCHAR→VARCHAR(MAX) cần xây dựng lại bảng |
| Oracle | Chỉ metadata (tức thì) | Xác thực dữ liệu rồi thay đổi metadata | Có thể thay đổi ngữ nghĩa BYTE→CHAR qua ALTER TABLE MODIFY |
Quy trình an toàn để thay đổi độ dài VARCHAR trong MySQL:
- Kiểm tra độ dài tối đa dữ liệu hiện có:
SELECT MAX(CHAR_LENGTH(column_name)) FROM table_name; - Xác định xem thay đổi có vượt qua ranh giới 255 byte hay không (vượt qua sẽ kích hoạt xây dựng lại bảng).
- Với bảng lớn (1 triệu+ hàng), sử dụng pt-online-schema-change hoặc gh-ost để thay đổi không ngừng hoạt động.
- Sau khi thay đổi, chạy
ANALYZE TABLEđể cập nhật thống kê trình tối ưu hóa.
Độ dài khuyến nghị theo loại trường
| Trường | VARCHAR khuyến nghị | Lý do |
|---|---|---|
| 254 | Tối đa theo RFC 5321 | |
| Tên đăng nhập | 50 | Ràng buộc hiển thị UI |
| Tên hiển thị | 100 | Hỗ trợ đa ngôn ngữ, bao gồm emoji |
| Tên (quốc tế) | 100 | Phù hợp với các nền văn hóa có tên dài và tên đệm |
| Số điện thoại | 20 | Định dạng E.164 tối đa 15 chữ số + tiền tố quốc gia + ký hiệu |
| URL | 2048 | Giới hạn thực tế của trình duyệt |
| Dòng địa chỉ | 200 | Định dạng địa chỉ quốc tế |
| Tên sản phẩm | 200 | Giới hạn trên phổ biến trong thương mại điện tử |
| Hash mật khẩu | VARCHAR(60) / CHAR(60) | Hash bcrypt cố định 60 ký tự. CHAR(60) là tối ưu |
| UUID | CHAR(36) / BINARY(16) | 36 ký tự có dấu gạch ngang. Lưu trữ nhị phân là 16 byte và hiệu quả hơn |
- Khi có đặc tả hoặc tiêu chuẩn (RFC, ISO, v.v.) định nghĩa giá trị tối đa, hãy khớp với nó.
- Khi không có đặc tả, thêm biên 20-50% vào độ dài dữ liệu tối đa quan sát được.
- Lập kế hoạch cho tăng trưởng tương lai nhưng tránh giá trị quá lớn. Trong MySQL, hãy lưu ý ranh giới 255 byte.
- Triển khai cùng xác thực giới hạn ký tự ở phía ứng dụng để ngăn chặn sự không khớp với DB.
- Với các cột quốc tế hóa, thiết kế dựa trên ngôn ngữ/văn hóa dài nhất, không chỉ ngôn ngữ chính của bạn.
Kết luận
Thiết kế độ dài VARCHAR đòi hỏi xem xét toàn diện về đặc tính dữ liệu, mã hóa và triển khai nội bộ RDBMS. Thay vì mặc định 255, hãy đặt độ dài với lý do rõ ràng để cải thiện hiệu quả lưu trữ, hiệu suất truy vấn và chất lượng dữ liệu. Trong MySQL, hãy đặc biệt chú ý đến ranh giới tiền tố 255 byte, cấp phát bộ nhớ bảng tạm và tác động kích thước chỉ mục. Để tìm hiểu toàn diện về tối ưu hóa SQL, hãy khám phá tài liệu tối ưu hiệu suất SQL. Sử dụng Bộ đếm ký tự để đo độ dài dữ liệu thực tế khi thiết kế schema của bạn.