mysql2로 바꿨더니 평점이 갑자기 깨졌다. DECIMAL이 문자열로 오는 함정
드라이버를 mysql2로 바꾸자 평균 별점 화면에서 toFixed is not a function 에러가 났다. DECIMAL을 문자열로 돌려주는 이유와 decimalNumbers 옵션으로 고친 과정을 정리했다.
#MySQL #Node #트러블슈팅 #백엔드
커넥션 풀 쓰려고 드라이버를 mysql에서 mysql2로 바꿨다. 대부분 멀쩡했는데, 맛집 별점 화면을 열자마자 콘솔에 이게 터졌다. TypeError: avgRating.toFixed is not a function 분명 숫자여야 하는데 toFixed가 없단다. mysql일 땐 잘 되던 게 mysql2로 바꾸니까 깨진 거다. 드라이버만 바꿨지 쿼리도 화면 코드도 그대로라 범인 찾는 데 시간이 좀 걸렸다. 이 글에선 이 함정을 정리해본다. 증상 맛집마다 별점 평균을 AVG로 구해서 소수점 한 자리로 보여주고 있었다. 쿼리는 이랬다. SELECT fp.*, (SELECT AVG(star) FROM FavPlaceStars WHERE favplace_id = fp.id) AS avgRating FROM FavPlace fp 그리고 프론트는 이랬다. // 평균 별점을 4.5 이런 식으로 표시 span {avgRating.toFixed(1)} /span toFixed는 숫자에만 있는 메서드다. 즉 avgRating이 숫자가 아니라는 얘기였다. 원인은 DECIMAL을 문자열로 준다는 것 이유는 황당할 만큼 단순했다. mysql2는 DECIMAL 타입을 기본적으로 문자열로 반환한다. AVG(star)의 결과가 DECIMAL이라, 4.5라는 숫자가 아니라 "4.5"라는 문자열로 넘어온 거다. 문자열엔 toFixed가 없으니 당연히 깨진다. 구 mysql 드라이버는 이걸 숫자로 줬었는데, mysql2는 정밀도 손실을 막으려고 일부러 문자열로 준다. 자바스크립트 number는 64비트 부동소수점이라 소수점 자리가 많거나 큰 값이면 그대로 담을 수 없는데, DECIMAL은 그런 값을 정확히 저장하는 타입이다. 그걸 number로 바꾸는 순간 정확도가 깨질 수 있으니 문자열로 두고 판단을 사용자에게 맡기는 거다. 의도는 좋은데, 모르고 있으면 이렇게 당한다. 해결은 풀 설정 한 줄과 프론트 이중 방어 풀 만들 때 decimalNumbers: true 옵션을 주면 DECIMAL을 숫자로 받게 할 수 있다. const pool = mysql.createPool({ host: process.env.DB_HOST, // ... // mysql2는 DECIMAL을 기본적으로 문자열로 반환한다. // 숫자로 받아서 프론트의 .toFixed() 같은 게 안 깨지게 함. decimalNumbers: true, }); 별점 평균은 소수점 한두 자리면 충분해서 정밀도 걱정은 없었다. 돈이나 좌표처럼 자리수가 중요한 값이었으면 문자열로 받아서 따로 다뤘을 거다. 그리고 프론트에서도 혹시 모르니 Number()로 한 번 감싸서 이중 방어했다. 백엔드 설정이 어쩌다 바뀌어도 안 깨지게. span {Number(avgRating).toFixed(1)} /span 나중에 여행 플래너에서 좌표를 DECIMAL로 저장할 때도 같은 함정을 한 번 더 만날 뻔했다. 그때는 이 글이 기억나서 쿼리 결과를 Number()로 바꾸는 줄을 미리 넣어뒀다. 갈아타긴 했나 글에는 갈아탔다고 썼는데, 그 프로젝트 package.json을 지금 열어보면 이렇다. "dependencies": { "mysql": "^2.18.1", - 안 지웠다 "mysql2": "^3.15.3" } 새로 짠 코드는 mysql2를 쓰고, 예전 코드는 여전히 mysql을 부른다. 한 프로젝트 안에 드라이버가 두 개 돌고 있는 상태가 몇 달째다. 평점 깨진 것만 고치고 나머지는 안 옮겼다. 안 옮긴 쪽이 잘 돌고 있어서 손댈 이유를 못 찾았는데, 이러면 나중에 또 같은 함정을 밟을 것 같긴 하다. 드라이버가 두 개면 같은 컬럼이 어떤 라우트에선 숫자로, 어떤 라우트에선 문자열로 오는 상태가 되기 때문이다. mysql에서 mysql2로 옮길 계획이 있다면, 옮기기 전에 DECIMAL 컬럼을 쓰는 쿼리부터 찾아두길 추천한다.