본문 바로가기

MySQL 8.4 실행 환경

Windows 서비스, Homebrew, Docker 가운데 하나로 MySQL 8.4 서버를 준비하고, 설치 화면 대신 서버의 버전과 엔진, sql_mode, 시간대, 문자 집합을 SQL로 확인합니다. 서버와 데이터베이스, 세션의 설정이 미치는 범위를 구별하고, 어느 환경에서든 같은 결과를 얻도록 환경 지문을 남기는 방법을 다룹니다.

운영체제마다 설치 화면은 금방 바뀌고, GUI 도구에서 연결에 성공했다는 표시가 떠도 서버의 버전이나 세션의 설정은 서로 다를 수 있습니다. 이 교재는 MySQL 8.4 LTS와 InnoDB를 기준으로 하며, 실행 환경이 준비되었는지를 화면 캡처가 아니라 SQL의 결과로 확인합니다. 설치 방법이 달라도 같은 진단 결과에 이르면 준비가 끝난 것입니다.

이 글에서는 테이블을 만들지 않고 서버와 연결만 확인합니다. 환경을 준비한 뒤에는 파일 저장과 DBMS의 CREATE DATABASE IF NOT EXISTS부터 실행해 데이터베이스와 회원 테이블을 만듭니다. Docker를 쓰면 빈 board_lab을 미리 만들어 둘 수도 있습니다.

서버를 준비하는 세 가지 방법

진단 SQL을 실행하려면 MySQL 서버와 명령줄 클라이언트가 필요합니다. 아래 세 방법은 차례로 하는 단계가 아니라 운영체제와 환경에 맞춰 하나만 고르는 대안입니다. 어느 방법이든 MySQL 8.4 서버가 실행 중이고 클라이언트로 접속해 서버의 버전을 조회할 수 있으면 됩니다.

Windows에서 서비스로 실행하기

MySQL Community Server 8.4 LTS 설치 프로그램으로 서버를 설치하고 root 암호를 정합니다. Windows 서비스의 이름은 설치할 때의 선택에 따라 다를 수 있으므로, 먼저 실제 이름을 조회한 뒤 그 이름으로 시작합니다.

Get-Service -Name 'MySQL*'
Start-Service -Name 'MySQL84'

$env:Path += ';C:\Program Files\MySQL\MySQL Server 8.4\bin'
mysql --version
mysql -u root -p

Get-Service가 보여 준 이름이 MySQL84가 아니라면 Start-Service에도 그 이름을 씁니다. mysql.exe를 찾지 못하면 서버의 bin 디렉터리를 지금 터미널의 경로에 더합니다. 암호는 명령 뒤에 붙이지 말고, -p를 주었을 때 클라이언트가 띄우는 입력 프롬프트에서 입력합니다. 명령에 붙인 암호는 셸의 기록에 남기 때문입니다.

macOS에서 Homebrew로 실행하기

Homebrew를 쓰는 환경이라면 8.4 포뮬러를 설치하고 백그라운드 서비스로 시작합니다. 버전이 붙은 포뮬러는 실행 파일이 기본 경로에 연결되지 않을 수 있으므로 brew --prefix가 돌려준 경로를 씁니다.

brew install [email protected]
brew services start [email protected]

"$(brew --prefix [email protected])/bin/mysql" --version
"$(brew --prefix [email protected])/bin/mysql" -u root -p

처음의 root 인증 방식과 암호 설정은 설치할 때 나오는 포뮬러의 안내를 따릅니다. 서비스가 올라오지 않으면 brew services list와 MySQL의 오류 로그를 먼저 보고, 같은 3306번 포트를 쓰는 다른 MySQL이나 MariaDB가 있는지 확인합니다.

Docker로 실행하기

이미 설치된 MySQL과 부딪히지 않거나 실습 데이터를 쉽게 처음부터 다시 만들고 싶다면 컨테이너가 간편합니다. 아래의 암호는 내 컴퓨터에서만 쓰는 학습용 예이며, 함께 쓰는 환경이나 운영 환경에서는 비밀 정보 저장소로 관리해야 합니다.

docker volume create board-lab-mysql-data

docker run --name board-lab-mysql \
  --detach \
  --publish 3306:3306 \
  --env MYSQL_ROOT_PASSWORD=board-lab-local-8426 \
  --env MYSQL_DATABASE=board_lab \
  --volume board-lab-mysql-data:/var/lib/mysql \
  mysql:8.4

docker exec board-lab-mysql mysqladmin ping -u root -pboard-lab-local-8426
docker exec -it board-lab-mysql mysql -u root -pboard-lab-local-8426

mysqladmin ping이 mysqld is alive를 출력하면 서버가 연결을 받을 준비가 된 것입니다. 3306번 포트를 이미 쓰고 있다면 기존 서버를 멈추거나, 호스트 쪽 포트만 3307:3306처럼 바꾸고 GUI와 애플리케이션도 3307로 접속합니다. 데이터는 볼륨에 저장되므로 컨테이너를 지우고 다시 만들어도 볼륨을 지우지 않는 한 남아 있습니다. 이미지 태그를 latest가 아니라 8.4로 고정한 것은 시간이 지나 다른 주 버전이 내려오는 일을 막기 위해서입니다.

처음 실행할 SQL 읽기

mysql> 프롬프트에는 SQL 문장을 입력하고 세미콜론 ;으로 끝냅니다. 이 글에서는 다음 몇 가지 표현만 씁니다. SELECT 값은 테이블이 없어도 값을 계산해 보여 주고, VERSION()처럼 괄호가 붙은 이름은 값을 돌려주는 함수이며, AS server_version은 결과 열에 읽기 쉬운 별칭을 붙입니다. @@port, @@session.time_zone처럼 @@로 시작하는 표현은 서버나 지금 연결의 시스템 변수를 읽고, SHOW DATABASES는 이 서버에서 볼 수 있는 데이터베이스의 목록을 보여 줍니다.

SELECT 1 AS connection_ok;

connection_ok 열에 1이 나오면 서버가 가장 단순한 질문에 답한 것입니다. 이어서 접속한 서버가 학습 기준과 맞는지 확인합니다.

SELECT
  VERSION() AS server_version,
  @@version_comment AS distribution_name,
  CURRENT_USER() AS authenticated_account,
  @@port AS server_port;

SHOW DATABASES;
server_version | distribution_name            | authenticated_account      | server_port
8.4.x          | MySQL Community Server - GPL | root@localhost 또는 root@% | 3306

버전의 x 자리는 설치한 패치 버전에 따라 다르고, distribution_name은 서버를 빌드한 쪽이 정한 설명이어서 Homebrew로 설치한 서버에서는 MySQL Community Server - GPL 대신 Homebrew가 나옵니다. 버전이 8.4 계열이 아니거나 의도한 계정이 아니라면 다음으로 넘어가지 않습니다. GUI에 저장해 둔 오래된 연결이 방금 시작한 컨테이너와 다른 서버를 가리키는 경우가 흔하기 때문입니다. @@port는 서버 쪽의 포트이므로, Docker를 3307:3306으로 열었다면 호스트에서 접속한 포트는 3307이지만 이 값은 3306으로 나옵니다.

환경을 숨긴 결과는 아무것도 알려 주지 않는다

서버의 버전을 고정하지 않고 sql_mode와 시간대를 확인하지 않은 채 예제를 실행하면, 어떤 환경에서는 느슨한 GROUP BY가 통과하고 다른 환경에서는 오류 1055가 나며, 같은 시각을 적은 리터럴도 세션의 시간대에 따라 다르게 해석될 수 있습니다. 다음은 "준비됨"만 알려 주는 결과의 예입니다.

SELECT 'status' AS setting_name, 'ready' AS setting_value
UNION ALL SELECT 'version', 'UNKNOWN'
UNION ALL SELECT 'sql_mode', 'UNKNOWN'
UNION ALL SELECT 'timezone', 'UNKNOWN'
UNION ALL SELECT 'charset', 'UNKNOWN';
setting_name | setting_value
status       | ready
version      | UNKNOWN
sql_mode     | UNKNOWN
timezone     | UNKNOWN
charset      | UNKNOWN

이런 결과는 어느 버전, 어느 설정에서도 똑같이 나오므로, 뒤에서 오류가 났을 때 환경의 문제인지 SQL의 문제인지 가를 단서를 주지 않습니다. 재현할 수 있다는 것은 명령이 한 번 성공했다는 뜻이 아니라, 같은 입력과 같은 규칙에서 같은 결과를 얻는다는 뜻입니다. 그래서 실제 설정값을 출력해 기록합니다.

서버, 데이터베이스, 세션

MySQL 서버 하나는 여러 데이터베이스와 여러 연결을 가집니다. sql_mode나 time_zone처럼 전역 범위와 세션 범위가 모두 있는 시스템 변수는, 전역값이 새 세션의 기본값이 되고 세션값은 지금 연결에만 적용됩니다. 모든 변수가 두 범위를 다 갖는 것은 아닙니다. USE로 데이터베이스를 골라도 인증된 계정이나 시간대, 모드는 바뀌지 않습니다.

MYSQL · CONNECTION SCOPE

DB는 남아도 새 연결의 선택과 설정은 다시 확인

저장된 데이터베이스, 현재 연결의 선택, 연결의 실행 규칙은 서로 다른 상태입니다.

데이터베이스의 존재와 현재 연결의 선택은 별개다
확인할 상태정하거나 확인하는 방법새 연결을 열면
저장된 데이터베이스CREATE DATABASE로 생성 · SHOW DATABASES로 목록 확인같은 서버와 저장소의 데이터베이스는 유지됩니다.
현재 데이터베이스USE board_lab로 선택 · DATABASE()로 확인접속 시 기본 DB를 지정하지 않았다면 다시 선택해야 합니다.
연결의 실행 규칙SET SESSION으로 모드·시간대 설정 · SET NAMES로 연결 문자 규칙 설정새 세션에 필요한 초기화 SQL을 다시 적용합니다.
저장된 데이터베이스
정하거나 확인하는 방법: CREATE DATABASE로 생성 · SHOW DATABASES로 목록 확인
새 연결을 열면: 같은 서버와 저장소의 데이터베이스는 유지됩니다.
현재 데이터베이스
정하거나 확인하는 방법: USE board_lab로 선택 · DATABASE()로 확인
새 연결을 열면: 접속 시 기본 DB를 지정하지 않았다면 다시 선택해야 합니다.
연결의 실행 규칙
정하거나 확인하는 방법: SET SESSION으로 모드·시간대 설정 · SET NAMES로 연결 문자 규칙 설정
새 연결을 열면: 새 세션에 필요한 초기화 SQL을 다시 적용합니다.

문자 집합도 두 곳에 있습니다. 데이터베이스와 테이블의 문자 집합은 값을 어떻게 저장할지를, 연결의 문자 집합은 클라이언트와 서버가 주고받는 바이트를 어떻게 해석할지를 정하므로 둘 다 확인해야 합니다. sql_mode와 @@ 변수, utf8mb4_0900_ai_ci 같은 정렬 규칙은 MySQL의 기능입니다. 표준 SQL을 쓰더라도 오류의 처리, 식별자를 감싸는 규칙, 날짜 함수, 격리 수준의 기본값은 제품과 버전마다 다르므로, 이 교재는 MySQL 8.4의 동작을 밝히고 다른 제품에 옮길 수 있는 부분을 글마다 구분합니다.

세션을 진단하고 고정한다

서버의 버전이 8.4 계열인지, InnoDB가 트랜잭션을 지원하는지 확인한 뒤, 이 학습 연결에 필요한 세션 설정을 정하고 그 값을 다시 읽어 봅니다. 함께 쓰는 서버의 전역 설정은 함부로 바꾸지 않습니다.

SELECT VERSION() AS mysql_version;
SELECT DATABASE() AS current_database;

SELECT engine, support, transactions
FROM information_schema.engines
WHERE engine = 'InnoDB';

SET SESSION sql_mode =
  'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
SET SESSION time_zone = '+00:00';
SET NAMES utf8mb4 COLLATE utf8mb4_0900_ai_ci;

SELECT
  @@session.sql_mode AS session_sql_mode,
  @@session.time_zone AS session_time_zone,
  @@character_set_connection AS connection_charset,
  @@collation_connection AS connection_collation;

결과는 다음 값들과 맞아야 합니다. 실제 화면에는 SELECT마다 결과 표가 따로 나오지만, 여기서는 확인할 값만 이름과 함께 한곳에 모았습니다. 8.4.x의 x는 설치한 패치 버전에 따라 달라지고, session_sql_mode는 앞에서 SET한 목록과 같은 값이 나오므로 표에서는 뺐습니다.

mysql_version        | 8.4.x
current_database     | NULL 또는 board_lab
InnoDB support       | DEFAULT
transactions         | YES
session_time_zone    | +00:00
connection_charset   | utf8mb4
connection_collation | utf8mb4_0900_ai_ci

여기서 정한 sql_mode는 MySQL 8.4의 기본값과 같은 목록입니다. STRICT_TRANS_TABLES는 잘못된 값을 넣을 때 경고만 남기지 않고 오류로 거부하게 합니다. ONLY_FULL_GROUP_BY는 행을 묶어 집계하는 쿼리에서, SELECT 목록이나 HAVING, ORDER BY에 쓴 집계되지 않은 열이 GROUP BY에 없고 GROUP BY의 열로 값이 하나로 정해지지도 않으면 그 쿼리를 거부합니다. GROUP BY 없이 집계 함수를 쓴 쿼리에 집계되지 않은 열을 함께 고르는 것도 거부합니다. NO_ZERO_DATE, NO_ZERO_IN_DATE, ERROR_FOR_DIVISION_BY_ZERO는 엄격 모드와 함께 쓰도록 되어 있어서, 셋을 빼고 엄격 모드만 켜면 MySQL이 경고를 남깁니다. 시간대를 UTC로 정한 것은 서버가 어느 나라에 있든 결과가 같게 하기 위해서이며, DATETIME 타입은 값을 시간대에 따라 바꾸지 않으므로 어느 기준의 시각인지는 열의 규칙으로 따로 정해 둡니다.

DATABASE()는 데이터베이스가 있는지가 아니라 지금 연결이 고른 데이터베이스를 보여 줍니다. 접속할 때 기본 데이터베이스를 지정하지 않았다면, Docker가 board_lab을 만들어 두었더라도 USE board_lab을 실행하기 전까지는 NULL입니다. 세션 설정은 연결마다 적용되므로, 연결을 새로 열거나 컨테이너를 다시 만들면 위의 SET 문장을 다시 실행합니다.

환경 지문을 남긴다

중요한 설정을 행 하나씩 출력해 두면 환경마다 무엇이 다른지 한눈에 비교할 수 있습니다. 이 결과를 환경 지문이라고 부르고, 뒤에서 오류를 조사할 때 가장 먼저 붙이는 기준으로 씁니다.

SELECT 'version' AS setting_name, VERSION() AS setting_value
UNION ALL SELECT 'database', COALESCE(DATABASE(), '<none>')
UNION ALL SELECT 'sql_mode', @@session.sql_mode
UNION ALL SELECT 'time_zone', @@session.time_zone
UNION ALL SELECT 'connection_charset', @@character_set_connection
UNION ALL SELECT 'connection_collation', @@collation_connection;

SELECT schema_name, default_character_set_name, default_collation_name
FROM information_schema.schemata
WHERE schema_name = 'board_lab';

아직 board_lab을 만들지 않았다면 두 번째 조회가 0행인 것이 정상이고, 만든 뒤에는 utf8mb4와 utf8mb4_0900_ai_ci가 나와야 합니다. 버전이 8.4가 아니라면 예제를 억지로 맞추지 말고 기준에 맞는 서버를 준비합니다.

이 지문은 뒤의 여러 장이 기대는 설정을 담고 있습니다. 집계 함수와 GROUP BY의 규칙은 ONLY_FULL_GROUP_BY에, 격리 수준과 잠금은 InnoDB에, 유효 기간 모델은 시간대에, JSON 구조와 검증은 서버의 버전에 기대므로, 실습의 기록에 함께 남겨 둡니다.

환경에서 자주 놓치는 것

컨테이너는 서버와 데이터를 쉽게 재현하게 해 주지만, 백업이나 모니터링, 업그레이드를 대신 해 주지는 않습니다. 볼륨을 다시 쓴 컨테이너는 깨끗한 환경이 아니므로, 예전에 만든 테이블이나 다른 정렬 규칙이 남아 있을 수 있습니다. IF NOT EXISTS로 기존 데이터베이스를 다시 쓸 때도 마찬가지여서, 메타데이터로 실제 설정을 확인해야 합니다.

GUI 도구와 명령줄 클라이언트가 같은 서버를 보는지는 SELECT CONNECTION_ID(), @@hostname, @@port;처럼 연결과 서버를 식별하는 값을 양쪽에서 비교해 확인합니다. 한글이 화면에 맞게 보여도 저장된 바이트와 연결의 변환이 올바른지는 한글과 이모지, 영문을 넣었다 읽어 보는 것으로 확인합니다. 테이블 이름의 대소문자를 구별하는지는 운영체제와 lower_case_table_names 설정에 따라 다르므로, 이 교재는 테이블 이름을 모두 소문자 snake_case로 씁니다.

root는 설치와 관리용 계정이고, 애플리케이션은 필요한 권한만 가진 계정으로 연결합니다. 암호를 SQL의 실행 기록이나 저장소에 남기지 않는 것도 처음부터 들일 습관입니다.

정리

  • 설치 화면이나 GUI의 연결 표시 대신, 실제 서버의 버전과 세션의 변수를 SQL로 확인합니다.
  • 전역 변수는 새 세션의 기본값이고 세션 변수는 지금 연결에만 적용되며, USE는 데이터베이스만 고를 뿐 다른 설정을 바꾸지 않습니다.
  • 학습 연결에는 MySQL 8.4의 기본값과 같은 sql_mode, UTC 시간대, utf8mb4 연결을 정하고, 연결을 새로 열 때마다 다시 적용합니다.
  • 버전, 모드, 시간대, 문자 집합을 환경 지문으로 남겨 두면 뒤의 오류를 환경과 SQL 가운데 어디서 찾을지 가를 수 있습니다.
  • 컨테이너와 GUI는 재현을 돕는 도구일 뿐, 설정의 확인을 대신하지 않습니다.

연습 문제

1. 새로 연 연결

연결 A에서 USE board_lab과 UTC 설정을 마쳤습니다. 기본 데이터베이스를 지정하지 않고 연 두 번째 연결 B에서는 아직 아무 설정도 하지 않았습니다. B에서도 board_lab이 보이나요? B의 DATABASE()도 board_lab인가요? A에서 바꾼 세션의 시간대가 B에도 적용되나요?

해설 보기

데이터베이스는 서버에 저장된 객체이므로, 권한이 있다면 B에서도 SHOW DATABASES에 board_lab이 보입니다. 하지만 지금 고른 데이터베이스는 연결마다의 상태여서, 기본 데이터베이스 없이 연 B의 DATABASE()는 NULL입니다. 세션의 시간대도 A에서만 바꾼 것이므로 B에는 전역 기본값이 적용됩니다. 전역 기본값이 마침 UTC라서 같은 값이 보이더라도, A의 설정이 B로 전해졌다고 보면 안 됩니다. B에서는 다음처럼 필요한 설정을 다시 적용합니다.

SET SESSION sql_mode =
  'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
SET SESSION time_zone = '+00:00';
SET NAMES utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE board_lab;

2. 바꾼 포트와 보이는 포트

Docker 컨테이너를 --publish 3307:3306으로 띄우고 호스트에서 mysql -h 127.0.0.1 -P 3307 -u root -p로 접속했습니다. 이 연결에서 SELECT @@port;는 무엇을 돌려주나요? 애플리케이션의 설정에는 어느 포트를 적어야 하나요?

해설 보기

@@port는 서버 프로세스가 컨테이너 안에서 듣고 있는 포트이므로 3306을 돌려줍니다. 호스트의 3307번 포트로 온 연결을 Docker가 컨테이너의 3306번으로 넘겨 주기 때문입니다. 호스트에서 접속하는 애플리케이션의 설정에는 3307을 적어야 하고, docker network create로 만든 사용자 정의 네트워크에 함께 연결한 다른 컨테이너에서 접속한다면 컨테이너 이름과 3306을 씁니다. 이 글처럼 --network 없이 띄운 컨테이너가 있는 기본 bridge 네트워크에서는 컨테이너 이름으로 서로를 찾지 못합니다. @@port가 접속에 쓴 포트와 다르다고 엉뚱한 서버에 붙었다고 판단하지 않도록 이 차이를 기억해 둡니다.

3. 모드를 줄였을 때의 경고

어느 팀원이 세션 모드를 'STRICT_TRANS_TABLES,ONLY_FULL_GROUP_BY,NO_ENGINE_SUBSTITUTION'으로 정했더니 SET 문장 뒤에 경고가 하나 남았습니다. 오류가 아니라 경고인데 신경 써야 할까요? 왜 이런 경고가 남았을까요?

해설 보기

MySQL 8.4에서 NO_ZERO_DATE, NO_ZERO_IN_DATE, ERROR_FOR_DIVISION_BY_ZERO는 엄격 모드와 함께 쓰도록 되어 있어서, 엄격 모드만 켜고 셋을 빼면 둘을 함께 쓰라는 경고를 남깁니다. 문장은 실행되었으므로 오류는 아니지만, 이 설정에서는 '2026-00-10' 같은 0이 든 날짜나 0으로 나누는 계산이 오류 없이 통과할 수 있어 기본 설정과 결과가 달라집니다. SHOW WARNINGS;로 경고의 내용을 확인하고, 특별한 이유가 없다면 본문처럼 기본값과 같은 목록을 쓰는 편이 다른 환경과 결과를 맞추기 쉽습니다.

다음 글 파일 저장과 DBMS에서는 준비한 서버에 board_lab 데이터베이스와 첫 테이블 members를 만들고, DBMS가 파일 저장 대신 해 주는 일을 정리합니다.