테이블을 만들고 운영할 때 Oracle·Tibero, MySQL, MSSQL 이 어떻게 다른지 한 페이지에 모았습니다.
Ctrl+F로 기능 이름(예:IDENTITY,SAVEPOINT,SKIP LOCKED,INVISIBLE)을 검색하면 됩니다. 원리는 각 절 끝에 적은 레슨에서 다루고, 여기서는 바로 찾아 쓰는 표와 짧은 코드만 담습니다. Tibero 는 Oracle 호환이라 Oracle 과 묶고, 다른 점만 따로 적습니다.
자주 헷갈리는 10가지입니다.
| 기능 | Oracle·Tibero | MySQL | MSSQL |
|---|---|---|---|
| 한글 문자열 타입 | VARCHAR2(n CHAR) |
VARCHAR(n) (문자 수) |
NVARCHAR(n) |
| DDL 과 트랜잭션 | 암묵적 커밋 | 암묵적 커밋 | 트랜잭션 안에서 롤백 가능 |
| 채번 방식 | 시퀀스, IDENTITY (12c) | AUTO_INCREMENT |
IDENTITY, SEQUENCE (2012) |
| UNIQUE 컬럼의 NULL | 여러 개 허용 | 여러 개 허용 | 하나만 허용 |
| 함수 기반 인덱스 | 지원 | 8.0.13 부터 | 계산 열 + 인덱스 |
| 클러스터드 인덱스 | 없음 (힙) | PK 가 클러스터드 | PK 가 기본 클러스터드 |
| 자동 커밋 | 도구가 정함 | 켬 | 켬 |
| 기본 격리 수준 | READ COMMITTED |
REPEATABLE READ |
READ COMMITTED |
| 락 대기 기본값 | 무한 | 50초 | 무한 |
| 데드락 시 롤백 범위 | 문장만 | 희생 트랜잭션 전체 | 희생 트랜잭션 전체 |
핵심은 세 가지입니다.
VARCHAR2(n) 은 기본이 바이트 단위라 한글이 3바이트로 셉니다.문자열 길이 단위와 컬럼 변경 문법이 DB마다 다릅니다.
| 기능 | Oracle·Tibero | MySQL | MSSQL |
|---|---|---|---|
| 기본 문자열 타입 | VARCHAR2(n) (바이트) |
VARCHAR(n) (문자) |
VARCHAR(n) (바이트) |
| 문자 단위 지정 | VARCHAR2(n CHAR) |
기본이 문자 단위 | NVARCHAR(n) |
| 한글 저장 | 바이트면 3바이트씩 | 문자 수로 셈 | NVARCHAR 사용 |
빈 문자열 '' |
NULL 로 저장 |
빈 문자열 그대로 | 빈 문자열 그대로 |
| 타입만 변경 | MODIFY |
MODIFY COLUMN |
ALTER COLUMN |
| 이름과 타입 동시 변경 | RENAME COLUMN 후 MODIFY |
CHANGE COLUMN 한 번 |
sp_rename 후 ALTER COLUMN |
| DDL 실행 시 | 암묵적 커밋 | 암묵적 커밋 | 트랜잭션 안에서 롤백 가능 |
| 테이블 복사 | CREATE TABLE AS SELECT |
CREATE TABLE AS SELECT |
SELECT INTO |
참조되는 표 TRUNCATE |
불가 (FK) | 불가 (FK) | 불가 (FK) |
컬럼 변경 문법은 아래와 같습니다. 이름과 타입을 같이 바꿀 때 문장 수가 다릅니다.
-- Oracle · Tibero
ALTER TABLE emp RENAME COLUMN name TO ename;
ALTER TABLE emp MODIFY (ename VARCHAR2(50 CHAR));
-- MySQL
ALTER TABLE emp CHANGE COLUMN name ename VARCHAR(50);
-- MSSQL
EXEC sp_rename 'emp.name', 'ename', 'COLUMN';
ALTER TABLE emp ALTER COLUMN ename NVARCHAR(50);주의Oracle 은
''를NULL로 저장합니다. NOT NULL 컬럼에''를 넣으면 오류이고,col = ''조건은 항상 알 수 없음이 됩니다. MySQL·MSSQL 은 빈 문자열이 값입니다.
DDL 을 배포 스크립트에 넣을 때는 이 차이가 중요합니다. Oracle·MySQL 은 중간에 실패해도 앞 문장이 이미 반영됩니다. MSSQL 은 BEGIN TRAN 으로 묶어 롤백할 수 있습니다.
관련 레슨은 SQL 중급의 DDL·제약 레슨, 실무의 데이터 이관·검증 레슨입니다.
제약은 문법보다 NULL 을 다루는 방식과 버전이 다릅니다.
| 기능 | Oracle·Tibero | MySQL | MSSQL |
|---|---|---|---|
| UNIQUE 와 NULL | 여러 개 허용 | 여러 개 허용 | 하나만 허용 |
| NULL 허용 UNIQUE 우회 | 해당 없음 | 해당 없음 | 필터 인덱스 |
CHECK |
지원 | 8.0.16 부터 동작 | 지원 |
CHECK 옛 버전 |
해당 없음 | 8.0.16 전에는 무시 | 해당 없음 |
ON UPDATE 절 |
없음 | 지원 | 지원 |
| 함수 기반 유니크 | 함수 기반 유니크 인덱스 | ((식)) 유니크 인덱스 (8.0.13) |
계산 열 + 유니크 인덱스 |
함수 기반 유니크는 대소문자 무시 같은 요구에 씁니다. 아래 는 이름을 대문자로 바꾼 값이 중복되지 않게 합니다.
-- Oracle · Tibero
CREATE UNIQUE INDEX ux_emp_uname ON emp (UPPER(name));
-- MySQL (8.0.13 부터)
CREATE UNIQUE INDEX ux_emp_uname ON emp ((UPPER(name)));
-- MSSQL
ALTER TABLE emp ADD uname AS UPPER(name);
CREATE UNIQUE INDEX ux_emp_uname ON emp (uname);MySQL 8.0.16 이전에는 CHECK 를 써도 오류 없이 무시했습니다. 옛 버전에서 이관한 스키마는 제약이 실제로 걸려 있는지 확인합니다.
관련 레슨은 SQL 중급의 제약 레슨입니다.
번호를 자동으로 붙이는 방식입니다.
| 기능 | Oracle·Tibero | MySQL | MSSQL |
|---|---|---|---|
| 시퀀스 객체 | 있음 | 없음 | SEQUENCE (2012) |
| 자동 증가 컬럼 | IDENTITY (12c) |
AUTO_INCREMENT |
IDENTITY |
| 다음 번호 | seq.NEXTVAL |
자동 | NEXT VALUE FOR seq |
| 방금 번호 조회 | seq.CURRVAL |
LAST_INSERT_ID() |
SCOPE_IDENTITY() |
| 조회 조건 | 같은 세션에서 NEXTVAL 후 |
같은 연결 | @@IDENTITY 는 트리거 영향 |
| 재시작 후 카운터 | 시퀀스는 유지 | 8.0 부터 유지 | 유지 |
| 롤백 시 번호 | 되돌아가지 않음 | 되돌아가지 않음 | 되돌아가지 않음 |
시퀀스와 IDENTITY 는 롤백해도 번호가 되돌아가지 않아 구멍이 생깁니다. 구멍 없는 번호가 필요하면 채번 테이블을 씁니다.
UPDATE 를 먼저 실행해 행을 잠급니다.| 기능 | Oracle·Tibero | MySQL | MSSQL |
|---|---|---|---|
| 잠그며 읽기 | SELECT ... FOR UPDATE |
SELECT ... FOR UPDATE |
WITH (UPDLOCK, HOLDLOCK) |
| 한 문장 채번 | UPDATE ... RETURNING INTO |
LAST_INSERT_ID(식) |
UPDATE ... OUTPUT inserted.x |
| 0 채우기 | LPAD |
LPAD |
RIGHT('0000' + CAST(...)) |
한 문장 채번은 아래와 같습니다. 세 DB 모두 갱신과 조회가 한 번에 끝나 동시 실행에도 번호가 겹치지 않습니다.
-- Oracle · Tibero (PL/SQL 안)
UPDATE seq_tab SET last_no = last_no + 1
WHERE name = 'ORD'
RETURNING last_no INTO v_no;
-- MySQL
UPDATE seq_tab SET last_no = LAST_INSERT_ID(last_no + 1)
WHERE name = 'ORD';
SELECT LAST_INSERT_ID();
-- MSSQL
UPDATE seq_tab SET last_no = last_no + 1
OUTPUT inserted.last_no
WHERE name = 'ORD';LPAD 는 자릿수를 넘으면 앞이 아니라 뒤가 잘립니다(Oracle·MySQL). 자릿수를 넉넉히 잡습니다.
IDENTITY 컬럼에 큰 값을 직접 넣었을 때 Oracle 은 카운터가 따라가지 않아 이후 충돌할 수 있습니다. MySQL AUTO_INCREMENT 와 MSSQL IDENTITY 는 더 큰 값을 따라갑니다.
관련 레슨은 SQL 실무의 채번 레슨입니다.
인덱스 종류와 사용 여부를 확인하는 방법입니다.
| 기능 | Oracle·Tibero | MySQL | MSSQL |
|---|---|---|---|
| 클러스터드 | 힙 (IOT 는 예외) | InnoDB PK | PK 가 기본 클러스터드 |
| 함수 기반 | 지원 | ((식)) (8.0.13) |
계산 열 + 인덱스 |
INCLUDE 컬럼 |
없음 | 없음 | 지원 |
| 단일 컬럼과 NULL | NULL 을 저장하지 않음 | 저장함 | 저장함 |
| 커버링 표시 | BY INDEX ROWID 사라짐 |
Using index |
Key Lookup 사라짐 |
| Skip Scan | 지원 | 8.0.13 부터 | 없음 |
| 보조 인덱스 구조 | ROWID 를 가짐 | PK 를 포함 | 클러스터드 키를 포함 |
Oracle 의 단일 컬럼 B-tree 인덱스는 NULL 을 저장하지 않아 IS NULL 조건에 쓸 수 없습니다. 조회가 잦으면 복합 인덱스나 다른 표현을 씁니다.
인덱스가 실제로 쓰이는지 확인하는 방법과 임시로 숨기는 방법은 아래 표입니다.
| 기능 | Oracle·Tibero | MySQL | MSSQL |
|---|---|---|---|
| 사용 기록 조회 | MONITORING USAGE, DBA_INDEX_USAGE (12cR2) |
sys.schema_unused_indexes |
dm_db_index_usage_stats |
| 기록 초기화 | 해당 없음 | 재시작 후 초기화 | 재시작 후 초기화 |
| 인덱스 숨기기 | INVISIBLE (11g) |
INVISIBLE (8.0) |
DISABLE |
| 되돌리기 | VISIBLE 로 즉시 |
VISIBLE 로 즉시 |
REBUILD 필요 |
MSSQL 의 사용 기록은 재시작하면 초기화됩니다. 재시작 직후의 "사용 안 함" 을 보고 인덱스를 지우지 않습니다.
관련 레슨은 대용량·배치 03 인덱스 튜닝입니다.
큰 표를 조각으로 나누는 기능입니다.
| 기능 | Oracle·Tibero | MySQL | MSSQL |
|---|---|---|---|
| 방식 | RANGE·LIST·HASH |
RANGE·LIST·HASH |
범위만 |
| 자동 생성 | INTERVAL (11g) |
없음 | 없음 |
| 나누는 기준 정의 | PARTITION BY |
PARTITION BY |
파티션 함수 + 구성표 |
| 경계 방향 | VALUES LESS THAN |
VALUES LESS THAN |
RANGE LEFT·RIGHT |
| 조각 교체 | EXCHANGE PARTITION |
EXCHANGE PARTITION |
SWITCH |
| 조각 비우기 | TRUNCATE PARTITION |
TRUNCATE PARTITION |
TRUNCATE 의 WITH (PARTITIONS) (2016) |
| 프루닝 확인 | 실행 계획 | EXPLAIN 의 partitions 열 |
실행 계획 |
| 라이선스 | Enterprise 유료 옵션 | 기본 | 2016 SP1 부터 Standard 허용 |
제약이 DB마다 다릅니다.
UPDATE GLOBAL INDEXES 를 붙입니다.Oracle 의 INTERVAL 파티션은 아래 처럼 첫 조각만 정의하면 새 달의 데이터가 들어올 때 조각이 자동으로 생깁니다.
-- Oracle · Tibero
CREATE TABLE ord_log (
id NUMBER
, ord_date DATE
)
PARTITION BY RANGE (ord_date)
INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'))
(
PARTITION p0 VALUES LESS THAN (DATE '2026-01-01')
);파티션의 목적은 조각 단위 삭제·교체와 프루닝입니다. 조건에 파티션 키가 없으면 모든 조각을 읽습니다.
관련 레슨은 대용량·배치 05 파티션입니다.
시작과 종료 방식, 오류 시 동작이 다릅니다.
| 기능 | Oracle·Tibero | MySQL | MSSQL |
|---|---|---|---|
| 시작 | 첫 DML | START TRANSACTION, BEGIN |
BEGIN TRAN |
| 암묵적 시작 | 항상 | autocommit=0 이면 |
IMPLICIT_TRANSACTIONS ON |
| 자동 커밋 | 도구가 정함 | 켬 | 켬 |
| 도구별 예 | SQL*Plus 끔, JDBC 켬 | 해당 없음 | 해당 없음 |
| 저장점 | SAVEPOINT s |
SAVEPOINT s |
SAVE TRAN s |
| 저장점 롤백 | ROLLBACK TO [SAVEPOINT] s |
ROLLBACK TO [SAVEPOINT] s |
ROLLBACK TRAN s |
| 오류 시 기본 | 문장만 취소 | 문장만 취소 | 문장만 취소 |
| 전체 취소로 바꾸기 | 해당 없음 | 해당 없음 | XACT_ABORT ON |
| 기본 격리 | READ COMMITTED |
REPEATABLE READ |
READ COMMITTED |
Oracle 의 BEGIN 은 트랜잭션 시작이 아니라 PL/SQL 블록 시작입니다. 트랜잭션은 첫 DML 에서 시작합니다.
MySQL 의 REPEATABLE READ 스냅샷은 트랜잭션 시작 시점이 아니라 첫 읽기 시점에 만들어집니다.
저장점 사용은 아래와 같습니다. 저장점 이후만 취소하고 앞 작업은 유지합니다.
-- Oracle · Tibero, MySQL
SAVEPOINT s1;
ROLLBACK TO SAVEPOINT s1;
-- MSSQL
SAVE TRAN s1;
ROLLBACK TRAN s1;주의오류가 나도 기본은 그 문장만 취소되고 트랜잭션은 열려 있습니다. 앞 문장이 남은 채 커밋하지 않도록 애플리케이션이 롤백을 결정해야 합니다. MSSQL 은
SET XACT_ABORT ON으로 전체 취소로 바꿉니다.
관련 레슨은 SQL 실무의 트랜잭션 레슨입니다.
락 대기와 데드락 처리는 운영 중 가장 자주 부딪히는 차이입니다.
| 기능 | Oracle·Tibero | MySQL | MSSQL |
|---|---|---|---|
| 기본 대기 한도 | 무한 | 50초 | 무한 |
| 대기 지정 | NOWAIT, WAIT n |
innodb_lock_wait_timeout |
SET LOCK_TIMEOUT |
| 시간 초과 오류 | WAIT n 지정 시 오류 |
1205 | 1222 (설정 시) |
| 데드락 오류 | ORA-00060 |
1213 | 1205 |
| 데드락 롤백 범위 | 문장만 | 희생 트랜잭션 전체 | 희생 트랜잭션 전체 |
| 작업 큐 잠금 | SKIP LOCKED |
SKIP LOCKED (8.0) |
UPDLOCK + READPAST |
| FK 컬럼 인덱스 | 수동 | InnoDB 자동 | 수동 |
오류 번호가 같은 1205 라도 MySQL 은 시간 초과, MSSQL 은 데드락입니다. 오류 번호만 보고 재시도 로직을 공유하지 않습니다.
Oracle 은 데드락에서 걸린 문장만 롤백하고 트랜잭션은 남아 있습니다. 애플리케이션이 롤백하지 않으면 락이 계속 잡혀 있습니다.
FK 컬럼에 인덱스가 없을 때 Oracle 은 부모 행을 지우거나 키를 바꾸면 자식 표를 잠급니다. MySQL InnoDB 는 인덱스를 자동으로 만들어 주지만 MSSQL·Oracle 은 직접 만듭니다.
작업 큐처럼 잠긴 행을 건너뛰는 문법은 아래와 같습니다.
-- Oracle · Tibero, MySQL 8.0
SELECT
id
FROM job
WHERE status = 'WAIT'
ORDER BY id
FOR UPDATE SKIP LOCKED;
-- MSSQL
SELECT TOP (10)
id
FROM job WITH (UPDLOCK, READPAST)
WHERE status = 'WAIT'
ORDER BY id;Oracle 에서 FETCH FIRST 와 FOR UPDATE 를 같이 쓰면 ORA-02014 입니다. 또 ROWNUM 조건은 SKIP LOCKED 보다 먼저 적용되어 원하는 건수를 못 채울 수 있습니다.
MSSQL 은 LOCK_TIMEOUT 을 설정하지 않으면 무한 대기입니다. Oracle 도 기본은 무한이라, 운영 배치에는 대기 한도를 명시합니다.
관련 레슨은 대용량·배치 07 락과 데드락입니다.
계획을 보는 명령과 통계 수집, 힌트 형태입니다.
| 기능 | Oracle·Tibero | MySQL | MSSQL |
|---|---|---|---|
| 예상 계획 | EXPLAIN PLAN FOR + DBMS_XPLAN.DISPLAY |
EXPLAIN |
SHOWPLAN_TEXT, SSMS Ctrl+L |
| 실제 계획 | ALLSTATS LAST 형식 |
EXPLAIN ANALYZE (8.0.18) |
STATISTICS PROFILE, SSMS Ctrl+M |
| 실제 행 수 열 | A-Rows (예상은 E-Rows) |
EXPLAIN ANALYZE 출력 |
STATISTICS PROFILE 의 Rows |
| 트리 형식 | 해당 없음 | FORMAT=TREE (8.0.16) |
해당 없음 |
| 통계 수집 | DBMS_STATS |
ANALYZE TABLE |
UPDATE STATISTICS |
| 자동 수집 | 10g 부터 | 변경 10% 에서 자동 재계산 기본 | AUTO_UPDATE 기본 |
| 히스토그램 | DBMS_STATS 옵션 |
UPDATE HISTOGRAM (8.0) |
통계 객체에 늘 포함 |
| 힌트 형태 | /*+ ... */ |
USE INDEX, /*+ */ (5.7.7) |
WITH (INDEX()), OPTION () |
| 힌트 오류 | 조용히 무시 | 해당 없음 | 해당 없음 |
Oracle 에서 실제 행 수를 보려면 통계 수집 힌트를 붙여 실행한 뒤 커서 계획을 봅니다. 아래와 같습니다.
-- Oracle · Tibero
SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM orders;
SELECT * FROM TABLE(
DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST')
);예상 행 수(E-Rows)와 실제 행 수(A-Rows)가 크게 다르면 통계가 낡았거나 조건 추정이 틀린 것입니다.
DB별 통계 수집 명령은 아래와 같습니다.
-- Oracle · Tibero
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'ORDERS');
-- MySQL
ANALYZE TABLE orders;
ANALYZE TABLE orders UPDATE HISTOGRAM ON status WITH 16 BUCKETS;
-- MSSQL
UPDATE STATISTICS orders WITH FULLSCAN;주의비용 숫자는 DB 간 비교가 무의미합니다. 같은 쿼리의 계획 변화를 같은 DB 안에서 비교합니다. Oracle 힌트는 이름이 틀려도 오류 없이 무시되므로 계획을 다시 확인합니다.
바인드 값에 따라 계획이 달라지는 문제는 DB마다 이름이 다릅니다. Oracle 은 바인드 피킹(11g 적응형 커서 공유)이고, MSSQL 은 파라미터 스니핑입니다. MSSQL 은 RECOMPILE·OPTIMIZE FOR 로 다룹니다. Query Store 는 2016 부터입니다.
관련 레슨은 대용량·배치 01 실행 계획 읽기, 04 통계 정보와 힌트입니다.
대량 적재와 청크 처리, 공간 회수 문법입니다.
| 기능 | Oracle·Tibero | MySQL | MSSQL |
|---|---|---|---|
| 적재 도구 | SQL*Loader, APPEND |
LOAD DATA |
BULK INSERT, bcp |
여러 행 VALUES |
23ai | 지원 | 2008 (1000행) |
| JDBC 배치 | 드라이버 배치 | rewriteBatchedStatements=true |
드라이버 배치 |
| 청크 삭제 | ROWNUM, 키 범위 |
DELETE ... LIMIT |
DELETE TOP (n) |
| 청크 갱신 | 서브쿼리 안 FETCH FIRST |
UPDATE ... LIMIT |
UPDATE TOP (n) |
LIMIT 범위 |
해당 없음 | 단일 표만 | 순서 비보장 |
| 삭제 행 받기 | DELETE ... RETURNING INTO |
해당 없음 | DELETE ... OUTPUT deleted.* |
| 공간 회수 | SHRINK SPACE (10g), MOVE |
OPTIMIZE TABLE |
REBUILD |
청크 삭제는 한 번에 지우지 않고 잘라서 커밋합니다. 아래 은 DB별 한 번의 청크 삭제입니다.
-- Oracle · Tibero
DELETE FROM orders
WHERE status = 'DONE'
AND ROWNUM <= 10000;
-- MySQL
DELETE FROM orders
WHERE status = 'DONE'
LIMIT 10000;
-- MSSQL
DELETE TOP (10000) FROM orders
WHERE status = 'DONE';MySQL 의 DELETE ... LIMIT 은 단일 표에서만 되고, IN (서브쿼리) 안에는 LIMIT 을 쓸 수 없습니다. 서브쿼리의 FETCH FIRST 도 MySQL 에서는 안 됩니다.
Oracle 의 MOVE 는 인덱스를 UNUSABLE 로 만들어 다시 만들어야 합니다. MSSQL 의 SHRINKFILE 은 신중히 씁니다.
지울 양이 표의 대부분이면 DELETE 를 나누기보다 남길 행만 새 표에 복사하고 바꾸는 편이 빠릅니다. 청크 삭제는 일부만 지울 때 씁니다.
관련 레슨은 대용량·배치 06 대량 DML·배치 커밋, 08 대용량 삭제·아카이빙입니다.
프로시저 정의와 호출 문법입니다.
| 기능 | Oracle·Tibero | MySQL | MSSQL |
|---|---|---|---|
| 본문 형태 | IS BEGIN ... END |
BEGIN ... END |
AS BEGIN ... END |
| Tibero | tbPSM | 해당 없음 | 해당 없음 |
| 구분자 변경 | 해당 없음 | DELIMITER (클라이언트 명령) |
해당 없음 |
| 파라미터 | IN·OUT |
IN·OUT |
@p, 출력은 OUTPUT |
| 영향 행 수 | SQL%ROWCOUNT |
ROW_COUNT() |
@@ROWCOUNT |
| 예외 처리 | EXCEPTION WHEN |
DECLARE HANDLER |
TRY ... CATCH (2005) |
| 오류 발생 | RAISE_APPLICATION_ERROR |
SIGNAL SQLSTATE '45000' |
THROW (2012) |
| 다시 던지기 | RAISE |
RESIGNAL |
THROW; (CATCH 안) |
| 호출 | EXEC p 또는 BEGIN p; END; |
CALL p(@b) |
EXEC p |
| 교체 생성 | CREATE OR REPLACE |
없음 | CREATE OR ALTER (2016 SP1) |
RAISE_APPLICATION_ERROR 의 오류 번호는 -20000 부터 -20999 까지 씁니다.
SQL%ROWCOUNT·ROW_COUNT()·@@ROWCOUNT 는 직전 DML 의 영향 행 수입니다. 다른 문장이 끼면 값이 바뀌므로 DML 바로 다음에 읽습니다.
같은 일을 하는 프로시저를 아래 에서 DB별로 비교합니다. 부서 코드를 받아 인상하고 영향 행 수를 돌려줍니다.
-- Oracle · Tibero
CREATE OR REPLACE PROCEDURE raise_sal(p_dept IN VARCHAR2, p_cnt OUT NUMBER) IS
BEGIN
UPDATE emp SET sal = sal + 10 WHERE dept = p_dept;
p_cnt := SQL%ROWCOUNT;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001, '인상 실패');
END;
/
-- MySQL
DELIMITER //
CREATE PROCEDURE raise_sal(IN p_dept VARCHAR(10), OUT p_cnt INT)
BEGIN
UPDATE emp SET sal = sal + 10 WHERE dept = p_dept;
SET p_cnt = ROW_COUNT();
END //
DELIMITER ;
-- MSSQL
CREATE OR ALTER PROCEDURE raise_sal @dept VARCHAR(10), @cnt INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
UPDATE emp SET sal = sal + 10 WHERE dept = @dept;
SET @cnt = @@ROWCOUNT;
END;MySQL 에서 바이너리 로그를 켠 서버는 함수에 DETERMINISTIC 등의 특성을 지정해야 만들어집니다. MSSQL 은 SET NOCOUNT ON 을 빠뜨리면 영향 행 수 메시지가 결과로 섞이고, 스칼라 함수는 dbo. 를 붙여 호출합니다.
COMMIT 은 보통 프로시저 안이 아니라 호출한 쪽이 결정합니다. 프로시저가 마음대로 커밋하면 호출자가 여러 프로시저를 한 트랜잭션으로 묶을 수 없습니다.
관련 레슨은 SQL 실무의 프로시저 레슨입니다.
DDL·운영 쪽에서 실제로 틀렸다가 고친 사례입니다.
| 틀린 생각 | 실제 |
|---|---|
Oracle VARCHAR2(10) 은 10글자 |
기본은 10바이트, 한글이면 3글자 |
| DDL 은 롤백할 수 있다 | Oracle·MySQL 은 암묵적 커밋, MSSQL 만 롤백 가능 |
| UNIQUE 컬럼은 NULL 을 하나만 | Oracle·MySQL 은 여러 개, MSSQL 만 하나 |
MySQL 은 옛 버전에서도 CHECK 가 동작 |
8.0.16 이전에는 무시 |
Oracle 에도 ON UPDATE 절이 있다 |
없음 |
| 이름과 타입은 한 문장으로 변경 | MySQL 만 CHANGE COLUMN 한 번 |
| 롤백하면 채번 번호도 되돌아간다 | 시퀀스·IDENTITY 는 구멍이 생김 |
참조되는 표도 TRUNCATE 가능 |
FK 로 참조되면 불가 |
Oracle 은 NULL 도 인덱스에 저장 |
단일 컬럼 B-tree 는 저장 안 함 |
MySQL 기본 격리는 READ COMMITTED |
REPEATABLE READ |
| 오류가 나면 트랜잭션 전체가 취소 | 기본은 그 문장만 취소 |
| 1205 는 데드락 오류 | MySQL 1205 는 시간 초과, MSSQL 1205 가 데드락 |
| 데드락이면 Oracle 도 전체 롤백 | Oracle 은 문장만 롤백 |
| 모든 DB 가 FK 인덱스를 만들어 준다 | MySQL InnoDB 만 자동 |
DELETE ... LIMIT 은 서브쿼리에도 됨 |
MySQL 은 단일 표만, IN 서브쿼리 안 불가 |
| Oracle 힌트가 틀리면 오류가 난다 | 조용히 무시 |
EXPLAIN 비용으로 DB 끼리 비교 |
비용 숫자는 DB 간 비교 무의미 |
이 표의 대부분은 문법이 아니라 기본값과 기본 동작의 차이입니다. 새 DB 로 옮길 때는 대응표보다 이 기본값들부터 확인합니다.
지금까지 SQL 그룹 41개 레슨을 중급의 서브쿼리와 조인, 고급의 분석 함수와 계층, 실무의 이력과 이관, 대용량의 실행 계획과 락으로 이어 왔습니다. 이 세 장의 대응표는 그 내용을 세 DB 에서 다시 찾아볼 때 쓰는 색인이며, 헷갈리면 먼저 "한눈에 보기" 를 확인한 뒤 해당 절과 레슨으로 돌아가면 됩니다.