문제
utf8mb4을 사용하는 MySQL에서 한 테이블에 자료형이 varchar(1)인 'name' 컬럼이 있다.
그 테이블에 다음과 같은 쿼리를 실행하면 결과는 어떻게 될까?
insert into test(name) values('✅');
insert into test(name) values('🇰🇷');
정답
첫번째 쿼리는 성공하고, 두번째 쿼리는 "Data too long for column name 'name'"으로 실패한다.
같은 이모티콘이고 varchar 자료형을 사용했음에도 왜 다른 길이로 인식된 걸까?
Unicode (유니코드)
유니코드(The Unicode Standard)는 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준이다. 한글, 한자, 영어 등의 언어부터 이모티콘까지 다양한 문자들이 유니코드로 표시된다. 예로 "✅" 이모티콘은 유니코드 "U+2705"이다.
이러한 유니코드를 컴퓨터가 이해할 수 있게 인코딩하는 방식 중 하나가 바로 널리 쓰이는 UTF-8 이다.
Code Point
그리고 유니코드에서 하나의 의미를 가지는 연속된 bit의 모음을 코드 포인트라고 한다. 하나의 코드 포인트는 인코딩 방식에 따라 1~4byte의 크기를 가진다.
예시로 "✅" 이모티콘은 유니코드 "U+2705" 이고, 이를 2진수로 나타내면 "1010 1001 0001(2)" 다. 그리고 해당 이진수를 UTF-8로 인코딩하면 3byte의 크기를 가진다.
final byte[] bytes = "✅".getBytes(StandardCharsets.UTF_8);
System.out.println(bytes.length); // 3
그리고 MySQL의 char_length() 메서드는 이 코드 포인트를 기준으로 글자 수를 센다.
https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_char-length
CHAR_LENGTH(str):
Returns the length of the string str, measured in code points. A multibyte character counts as a single code point.
이는 MySQL 만이 아니다. 유니코드를 지원하는 자바나 파이썬 또한 코드 포인트를 기준으로 글자 수를 계산한다.
유니코드를 인식하지 못하는 언어에서는 다른 기준으로 글자 수를 계산한다.
그 중 C에서는 byte 단위로 글자 수를 계산하므로 "✅"의 글자 수는 3이 된다.
#include <stdio.h>
int main()
{
char c[] = "✅";
int len = 0;
while (c[len] != '\0') {
len++;
}
printf("%d", len); // 3
return 0;
}
하나의 문자 != 하나의 Code Point
한 코드 포인트는 하나의 문자가 아닌 하나의 '의미'를 가리킨다. 따라서 하나의 문자는 한 개의 코드 포인트를 필수적으로 가지며, 필요에 따라 추가 코드 포인트를 포함한다.
"❣️" 이모티콘의 코드 포인트 개수를 세면 2개로 나온다.
System.out.println("❣️".length()); // 2
그리고 해당 이모티콘의 코드 포인트는 "U+2763, U+FE0F" 이다.
유니코드 표에서 찾아보면 U+2763은 "❣"이고 U+FE0F는 "Variation Selectors"이다. 즉 U+2763 이모티콘을 변형한 형태 중 하나가 예시인 것이다.
이런 식으로 코드 포인트 여러 개가 하나의 문자가 될 수 있다.
"#️⃣" 이모티콘은 "U+0023, U+FE0F, U+20E3"의 코드 포인트를 가지고 있으며 이는 각각 "#", "Variation Selectors", "Combining Enclosing Keycap" 이다.
문제에서 여러 글자로 인식 된 "🇰🇷" 이모티콘 역시 여러 개의 코드 포인트를 가진다. 따라서 이모티콘이나 특수 문자의 입력을 허용한다면 글자 수 제한을 넉넉하게 해야한다.
사담
운영하는 서비스에서 사용자가 이름에 이모티콘을 넣다가 에러가 발생한 덕분에 해당 내용에 대해 자세히 알아보게 되었다.
이 짤이 생각나는 에러였다. 끝
참고
- https://meetup.nhncloud.com/posts/317