Vài điểm nhỏ về UTF-8 cho cơ sở dữ liệu MySQL

Khi làm web bằng PHP và MySQL thì vấn đề xử lý tiếng Việt rất được hay gặp. Đôi khi nó gây rắc rối cho người làm web ở các thao tác như: sao lưu và phục hồi cơ sở dữ liệu, tạo table mới, lưu trữ và lấy dữ liệu từ cơ sở dữ liệu và hiển thị đúng, ... Bản thân tôi cũng đã gặp vài lần rắc rối với tiếng Việt trong việc xử lý dữ liệu, nên viết bài này tổng hợp 1 số cái đã trải qua để ghi nhớ cho sau này.

Tìm hiểu về charset và collation


Chúng ta tạm hiểu charset (viết đầy đủ là character set) là tập hợp các ký tự và mã hóa tương ứng của chúng, còn collation là quy tắc để so sánh các ký tự nằm trong 1 charset. Trang chủ của MySQL có ví dụ khá rõ ràng để giải thích 2 khái niệm này.

Thông thường (ở đây chúng ta chỉ nói đến MySQL) thì đối với mỗi charset có 1 collation mặc định đi kèm theo nó, VD latin1 <=> latin1_swedish_ci, utf8 <=> utf8_general_ci, ...

Các tham số của charset và collation


Đối với MySQL, thì charset và collation hiện diện ở những vị trí sau:

- Phía client: dữ liệu của phía client trước tiên cần phải mã hóa theo 1 charset nào đó, thông thường chúng ta dùng utf-8. Giá trị của charset này được MySQL lưu giữ trong tham số hệ thống character_set_client

- Phía server: charset và collation được dùng để mã hóa dữ liệu lưu trong hệ thống, các tham số tương ứng là character_set_servercollation_server. Ngoài ra, khi server trả dữ liệu trở lại về cho client thì chúng cũng cần phải có charset tương ứng, và tham số ứng với nó là character_set_results

- Hơn thế nữa, nằm trung gian giữa client và server là 1 kết nối (connection) được tạo ra khi client gửi yêu cầu tới server (hoặc server trả kết quả về cho client). Connection này cũng yêu cầu phải có charset và collation, chúng được lưu trong các tham số hệ thống character_set_connectioncollation_connection

Như vậy, tổng hợp lại có cả thảy 6 tham số lưu giữ những thuộc tính về charset và collation:

character_set_client
character_set_server
collation_server
character_set_results
character_set_connection
collation_connection

Từ đây chúng ta suy ra để server và client "có tiếng nói chung", VD như chúng ta muốn ghi vào table dữ liệu ở đúng dạng utf8 mà chúng ta có, thì các tham số trên phải được thiết lập cùng 1 nhóm với nhau utf8utf8_general_ci.

Cách thiết lập charset và collation


Đối với người quản trị server, có quyền thao tác trên dòng lệnh với mysql thì có thể thiết lập chúng bằng câu lệnh sau:

SET character_set_client = utf8;
SET character_set_server = utf8;
SET collation_server = utf8_general_ci;
...
(các lệnh sau tương tự)

(ở đây tôi lấy VD là utf8, đối với các charset và collation khác làm tương tự)

Tuy vậy, như thế khá dài dòng, MySQL có cung cấp cho chúng ta câu lệnh ngắn hơn như sau:

SET NAMES utf8 [COLLATE utf8_general_ci]

Phần trong dấu ngoặc vuông [] là không bắt buộc, nghĩa là nếu không có phần đó, collation mặc định tương ứng với charset đó sẽ được dùng. Chỗ này lưu ý là với mỗi charset có thể có nhiều collation.

Câu lệnh trên không tương ứng với hoàn toàn 6 lệnh ban đầu, mà nó chỉ tương đương với 3 lệnh sau:

SET character_set_client = utf8;
SET character_set_results = utf8;
SET character_set_connection = utf8;

Ở đây cũng lưu ý là các collation sẽ được thiết lập (mặc định) tương ứng với các charset đó. Tuy vậy, các bạn có thể thấy là vẫn thiếu lệnh thiết lập cho character_set_server, tôi cũng không rõ tại sao :(.

Ngoài ra, MySQL còn cung cấp 1 lệnh rút ngắn khác, tương tư như SET NAMES ở trên, đó là:

SET CHARACTER SET utf8;

Lệnh này cơ bản giống lệnh trên, tuy vậy, nó thiết lập charset và collation của connection (2 tham số character_set_connection, collation_connection) giống với charset và collation của database (2 tham số character_set_database, collation_database), nó tương đương với:

SET character_set_client = utf8;
SET character_set_results = utf8;
SET collation_connection = @@collation_database;

Đối với lập trình viên, khi bạn không có quyền thao tác trên dòng lệnh của mysql, thì có thể dùng câu lệnh PHP sau:

mysql_query('SET NAMES utf8 COLLATE utf8_general_ci');
hoặc
mysql_query('SET CHARACTER SET utf8');

Khi tôi thử nghiệm dùng các lệnh trên để thiết lập cho các kết nối tới cơ sở dữ liệu thì lệnh SET NAMES hoạt động tốt hơn lệnh SET CHARACTER SET. Có thể là do MySQL để kiểu charset và collation mặc định cho database là latin1, mà chúng ta thì cần utf8.

Như thế, sau khi chạy lệnh SET NAMES (thường thì chạy theo cách 2 trong PHP) thì bạn có thể yên tâm tạo các truy vấn tới CSDL. Dữ liệu được ghi vào CSDL lúc này bạn có thể đọc được tiếng Việt luôn khi xem bằng PHPMyAdmin.

Những gì trình bày ở trên nên được áp dụng khi bạn tự viết 1 ứng dụng web mới. Đối với các ứng dụng đã có thì cần lưu ý xem nó có ảnh hưởng tới những gì đã có hay không (VD trong trường hợp dữ liệu của bạn lưu trữ không phải với collation utf8_general_ci chẳng hạn).

Một vài rắc rối khác


Không phải tất cả đều đúng cho mọi phiên bản của MySQL. Những điều trên được áp dụng cho MySQL version 5.0 trở lên. Từ bản 5.0, MySQL đã hỗ trợ tốt hơn cho đa ngôn ngữ. Tuy vậy, nếu bạn đã từng dùng MySQL 4.x thì vấn đề trở nên rắc rối hơn hẳn.

Tôi cũng từng gặp vấn đề lỗi hiển thị tiếng Việt khi sao lưu và phục hồi bằng PHPMyAdmin giữa 1 host dùng MySQL 4.x và 1 host dùng MySQL 5.0. Lúc đó tôi phải làm thủ công, mở file CSDL đó ra, sửa tất cả các câu lệnh tạo bảng có CHARSET=latin1 thành CHARSET=utf8.

Tuy vậy, vấn đề sao lưu, phục hồi CSDL phụ thuộc vào charset và collation của nhiều thứ (ngoài những thứ trên), có thể kể ra đây gồm:

  • charset, collation của database (và các table) ở host cũ
  • charset của file SQL được sao lưu
  • charset, collation của database ở host mới

Những thứ này liên quan trực tiếp đến database nhiều hơn (những thứ ở trên liên quan đến việc thao tác qua lại giữa ứng dụng web và MySQL). Kinh nghiệm của tôi cho thấy là để sao lưu và phục hồi được đúng thì nên thiết lập mọi tham số ở host mới giống hệt như host cũ. Ngoài ra thì với MySQL 4.x thì nên chuyển tất cả về latin1 (do nó chưa hỗ trợ tốt cho đa ngôn ngữ), còn với MySQL 5.x thì chuyển tất cả qua utf8.

Phần về sao lưu và phục hồi này tôi cũng chưa có nhiều kinh nghiệm lắm, nên để mở vậy, sau này khá hơn thì sẽ viết tiếp :)

Tham khảo: MySQL Documentation