Communitygithub.com

MySQL/MariaDB 데이터베이스 전문가 스킬

Claude, Codex, Copilot 등 AI 에이전트를 위한 MySQL/MariaDB 데이터베이스 심층 전문 스킬입니다. 올바른 데이터 타입, 문자셋/정렬 방식, 인덱스 설계, EXPLAIN 기반 쿼리 튜닝, 트랜잭션 격리 및 잠금, MySQL 특화 오류 사례를 다룹니다.

MariaDB 데이터베이스 전문가 스킬란 무엇인가요?

이 스킬은 MySQL 또는 MariaDB 데이터베이스를 사용하는 프로젝트에서 AI 에이전트(Claude Code, Cursor, Codex, Copilot 등)가 정확하고 효율적인 데이터베이스 스키마와 쿼리를 작성하도록 지원합니다. `package.json`에 `mysql2`/`mysql`이 있거나, Prisma의 `provider = "mysql"`, Sequelize의 `dialect: 'mysql'`, TypeORM의 `type: 'mysql'`/`'mariadb'` 설정, `docker-compose.yml`에서 `mysql`/`mariadb` 이미지 사용, 또는 `mysql://` 연결 문자열이 감지되면 자동으로 활성화됩니다. 스킬은 utf8mb4 문자셋, DECIMAL을 이용한 금액 처리, 적절한 크기의 VARCHAR/INT 사용, ENUM의 장단점, InnoDB vs MyISAM 선택, 복합 인덱스 설계(왼쪽 접두사 규칙, 커버링 인덱스, sargable 조건, 키셋 페이지네이션), 트랜잭션 격리 수준(REPEATABLE READ, 갭 락, 데드락), EXPLAIN 기반 쿼리 튜닝 등 MySQL/MariaDB에 특화된 지식을 제공합니다. 또한 MariaDB의 차이점(고유 확장 기능, utf8mb4_0900_ai_ci 부재, JSON 구현 차이)을 명시적으로 구분하여, MySQL 전용 기능이 MariaDB에서 동작하지 않을 수 있음을 경고합니다. 이 스킬은 데이터베이스 스키마 개발(`database-schema-dev`) 및 백엔드 개발(`backend-dev`) 에이전트의 엔진 중립적인 마이그레이션/접근 계층 위에 적용되는 MySQL 전문 지식 레이어입니다.

지원 대상✓Claude Code✓Codex CLI✓Cursor
npx skills add LDKTC/Agent-P-FullStack

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서


name: mysql description: Deep MySQL/MariaDB-specific database expertise — correct data types and charset/collation (utf8mb4, DECIMAL for money, right-sized VARCHAR/INT, ENUM tradeoffs), InnoDB vs MyISAM, index design and EXPLAIN-driven query tuning (composite index order, covering indexes, sargable predicates, keyset pagination), transaction isolation and locking (REPEATABLE READ, gap locks, deadlocks), and MySQL-specific correctness pitfalls. Use when package.json lists mysql2 or mysql, an ORM's dialect/provider is set to mysql (Prisma provider = "mysql", Sequelize dialect: 'mysql', TypeORM type: 'mysql'/'mariadb'), a docker-compose service runs a mysql/mariadb image, or a mysql:// connection string is present, including when asked directly (Thai or English) to เขียนหรือตรวจ schema/query MySQL ให้ถูกต้องและมีประสิทธิภาพ. Not a replacement for database-schema-dev's engine-agnostic migration workflow or backend-dev's access-layer code — this is the MySQL-specific expertise both draw on when the engine actually is MySQL/MariaDB.

MySQL

database-schema-dev 및 backend-dev의 MySQL 전용 전문 지식 레이어: 이 에이전트들은 엔진 중립적이며 프로젝트에 이미 있는 마이그레이션 도구/ORM과 일치합니다. 이 스킬은 실제 엔진이 MySQL 또는 그와 호환되는 포크인 MariaDB일 때 이 에이전트들이 활용하는 실제 MySQL 지식입니다. MariaDB가 다르게 동작하는 경우 이를 명시적으로 언급합니다 — 고급 기능에 대해 완전한 동등성을 가정하지 마십시오.

1단계 — 엔진이 실제로 MySQL/MariaDB인지 확인

package.json에서 mysql2/mysql, ORM 방언/프로바이더(Prisma의 provider = "mysql", Sequelize의 dialect: 'mysql', TypeORM의 type: 'mysql'/'mariadb'), mysql/mariadb 이미지를 실행하는 docker-compose.yml 서비스, 또는 mysql:// 연결 문자열을 확인합니다. 해당 사항이 없으면 중단하고 — database-schema-dev/backend-dev의 엔진 중립 기본값 또는 일치하는 엔진별 스킬을 대신 사용합니다.

2단계 — 문자셋, 정렬 방식, 스토리지 엔진

  • 기본 문자셋은 utf8이 아닌 utf8mb4입니다. MySQL의 utf8은 최대 3바이트 별칭으로 대부분의 이모지/천체 문자를 저장할 수 없습니다. 명시적 정렬 방식(utf8mb4_0900_ai_ci on MySQL 8+; MariaDB는 0900 정렬 방식이 없으며, 둘 다 대상으로 하려면 utf8mb4_unicode_ci 사용)을 함께 사용합니다.
  • 구체적인 이유가 없는 한 InnoDB를 사용합니다(MyISAM은 트랜잭션, 행 잠금, 외래 키가 없음). MySQL 5.5/MariaDB 10.x 이후 기본값이므로 보통 덮어쓰는 설정이 없는지 확인하는 정도입니다.
  • _ci 정렬 방식은 '[email protected]'이 '[email protected]'과 일치함을 의미합니다. 대소문자를 구분해야 하는 것(토큰, 비밀)에는 _bin/_cs(또는 해시된 컬럼)을 사용합니다.

3단계 — 올바른 데이터 타입

  • 금액/정확한 소수: DECIMAL(p,s), 절대 FLOAT/DOUBLE 사용 금지 — 이진 부동소수점은 대부분의 10진 분수를 정확히 표현할 수 없습니다.
  • 실제 범위에 따라 정수 크기 조정(TINYINT/SMALLINT/INT/BIGINT), 습관적으로 모든 곳에 BIGINT를 사용하지 않음 — 작은 타입은 더 작은 인덱스를 의미합니다. id 컬럼에 UNSIGNED는 무료 헤드룸을 제공하며, 음수가 될 일이 없습니다.
  • DATETIME은 시간대 변환 의도가 없는 벽시계 값용; TIMESTAMP(UTC 정규화, 세션 시간대에 따라 자동 변환, 2038년 범위 제한)는 실제 순간(instant)에만 사용 — 기본값으로 사용하지 마십시오.
  • VARCHAR(n)은 실제 제약 조건에 따라 크기를 지정하고, 반사적으로 VARCHAR(255)를 사용하지 않음; TEXT/BLOB은 진정으로 무제한인 콘텐츠용 — 둘 다 기본값이나 전체(접두사 제외) 인덱스를 가질 수 없습니다.
  • ENUM은 고정적이고 거의 변경되지 않는 집합에만 사용 — 값은 알파벳 순이 아닌 정의 순서로 정렬되며(ORDER BY에서 예상치 못한 결과), 값을 추가하는 것은 MySQL 8에서는 메타데이터만 변경하지만 MariaDB/구형 MySQL에서는 테이블을 다시 작성할 수 있습니다. 집합이 커지거나 메타데이터가 필요하면 조회 테이블이 더 잘 확장됩니다.
  • 네이티브 JSON(MySQL 5.7.8+/MariaDB 10.2+ — MariaDB의 JSON은 실제로 LONGTEXT에 CHECK 제약 조건을 더한 것이므로 이진 타입이 아니며, MySQL의 이진 저장/부분 업데이트 이점이 적용되지 않음)은 진정으로 스키마가 없는 데이터에만 사용 — 정기적으로 필터링하거나 조인하는 것은 실제 컬럼에 속합니다.

4단계 — 인덱스 설계

  • 복합 인덱스: 동등 조건으로 필터링하는 컬럼을 먼저 배치하고, 범위/정렬 컬럼은 마지막에 배치합니다. (status, created_at)는 WHERE status = ? ORDER BY created_at에 적합하며, 역순은 그렇지 않습니다.
  • 왼쪽 접두사 규칙: (a, b, c) 인덱스는 (a) 및 (a, b) 조회도 처리할 수 있습니다 — 중복 (a) 인덱스를 함께 추가하지 마십시오.
  • 커버링 인덱스(선택한 모든 컬럼 포함)를 사용하면 MySQL이 인덱스만으로 답을 찾을 수 있습니다(EXPLAIN에서 Using index). 핫하고 좁은 쿼리에는 유용하지만 모든 쿼리에 기본으로 사용할 필요는 없습니다.
  • 긴 VARCHAR/TEXT에 대한 접두사 인덱스(col(20)): COUNT(DISTINCT LEFT(col, n))을 COUNT(DISTINCT col)과 비교하여 n의 크기를 결정합니다.
  • 깊은 페이지네이션: LIMIT 20 OFFSET 100000은 매 페이지마다 여전히 100,000개의 행을 스캔하고 폐기합니다. 키셋 페이지네이션(WHERE (created_at, id) < (:last_created, :last_id) ORDER BY created_at DESC, id DESC LIMIT 20)은 동일한 복합 인덱스를 재사용하고 커서로 직접 이동합니다 — OFFSET 대신 무한 스크롤이나 깊은 API 페이지네이션에 사용합니다.
  • 모든 인덱스는 쓰기 시간과 디스크 공간을 소모합니다 — 실제 쿼리 패턴(database-schema-dev 1단계)에 따라 인덱스를 추가하고, 컬럼별로 추측성으로 추가하지 마십시오.

5단계 — EXPLAIN 기반 쿼리 튜닝

  • 쿼리가 빠르다고 신뢰하기 전에 EXPLAIN(또는 EXPLAIN ANALYZE, MySQL 8.0.18+/MariaDB 10.4+)을 실행합니다 — 쿼리 형태만으로 판단하지 마십시오.
  • type: ALL은 전체 테이블 스캔입니다; ref/range/const는 인덱스가 실제로 사용되고 있음을 의미합니다 — 최적화할 가치가 있는 쿼리는 사소한 테이블 크기를 넘어서면 ALL이 표시되지 않아야 합니다.
  • Extra: Using filesort/Using temporary는 MySQL이 인덱스 외부에서 정렬/중간 테이블을 생성했음을 의미합니다 — 보통 ORDER BY/GROUP BY와 일치하는 인덱스로 해결됩니다.
  • 인덱스된 컬럼에 래핑 함수나 묵시적 형변환(WHERE YEAR(created_at) = ..., 문자열 컬럼을 숫자와 비교)은 인덱스를 무효화합니다 — 대신 베어 컬럼 범위로 다시 작성합니다(created_at >= '2024-01-01' AND created_at < '2025-01-01').
  • 넓은 테이블에서 SELECT *는 커버링 인덱스를 무효화하고 요청되지 않은 TEXT/BLOB 컬럼을 가져옵니다 — 실제로 사용되는 것만 선택합니다.

6단계 — 트랜잭션 및 잠금

  • InnoDB는 행이 아닌 테이블을 잠그지만, 기본 격리 수준 REPEATABLE READ는 UPDATE/DELETE/잠금 SELECT의 범위 스캔에 갭 락도 사용합니다 — 범위 조건이 일치하는 행보다 더 많은 것을 차단할 수 있습니다. READ COMMITTED는 반복 읽기가 필요하지 않은 경우 갭 락을 방지합니다.
  • 그 스냅샷은 일반 SELECT만 다룹니다 — 동일한 트랜잭션 내의 잠금 읽기(SELECT ... FOR UPDATE, UPDATE, DELETE)는 대신 최신 커밋된 데이터를 읽으므로 스냅샷이 표시하지 않은 행을 볼 수 있습니다. 스냅샷 격리가 잠금 읽기도 보호한다고 가정하지 마십시오.
  • 트랜잭션을 짧게 유지합니다 — 열린 트랜잭션은 잠금을 보유하고, REPEATABLE READ에서는 InnoDB가 이전 행 버전을 제거하는 것도 차단합니다.
  • SELECT ... FOR UPDATE는 하나의 트랜잭션에서 읽은 후 쓰는 행을 잠급니다(잔액 확인 후 출금); 코드베이스 전체에서 일관된 행 순서로 잠금을 획득하여 데드락을 방지합니다.
  • 데드락(ERROR 1213, SQLSTATE 40001)은 실제 경합에서 예상됩니다 — InnoDB가 한쪽을 자동 롤백합니다; 해당 코드를 잡아서 더 긴 트랜잭션으로 "수정"하는 대신 재시도합니다. SHOW ENGINE INNODB STATUS → LATEST DETECTED DEADLOCK은 각 측이 보유/대기한 내용을 보여줍니다.
  • Autocommit은 연결당 기본적으로 켜져 있습니다 — 여러 문의 원자성에는 명시적 트랜잭션(또는 ORM의 래퍼)이 필요하며, 코드 상의 인접성만으로는 충분하지 않습니다.

7단계 — MySQL 특화 오류 사례 및 MariaDB 차이점

  • sql_mode는 STRICT_TRANS_TABLES를 포함해야 합니다(MySQL 5.7/MariaDB 10.2 이후 기본값) — 포함되지 않으면 범위를 벗어나거나 잘못된 타입의 값이 오류 대신 자동으로 잘립니다; 완화되지 않았는지 확인합니다.
  • AUTO_INCREMENT 값은 간격이 없지 않습니다(롤백도 하나를 소모함) — id가 연속적이라고 가정하지 마십시오.
  • 세션 time_zone은 TIMESTAMP 변환에 영향을 미치지만 DATETIME에는 영향을 미치지 않습니다 — 연결별 시간대가 혼합된 풀은 실제 "잘못된 시간" 버그입니다; 명시적으로 고정합니다.
  • MariaDB에는 MySQL에 해당하는 것이 없는 자체 확장 기능(CREATE SEQUENCE, 쓰기 시 RETURNING)이 있습니다 — MySQL도 대상인 경우 사용하지 마십시오; 윈도우 함수/CTE는 MySQL 8.0+/MariaDB 10.2+가 필요하므로 고정된 버전을 먼저 확인합니다.
  • MariaDB의 최적화 프로그램과 EXPLAIN 출력은 MySQL의 것과 동일한 엔진이 아닙니다 — 다른 엔진에서 튜닝된 계획을 신뢰하지 말고 실제 사용 중인 엔진에서 EXPLAIN을 다시 실행합니다.

8단계 — 완료 보고 전 자체 점검

새롭거나 변경된 모든 컬럼에 의도된 타입이 있고 문자셋이 utf8mb4로 설정되었는지 확인합니다. 모든 새 인덱스가 실제 쿼리 패턴으로 뒷받침되고 EXPLAIN 출력이 사용 중임을 보여주며, 깊거나 무제한 리스트 엔드포인트는 OFFSET 대신 키셋을 사용하는지 확인합니다. 여러 문으로 이루어진 쓰기는 명시적 트랜잭션으로 래핑되었고 잠금 읽기의 격리 동작이 의도적이며 가정되지 않았는지 확인합니다. 컴포즈 파일/CI가 MariaDB를 고정하는 경우, MySQL-8 전용 기능을 추가한 것이 없는지 확인합니다.

하지 말아야 할 것

  • utf8 문자셋이나 금액에 FLOAT/DOUBLE을 사용하지 마십시오 — utf8mb4와 DECIMAL이 올바른 기본값입니다.
  • 쿼리 패턴 없이 인덱스를 추가하거나 EXPLAIN을 확인하지 않고 쿼리를 "빠르다"고 말하지 마십시오.
  • 깊게 페이지를 나눌 수 있거나 무제한으로 실행될 수 있는 리스트에 OFFSET 페이지네이션을 사용하지 마십시오 — 키셋을 사용합니다.
  • 트랜잭션을 보호하는 작업보다 더 오래 열어두지 마십시오, 특히 REPEATABLE READ의 갭 락 아래에서는 더욱 그렇습니다.
  • AUTO_INCREMENT의 연속성이나 묵시적 타입 변환에 의존하여 정확성을 보장하지 마십시오.
  • MariaDB 대상에 MySQL-8 전용 기능(네이티브 JSON, utf8mb4_0900_ai_ci)이 있다고 가정하지 말고 고정된 버전을 확인합니다.
  • 엔진 중립적인 마이그레이션/접근 계층 코드를 직접 작성하지 마십시오 — 그것은 database-schema-dev/backend-dev의 역할입니다; 이 스킬은 그 위에 적용되는 MySQL 지식입니다.

관련 스킬