INSERT는 테이블에 새 행(row)을 추가하는 DML 문입니다.
테이블에 INSERT하려면 그 테이블에 대한 INSERT 권한이 필요합니다.
VALUES 방식 (단일/복수 행)
INSERT INTO tbl_name (col1, col2) VALUES (v1, v2);
INSERT INTO tbl_name (a,b,c)
VALUES (1,2,3), (4,5,6), (7,8,9); -- 다중 행 삽입
INSERT … SELECT (다른 테이블의 결과를 삽입)
INSERT INTO tgt (a,b,c)
SELECT x,y,z FROM src WHERE ...;
INSERT … SET (MySQL 특유 문법)
INSERT INTO users SET username='kim', email='k@example.com';
INSERT … TABLE (한 테이블에서 다른 테이블로 삽입)
INSERT INTO tgt TABLE src;
INSERT는 컬럼 리스트를 생략하면 테이블의 모든 컬럼에 값을 제공해야 하고, 컬럼값이 없으면 컬럼의 기본값(default) 또는 암묵적 기본값이 사용됩니다. (INSERT INTO tbl () VALUES (); 로 모든 컬럼에 default를 넣을 수도 있습니다.)
(자세한 문법·옵션 — MySQL 매뉴얼의 INSERT 문서 참고).
MySQL에서는 여러 방식으로 “없으면 삽입, 있으면 갱신” 같은 동작을 처리합니다.
INSERT … ON DUPLICATE KEY UPDATE
기본 동작: 삽입하려는 행이 PRIMARY KEY 또는 UNIQUE 제약과 충돌하면 UPDATE를 수행합니다.
예:
INSERT INTO products (sku, qty, price)
VALUES ('A123', 1, 10.0)
ON DUPLICATE KEY UPDATE qty = qty + VALUES(qty), price = VALUES(price);
MySQL 8.x 계열에서는 VALUES() 함수 사용이 점진적으로 deprecated 되어, VALUES(...) AS alias 방식으로 값에 대한 별칭(alias) 을 주고 alias.col로 참조하는 문법 권장(즉 ... VALUES (...) AS new ON DUPLICATE KEY UPDATE col = new.col)합니다.
ON DUPLICATE KEY UPDATE의 affected-rows 규칙: 삽입되면 1, 기존 행이 업데이트되면 2, 업데이트했지만 값이 실제로 바뀌지 않으면 0(특수 플래그 사용 시 변경 가능).
INSERT IGNORE
무시 가능한 오류(예: 중복키, 데이터 경계 위반 등)를 경고로 바꾸고 문제가 되는 행은 건너뜁니다. 전체 문은 계속 실행됩니다.
REPLACE INTO
중복 키가 있으면 기존 행을 삭제하고 새 행을 삽입(DELETE + INSERT와 유사). 부작용(트리거 작동, 삭제로 인한 FK 영향 등)이 있으므로 주의.
LAST_INSERT_ID(): AUTO_INCREMENT 컬럼이 있을 때 생성된 첫번째(문맥상) 자동증가 값을 얻습니다. 다중 행 삽입의 경우 첫 번째로 생성된 id를 반환합니다.
ROW_COUNT() / mysql_affected_rows(): 문으로 영향받은 행 수. ON DUPLICATE KEY UPDATE 사용 시 행당 영향값(1/2/0) 규칙이 적용됩니다.
mysql_info() / SHOW WARNINGS: INSERT 실행 후 Records: N Duplicates: M Warnings: K 같은 요약 정보와 상세 경고 확인 가능.
MySQL은 기본적으로 autocommit=ON 이므로, 트랜잭션을 명시적으로 시작하지 않으면 각 DML 문은 독립적으로 커밋됩니다. START TRANSACTION으로 여러 DML을 하나의 원자적 단위로 묶을 수 있습니다. (문장 단위로는 기본적으로 원자적: 문장 실행 중 에러가 나면 그 문장은 롤백됩니다.)
가능하면 여러 행을 한 번에 전송: 다중-VALUES 리스트는 네트워크 왕복·파싱 오버헤드 절감.
대량 적재는 LOAD DATA INFILE가 가장 빠름 (CSV → 테이블). LOCAL 옵션은 클라이언트 측 파일 읽기 기능과 보안 차이 있음.
트랜잭션으로 묶기: 다수의 INSERT를 하나의 트랜잭션(COMMIT 한 번)으로 처리하면 속도 향상.
인덱스 비용 고려: 대량 INSERT 시 인덱스 수가 많으면 삽입 비용이 크게 증가. 가능하면 배치 전에 인덱스 제거 후 재생성하거나, 작은 배치 단위로 삽입. (또는 LOAD DATA 활용)
서버 파라미터/옵션: innodb_flush_log_at_trx_commit, binlog_format 등 설정에 따라 퍼포먼스·내구성이 달라짐 — 운영환경에서는 신중 조정.
(정식 가이드: MySQL 매뉴얼의 INSERT 최적화 항목 참고).
LOAD DATA INFILE → 빠름(가능하면 이 방법 사용).
또는 INSERT 다중-VALUES를 적당한 배치 크기(예: 1,000 ~ 50,000 행, 시스템·행 크기에 따라 조정)로 나눠 전송.
대량작업 시 외래키 검사 일시 중지(SET FOREIGN_KEY_CHECKS=0) 또는 유니크 체크 비활성을 고려할 수 있으나 데이터 무결성/복구 계획을 반드시 검토해야 함.
온라인 프로덕션에서는 pt-online-schema-change/gh-ost 같은 도구로 스키마 변경과 병행 삽입 처리.
(성능 튜닝은 테이블 구조·하드웨어·버전마다 달라 테스트 필수).
항상 파라미터화(Prepared Statements) 해서 SQL 인젝션 방지. 대부분의 DB 드라이버에서 바인딩 사용 권장. MySQL의 서버측 prepared문과 클라이언트 라이브러리의 prepared 기능을 모두 지원합니다.
예 — PHP (PDO):
$stmt = $pdo->prepare("INSERT INTO users (username,email) VALUES (?, ?)");
$stmt->execute([$username, $email]);
$id = $pdo->lastInsertId();
예 — Python (mysql-connector-python / PyMySQL):
cur.execute("INSERT INTO users (username, email) VALUES (%s, %s)", (username, email))
conn.commit()
print(cur.lastrowid) # 또는 conn.insert_id()
INSERT IGNORE는 오류를 경고로 바꿔 건너뜁니다(원인 기록은 WARNINGS). SHOW WARNINGS;로 확인.
중복키/충돌을 처리하려면 ON DUPLICATE KEY UPDATE가 더 명확한 선택. (REPLACE는 행 삭제-삽입의 부작용 주의)
SHOW ERRORS / SHOW WARNINGS / 드라이버의 예외(예: PDOException)를 통해 상세 원인 파악.
여러 행 INSERT는 모두 성공해야 하나요?
단일 INSERT ... VALUES (...), (...) 문장은 문장 전체가 하나의 작업으로 처리됩니다. 에러 발생 시 그 문장 전체가 실패(롤백)됩니다(autocommit 모드일 때 문장 단위로 처리). 트랜잭션 안에서 여러 문장을 묶으면 원하는 원자성 확보 가능.
다중 행 삽입에서 LAST_INSERT_ID()는 어떤 값을 반환하나요?
다중 행 삽입 시 LAST_INSERT_ID()는 “첫 번째”로 생성된 AUTO_INCREMENT 값을 반환합니다(즉 배치의 첫 id).
MySQL에 RETURNING 절 있나요?
MySQL(표준 8.0 계열 기준)은 PostgreSQL/Oracle처럼 INSERT ... RETURNING을 네이티브로 지원하지 않습니다(따라서 삽입된 값을 바로 반환하려면 LAST_INSERT_ID() 또는 트랜잭션 내에서 SELECT를 별도로 실행해야 함). 일부 다른 DBMS에만 존재하는 기능입니다.
단일 행
INSERT INTO users (username, email, created_at)
VALUES ('yoon', 'y@example.com', NOW());
다중 행 (배치)
INSERT INTO events (name, happened_at)
VALUES
('e1', '2025-09-01'),
('e2', '2025-09-02'),
('e3', '2025-09-03');
INSERT … SELECT (데이터 복사)
INSERT INTO archive_orders (order_id, total)
SELECT id, total FROM orders WHERE created_at < '2024-01-01';
Upsert (alias 방식 권장)
INSERT INTO inventory (sku, qty)
VALUES ('A1', 10), ('B2', 5) AS new
ON DUPLICATE KEY UPDATE qty = qty + new.qty;
(위 예제는 MySQL 8.0.19+의 alias 사용 권장을 따릅니다; VALUES() 함수는 향후 제거될 수 있어 대체 방법으로 alias 사용 권장).
대량 CSV 적재 (빠름)
LOAD DATA INFILE '/var/data/users.csv'
INTO TABLE users
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
(username,email,created_at);
(LOCAL 옵션의 사용·보안 차이 주의`).
파라미터화된 쿼리(Prepared Statement) 사용 — 보안.
대량 삽입은 LOAD DATA 또는 다중-VALUES + 트랜잭션으로 처리.
중복 처리에는 ON DUPLICATE KEY UPDATE(alias 사용 권장) 또는 INSERT IGNORE/REPLACE 상황에 맞게 선택.
LAST_INSERT_ID() 동작(다중삽입 시 첫 id 반환) 기억.
프로덕션에서 큰 변경(인덱스, 제약 포함)은 스테이징에서 검증하고, 온라인 DDL 도구 고려.