MySQL - CHECK 제약조건 - CHECK Constraint

Share

Last Updated on 8월 6, 2024 by Jade(정현호)

안녕하세요

이번 포스팅에서는 MySQL 8.0.16 버전에서 기능 도입된 CHECK 제약조건(CHECK Constraint) 에 대해서 확인해보도록 하겠습니다.

제약조건(Constraint) 이란

데이터베이스에서 제약조건(Constraints)은 데이터의 무결성(Integrity)을 보장하기 위해 사용되는 규칙이나 조건입니다.

제약조건은 데이터가 입력, 수정, 삭제될 때 특정 조건을 만족해야 한다는 규칙을 정의하여 데이터베이스의 신뢰성과 일관성을 유지합니다.

제약조건은 데이터의 품질을 높이고 오류를 방지하는 데 중요한 역할을 합니다.

주요 제약조건에는 다음과 같은 종류가 있습니다.

  • PRIMARY KEY (기본 키) 제약조건
  • FOREIGN KEY (외래 키) 제약조건
  • UNIQUE (고유) 제약조건
  • NOT NULL 제약조건
  • CHECK 제약조건
  • DEFAULT
                   
                   

CHECK 제약 조건

CHECK 제약 조건은 데이터베이스에서 데이터 무결성 제약 조건의 한 종류입니다.

CHECK 제약 조건은 행에 입력되는 값을 확인하기 위한 검색 조건을 지정합니다. 검색 조건의 결과가 테이블의 임의의 행에 대해 FALSE이면 제약 조건이 위반됩니다(단, 결과가 알 수 없거나(NULL) TRUE이면 그렇지 않음)

이를 통해 데이터의 일관성 및 정확성을 유지하고, 비즈니스 규칙을 데이터베이스 레벨에서 구현할 수 있습니다.
                       

MySQL 지원버전

MySQL에서는 8.0.16 버전 부터 CHECK 제약조건이 정책을 사용할 수 있게 기능이 추가되었습니다.

8.0.16 이전 버전의 MySQL에서는 CHECK 제약 조건을 생성 또는 추가하는 구문만 지원되며 실제로 제약 조건은 생성되거나 평가되지 않습니다. 즉 제약 조건 정의는 무시되었습니다.

DDL 구문에서는 호환성을 위해 오래 전부터 지원되어 왔지만 실제로 CHECK 제약 조건은 이전에 적용된 적이 없습니다.

• 8.0.15 버전

mysql> select version()
+-----------+
| version() |
+-----------+
| 8.0.15    |
+-----------+

mysql> create table t1 (c1 int check (c1 > 0));

mysql> show create table t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `c1` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

mysql> insert into t1 values (0);
Query OK, 1 row affected (0.00 sec)


• 5.7.44 버전
MySQL 5.7 버전에서도 CHECK 제약조건은 실제로 동작하지 않습니다.

mysql> select version();
+------------+
| version()  |
+------------+
| 5.7.44-log |
+------------+

mysql> create table t1 (c1 int check (c1 > 0));

mysql> show create table t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `c1` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8

mysql> insert into t1 values (0);
Query OK, 1 row affected (0.01 sec)


이와 같이 DDL SQL 구문은 지원되나 실제 CHECK 제약조건이 적용되지 않았으나 MySQL 8.0.16 버전에서 이 누락된 무결성 제약 기능이 추가되었습니다.

• 8.0.16 버전

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.16    |
+-----------+

mysql> create table t1(col1 int check(col1>0));

mysql> show create table t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `col1` int(11) DEFAULT NULL,
  CONSTRAINT `t1_chk_1` CHECK ((`col1` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

mysql> insert into t1 values (0);
ERROR 3819 (HY000): Check constraint 't1_chk_1' is violated.


5.7 버전과 8.0.15 버전에서 show create table 구문으로 조회시 보이지 않았던 CHECK 제약조건에 대한 내용이 8.0.16 버전에서는 보이는 것을 확인할 수 있습니다.

  `col1` int(11) DEFAULT NULL,
  CONSTRAINT `t1_chk_1` CHECK ((`col1` > 0)) <!!--
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

         

MySQL에서 CHECK 제약조건 사용

              

테이블 생성시

CHECK 제약조건 표준 생성 구문은 다음과 같습니다.

[CONSTRAINT [symbol]] CHECK (expr) [[NOT] ENFORCED]

CREATE TABLE 및 ALTER TABLE 문을 통해서 생성하거나 생성된 테이블에 대해서 CHECK 제약조건을 수정 및 추가합니다.

• 구문 예시 1

mysql> create table test(col1 int check(col1 > 0));

mysql> show create table test\G
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `col1` int(11) DEFAULT NULL,
  CONSTRAINT `test_chk_1` CHECK ((`col1` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

CHECK 제약 조건 설정시 CHECK 제약조건의 이름을 설정하는 것은 선택 사항입니다. 제약 조건 이름이 별도로 설정하지 않으면 MySQL은 이에 대한 이름을 자동으로 생성합니다.


• 구문 예시 2(제약조건 이름 명시)

mysql> drop table test;

mysql> create table test(col1 int CONSTRAINT chk_test_col1 check(col1>0));

mysql> show create table test\G
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `col1` int(11) DEFAULT NULL,
  CONSTRAINT `chk_test_col1` CHECK ((`col1` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci


CHECK 제약 조건은 기본적으로 적용(활성)됩니다. CHECK 제약 조건을 설정하지만 적용하지 않으려면 "NOT FORCED" 옵션을 사용해야 합니다.

• 구문 예시 3

mysql> drop table test;

mysql> create table test(
col1 int CONSTRAINT chk_test_col1 check(col1>0) NOT ENFORCED
);

mysql> show create table test\G
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `col1` int(11) DEFAULT NULL,
  CONSTRAINT `chk_test_col1` CHECK ((`col1` > 0)) /*!80016 NOT ENFORCED */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

          

테이블 레벨과 컬럼 레벨

CHECK 제약 조건은 테이블 제약 조건과 컬럼 제약 조건으로 나눠서 설정할 수 있습니다.

• 구문 예시

mysql> drop table test;

mysql> create table test
(  
  col1 int check (col1 > 5),
  col2 int check (col2 > 0),
  col3 int check (col3 < 30),  
  check (col1 > col3),
  check (col1 <> col2)  
);

mysql> show create table test\G
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `col1` int(11) DEFAULT NULL,
  `col2` int(11) DEFAULT NULL,
  `col3` int(11) DEFAULT NULL,
  CONSTRAINT `test_chk_1` CHECK ((`col1` > 5)),
  CONSTRAINT `test_chk_2` CHECK ((`col2` > 0)),
  CONSTRAINT `test_chk_3` CHECK ((`col3` < 30)),
  CONSTRAINT `test_chk_4` CHECK ((`col1` > `col3`)),
  CONSTRAINT `test_chk_5` CHECK ((`col1` <> `col2`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci


"check (col1 > col3)" 와 "check (col1 <> col2)" 는 테이블 레벨의 제약 조건입니다. 테이블내 여러 컬럼을 참조할 수 있습니다.

"col1 int check (col1 > 5)" 이와 같은 제약 조건은 컬럼 레벨의 제약 조건입니다. 각각 컬럼 내에서 정책을 정의하며 해당 컬럼만 참조할 수 있습니다.  
                     

ALTER 구문을 통한 추가 및 수정

생성된 테이블에 대해서는 ALTER TABLE 구문을 통해서 CHECK 제약조건을 추가하거나 수정할 수 있습니다.

CHECK 제약조건 표준 추가 구문은 다음과 같습니다.

ALTER TABLE <table_name>
ADD [CONSTRAINT [symbol]] CHECK (condition) [[NOT] ENFORCED]

• 구문 예시 1

mysql> drop table test;

mysql> create table test
(  
  col1 int,
  col2 int
);

mysql> alter table test add CHECK(col1>0),
add constraint chk_test_col2 check(col2 >5);

mysql> show create table test\G
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `col1` int(11) DEFAULT NULL,
  `col2` int(11) DEFAULT NULL,
  CONSTRAINT `chk_test_col2` CHECK ((`col2` > 5)),
  CONSTRAINT `test_chk_1` CHECK ((`col1` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

 

CHECK 제약조건 표준 수정 구문은 다음과 같습니다.

ALTER TABLE <table_name>
ALTER CHECK symbol [ NOT ] ENFORCED

• 구문 예시 2

mysql> show create table test\G
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `col1` int(11) DEFAULT NULL,
  `col2` int(11) DEFAULT NULL,
  CONSTRAINT `chk_test_col2` CHECK ((`col2` > 5)),
  CONSTRAINT `test_chk_1` CHECK ((`col1` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

mysql> alter table test alter check test_chk_1 not enforced;

mysql> show create table test\G
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `col1` int(11) DEFAULT NULL,
  `col2` int(11) DEFAULT NULL,
  CONSTRAINT `chk_test_col2` CHECK ((`col2` > 5)),
  CONSTRAINT `test_chk_1` CHECK ((`col1` > 0)) /*!80016 NOT ENFORCED */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

            

CHECK 제약조건 삭제

CHECK 제약조건을 삭제하기 위해서는 ALTER TABLE 구문을 통해서 삭제할 수 있습니다.

CHECK 제약조건 표준 삭제 구문은 다음과 같습니다.

ALTER TABLE <table_name>
         DROP CHECK symbol;

• 구문 예시

mysql> show create table test\G
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `col1` int(11) DEFAULT NULL,
  `col2` int(11) DEFAULT NULL,
  CONSTRAINT `chk_test_col2` CHECK ((`col2` > 5)),
  CONSTRAINT `test_chk_1` CHECK ((`col1` > 0)) /*!80016 NOT ENFORCED */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

mysql> alter table test drop check test_chk_1;

mysql> show create table test\G
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `col1` int(11) DEFAULT NULL,
  `col2` int(11) DEFAULT NULL,
  CONSTRAINT `chk_test_col2` CHECK ((`col2` > 5))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

         

복잡한 조건식 사용

MySQL의 CHECK 제약조건은 특정 컬럼 또는 컬럼 조합의 값이 지정된 조건을 충족해야 하는지 확인하는 데 사용할 수 있습니다. 즉, CHECK 제약조건에서는 복잡한 계산식이나 논리식을 포함할 수 있습니다.

• 구문 예시 1

mysql> drop table test;

mysql> create table test (
    col0 varchar(100) not null,
    col1 int,
    col2 int,
    check (
        col1 >= 0 and
        col2 >= 0 and col2 <= 100 and
        col1 <= col2 and
        col1+col2 <=150
    )
);


위의 CHECK 구문에는 다음과 같은 계산식 및 조건이 설정되어 있습니다.

  • col1은 0 이상이어야 합니다.
  • col2는 0에서 100 사이에 있어야 합니다.
  • col2 는 col1 보다 크거나 같아야 합니다.
  • col1 과 col2 합이 150 보다 같거나 작아야 합니다.


• 구문 예시 2
다른 형태로 논리식을 추가하는 또 다른 예를 살펴보도록 하겠으며 다음의 예시와 같이 CASE 구문도 사용할 수 있습니다.

mysql> drop table test;

mysql> create table test (
 col1 varchar(10),
 col2 int
);

-- CHECK 제약조건 추가 1
mysql> alter table test
add constraint chk_col1_col2_01
check (
    case when col1 = 'a' 
    then case when col2 > 0 then 1 else 0 end
        else 1
    end = 1
);
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

-- CHECK 제약조건 추가 2
mysql> alter table test
add constraint chk_col1_col2_02
check (
    case when col1 = 'b' 
        then case when col2 > 5 then 1 else 0 end
        else 1
    end = 1
);
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show create table test\G
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `col1` varchar(10) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `col2` int(11) DEFAULT NULL,
  CONSTRAINT `chk_col1_col2_01` CHECK (((case when (`col1` = _utf8mb4'a') then (case when (`col2` > 0) then 1 else 0 end) else 1 end) = 1)),
  CONSTRAINT `chk_col1_col2_02` CHECK (((case when (`col1` = _utf8mb4'b') then (case when (`col2` > 5) then 1 else 0 end) else 1 end) = 1))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

            

적용 대상 DML 및 CHECK 제약조건 조회

DML Operation으로 분류되는 INSERT, UPDATE, REPLACE, LOAD DATA, LOAD XML에 대해서 행 입력 및 변경시 제약 조건이 평가됩니다. 조건이 FALSE로 평가되면 오류가 보고됩니다
(단, IGNORE 절을 사용하면 경고가 발생되고 문제가 있는 행은 건너뛰게 됩니다)


설정된 CHECK 제약조건에 대한 조회는 INFORMATION_SCHEMA.CHECK_CONSTRAINTS 나 INFORMATION_SCHEMA.TABLE_CONSTRAINTS 을 통해서 조회할 수 있습니다.

• 구문 예시

mysql> drop table test;

mysql> create table test (col1 int check (col1 > 0));
Query OK, 0 rows affected (0.03 sec)
 
mysql> select * from information_schema.check_constraints;
+--------------------+-------------------+-----------------+--------------+
| CONSTRAINT_CATALOG | CONSTRAINT_SCHEMA | CONSTRAINT_NAME | CHECK_CLAUSE |
+--------------------+-------------------+-----------------+--------------+
| def                | test              | test_chk_1      | (`col1` > 0) |
+--------------------+-------------------+-----------------+--------------+

mysql> select * from information_schema.table_constraints where table_name='test';
+--------------------+-------------------+-----------------+--------------+------------+-----------------+----------+
| CONSTRAINT_CATALOG | CONSTRAINT_SCHEMA | CONSTRAINT_NAME | TABLE_SCHEMA | TABLE_NAME | CONSTRAINT_TYPE | ENFORCED |
+--------------------+-------------------+-----------------+--------------+------------+-----------------+----------+
| def                | test              | test_chk_1      | test         | test       | CHECK           | YES      |
+--------------------+-------------------+-----------------+--------------+------------+-----------------+----------+

              

CHECK 제약조건 expression rules

CHECK 제약조건 표현식은 다음의 규칙을 준수해야 하며, 표현식에 허용되지 않은 구문이 포함될 경우 에러가 발생합니다.

  • AUTO_INCREMENT 속성이 있는 컬럼을 제외하고, generated columns 과 Non-generated은 허용합니다.
  • Literals, deterministic 내장 함수 및 연산자에 허용됩니다. 하지만 비결정적(Non-deterministic) 내장 함수(예: AVG, COUNT, RAND, LAST_INSERT_ID, FIRST_VALUE, LAST_VALUE, CONNECTION_ID(), CURRENT_USER(), NOW() 등)는 허용되지 않습니다.
  • Stored 함수와 loadable 가능한 함수에서는 허용되지 않습니다.
  • Stored 프로시저 및 function 매개변수는 허용되지 않습니다.
  • 변수(시스템 변수, 사용자 정의 변수, 저장 프로그램 로컬 변수)는 허용되지 않습니다.
  • 서브 쿼리는 허용되지 않습니다.
  • CHECK 제약 조건에 사용되는 컬럼에서는 외래 키 참조 작업(ON UPDATE, ON DELETE)이 금지됩니다. 마찬가지로 외래 키 참조 작업에 사용되는 컬럼에는 CHECK 제약 조건이 금지됩니다.
                   

미지원 버전에서의 CHECK 제약조건 대체

MySQL 8.0.16 이전 버전(5.7 버전 포함) 에서의 CHECK 제약조건과 같은 기능을 사용하려면 다음과 같은 대체 수단을 통해서 유사하게 제약조건 기능을 사용할 수도 있습니다.

ENUM

ENUM 데이터 타입은 열거형 데이터 타입으로 테이블의 컬럼에서 사용할 수 있는 문자열 값의 집합을 미리 정의하여 사용합니다.
이는 컬럼에 허용되는 값을 제한하고 데이터 무결성을 유지하는데 도움이 됩니다.

• 구문 예시

mysql> create table test (
    size enum('small', 'medium', 'large') not null,
    color enum('red', 'green', 'blue') not null
);


위 테이블에서 size 컬럼은 'small', 'medium', 'large' 중 하나의 값을 가질 수 있으며, color 컬럼은 'red', 'green', 'blue' 중 하나의 값을 가질 수 있습니다.

값의 종류가 적은 코드성 데이터 성격이 담기는 컬럼이라면 ENUM 데이터 타입을 통해서 CHECK 제약조건과 유사하게 컬럼에 값을 제한할 수 있습니다. 

다만 CHECK 제약조건에서 col1>0 과 같이 숫자형이면서 값의 범위가 확정적이지 않다면 ENUM 데이터 타입으로는 대체는 불가능합니다.

또한 고려할 사항으로 ENUM 타입의 값을 추가하거나 변경은 테이블 구조의 변경(ALTER) 작업이 동반됩니다.
                

트리거 (Trigger)

트리거를 사용하여 삽입 또는 업데이트 작업이 수행될 때 특정 조건을 검사할 수 있습니다. 그리고 조건이 만족되지 않으면 오류를 발생시킬 수 있습니다.

트리거 조건 검사 타입 중에서 BEFORE INSERT, BEFORE UPDATE, BEFORE DELETE 를 통해서 데이터의 입력 및 변경이 수행되기전에 트리거 내 로직을 구현하여 값을 제한하면 CHECK 조건과 유사하게 사용할 수 있습니다.

다만 테이블에 트리거 생성시에 그에 따른 여러 단점이 존재함으로 가급적 고려 순위 중 후 순위로의 고려가 필요할 것으로 생각합니다.
              

애플리케이션 로직 구현

데이터 유효성 검사를 애플리케이션 코드에서 구현하여 수행하는 방법입니다. 데이터베이스에 데이터를 삽입하거나 업데이트하기 전에 애플리케이션 로직에서 입력 값을 검증하는 방법입니다.

이 방법은 데이터베이스의 성능에 영향을 주지 않으며 복잡한 로직을 구현할 수 있다는 장점이 있지만, 모든 클라이언트나 애플리케이션이 동일한 검증 로직을 구현해야 하므로 관리 측면에서 어려움이 있을 수 있습니다.


이외 다른 여러 방법을 통해서 CHECK 제약조건을 대신하여 값을 입력 및 변경을 제한하고 무결성을 지키기 위한 방법은 있을 것으로 생각됩니다.
              

CHECK 제약조건 사용시 고려사항

제약 조건은 데이터베이스에서 데이터의 무결성을 유지하기 위한 중요한 메커니즘 중 하나입니다.

제약 조건을 사용하면 데이터의 정확성과 일관성을 보장할 수 있지만, 이는 종종 성능 지연이라는 트레이드 오프를 수반합니다.

데이터 무결성은 데이터베이스 내의 데이터가 정확하고 일관된 상태를 유지하는 것을 의미하며, 이를 위해서 제약 조건(Constraints)이라는 규칙을 설정 통해 허용되지 않는 데이터가 삽입되거나 값이 변경되는 것을 방지합니다.

이러한 무결성을 유지하기 위해서는 추가적인 검증 작업(로직)이 필요하며, 제약 조건을 엄격하게 설정할수록, 무결성을 보장하는 데 필요한 연산이 늘어나고, 그 결과로 쿼리의 수행속도가 제약 조건을 사용하지 않았을 때에 비해 상대적으로 처리가 늦어 질수도 있습니다.

CHECK 제약조건 설정에서 간단한 조건 부터 위의 예시와 같이 복잡한 계산식이나 논리식이 포함될 수 있습니다.

설정한 CHECK 제약조건이 어떤 것인지에 따라서 또는 기존의 데이터가 얼마큼 있는지(row 수)에 따라서 SQL 처리 속도에서 늦어질 수도 있거나 또는 차이가 없을 수도 있습니다. 또는 테이블에 데이터가 입력이나 변경되는 방식에 따라서도 지연의 차이의 체감 여부도 달라질 수 있을 것입니다.

컬럼 레벨에서 복잡하지 않은 CHECK 제약조건 Condition 이 아닌 테이블 레벨의 CHECK 제약조건(컬럼과 컬럼 간의 비교 및 연산식 등) 인 테이블과 제약조건이 없는 테이블과의 대량 데이터 입력과 같은 테스트를 하였을 때 수행 시간이 어느정도 차이가 발생하는 것을 확인할 수 있었습니다.

CHECK 제약조건을 사용할 경우 어떤 조건이 어떻게 설정되었는지에 따라서 오버헤드가 발생될 수도 있다는 점도 고려가 필요한 점이라고 생각 합니다.


이번 글에서는 MySQL 8.0.16 버전에서 추가된 CHECK 제약조건에 대해서 확인해보았으며, 여기서 글을 마무리하도록 하겠습니다.

             

Reference

Reference URL
mysql.com/create-table-check-constraints


연관된 다른 글

 

 

 

 

               

0
글에 대한 당신의 생각을 기다립니다. 댓글 의견 주세요!x