MySQL Invisible Columns 을 사용한 table audit 기능 활용

Share

Last Updated on 3월 21, 2023 by Jade(정현호)

안녕하세요. 
이번 포스팅은 MySQL Invisible Columns 을 활용한 table audit 내용 기록과 관련된 내용을 살펴보도록 하겠습니다.

포스팅은 lefred.be 사이트에서 다룬 내용을 번역 및 테스트를 진행한 내용으로 작성되어 있습니다.

invisible columns

invisible columns 은 MySQL 8.0.23 버전 부터 추가된 기능으로 일반적으로 쿼리에 숨겨져 있지만 명시 적으로 컬럼을 지정하여 조회하는 경우 액세스 할 수 있는 컬럼 기능 입니다.

invisible columns 을 유용하게 사용할 수 있는 상황으로는 응용 프로그램이 "SELECT * FROM 테이블" 과 같은 유형의 쿼리를 사용하여 테이블에 액세스하고 있을 경우 invisible columns 기능을 사용하여 컬럼을 추가하여 테이블 구조를 변경 하더라도 응용 프로그램에서는 오류나 별도의 변경 없이 계속 작업을 진행 할 수 있습니다.

수정 사항이 반영되어 새로 배포되는 응용 프로그램에서는 필요한 경우 명시 적으로 컬럼명을 지정하여 invisible column을 일반 컬럼 처럼 사용할 수도 있습니다.

관련한 더 자세한 사용방법은 이전의 포스팅을 참조하시면 됩니다.

         

Audit 기능 활용

invisible columns 은 위에서 그리고 이전 포스팅에서 설명한 것과 같이 SELECT * FROM 과 같은 실행에서는 해당 컬럼이 조회 되지 않으며 명시적으로 invisible columns 을 기재해야 조회 및 변경 등이 가능 합니다

그래서 애플리케이션에서는 업무적으로 사용하는 특정 컬럼을 지정하여 사용하거나, 컬럼수가 적어서 SELECT * FROM 으로 조회시 invisible columns 은 조회가 되지 않아 여러가지로 활용할 수가 있습니다
(물론 업무 쿼리에서 SELECT * FROM 유형으로 쓰는 경우는 많지 않다고 생각함)

위의 내용과 같이 보통의 경우는 invisible columns 이 보이지 않기 때문에 Audit 정보를 담은 컬럼을 invisible columns 속성으로 만들어서 사용한다면 그것도 좋은 활용 방안 중에 하나라고 생각 됩니다.
        

일반 타입의 컬럼을 활용

포스팅에서는 일반 컬럼 타입을 활용한 방법과 그 다음에 JSON 타입을 활용한 방법에 대해서 설명하려고 합니다.
먼저 일반 유형 타입의 컬럼을 사용한 audit 기능 활용에 대해서 확인 해보도록 하겠습니다.

테스트 테이블 과 invisible columns 컬럼도 추가하도록 하겠습니다.

테이블 생성 및 데이터 적재

CREATE TABLE `tb_test_demo` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `firstname` varchar(20) DEFAULT NULL,
  `lastname` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;


insert into tb_test_demo(firstname,lastname)
values('jade','jung'),('ethan','miller');

위와 같이 생성을 하였으며,테이블 생성 후 2건의 데이터를 입력 하였습니다.

audtit 정보를 담기 위한 invisible columns 속성의 컬럼을 4개 추가 하도록 하겠습니다.

alter table tb_test_demo 
         add created_at timestamp 
             default current_timestamp invisible, 
         add created_by varchar(60) not null 
             default '' invisible,
         add updated_at timestamp 
             on update current_timestamp invisible, 
         add updated_by varchar(60) not null 
             default '' invisible ;

각 컬럼에 저장되는 데이터의 속성은 아래와 같습니다.

created_at : 레코드가 추가(insert) 될때의 timestamp 정보
created_by : 레코드 추가(insert) 되었을 때 작업 실행한 유저명
updated_at : 레코드가 갱신(update) 될때의 timestamp 정보
updated_by : 레코드를 마지막 갱신할 때 수행한 유저명

입력된 데이터는 아래와 같습니다.

select * from tb_test_demo;
+----+-----------+----------+
| id | firstname | lastname |
+----+-----------+----------+
|  1 | jade      | jung     |
|  2 | ethan     | miller   |
+----+-----------+----------+

invisible columns 속성의 컬럼은 SELECT * 로는 출력 되지 않습니다

데이터 변경시 기록을 남길 수 있도록 trigger 를 생성하도록 하겠습니다.

create trigger demo_insert before insert on tb_test_demo 
        for each row
            set new.created_by=session_user();

            
create trigger demo_update before update on tb_test_demo 
        for each row
            set new.updated_by=session_user();


지금 상태에서 조회를 아래와 같은 결과를 확인 할 수 있습니다.

select *, created_at, created_by, updated_at, updated_by 
from tb_test_demo;

+----+-----------+----------+---------------------+------------+------------+------------+
| id | firstname | lastname | created_at          | created_by | updated_at | updated_by |
+----+-----------+----------+---------------------+------------+------------+------------+
|  1 | jade      | jung     | 2022-04-17 20:59:24 |            | NULL       |            |
|  2 | ethan     | miller   | 2022-04-17 20:59:24 |            | NULL       |            |
+----+-----------+----------+---------------------+------------+------------+------------+


다시 데이터 1건을 입력해보겠습니다.

insert into tb_test_demo (firstname, lastname) 
       values ('liam', 'harris');


다시 SELECT * 로 조회해보겠습니다.

select * from tb_test_demo;
+----+-----------+----------+
| id | firstname | lastname |
+----+-----------+----------+
|  1 | jade      | jung     |
|  2 | ethan     | miller   |
|  3 | liam      | harris   |
+----+-----------+----------+


그 다음에는 invisible columns 속성 컬럼을 포함아여 모든 컬럼을 조회를 해보도록 하겠습니다.

select *, created_at, created_by, updated_at, updated_by 
from tb_test_demo;
+----+-----------+----------+---------------------+---------------------+------------+------------+
| id | firstname | lastname | created_at          | created_by          | updated_at | updated_by |
+----+-----------+----------+---------------------+---------------------+------------+------------+
|  1 | jade      | jung     | 2022-04-17 20:59:24 |                     | NULL       |            |
|  2 | ethan     | miller   | 2022-04-17 20:59:24 |                     | NULL       |            |
|  3 | liam      | harris   | 2022-04-17 21:15:41 | test_user@localhost | NULL       |            |
+----+-----------+----------+---------------------+---------------------+------------+------------+

3번째 입력한 firstname 이 liam 인 유저의 레코드에는 creadted_by 정보가 표기 되는 것을 확인 할 수 있습니다.
created_at 과 updated_at은  default current_timestamp 속성으로 입력이나 갱신시 내용이 채워지게 됩니다.

이번에는 UPDATE 후 조회를 해보겠습니다.

update tb_test_demo 
set firstname='daniel' 
where id=2;


select *, created_at, created_by, updated_at, updated_by 
from tb_test_demo;
+----+-----------+----------+---------------------+---------------------+---------------------+---------------------+
| id | firstname | lastname | created_at          | created_by          | updated_at          | updated_by          |
+----+-----------+----------+---------------------+---------------------+---------------------+---------------------+
|  1 | jade      | jung     | 2022-04-17 20:59:24 |                     | NULL                |                     |
|  2 | daniel    | miller   | 2022-04-17 20:59:24 |                     | 2022-04-17 21:17:42 | test_user@localhost |
|  3 | liam      | harris   | 2022-04-17 21:15:41 | test_user@localhost | NULL                |                     |
+----+-----------+----------+---------------------+---------------------+---------------------+---------------------+

두번째 유저인 lastname 이 miller 유저의 first_name 을 갱신하였으며, 갱신 시간과 갱신을 실행한 유저의 정보가 기록된 것을 확인할 수 있습니다.

다른 DB 유저로 INSERT 실행 후 조회해 보도록 하겠습니다.

insert into tb_test_demo (firstname, lastname)
       values ('benjamin', 'martin');


select id, firstname, created_by, updated_by 
from tb_test_demo;
+----+-----------+---------------------+---------------------+
| id | firstname | created_by          | updated_by          |
+----+-----------+---------------------+---------------------+
|  1 | jade      |                     |                     |
|  2 | daniel    |                     | test_user@localhost |
|  3 | liam      | test_user@localhost |                     |
|  4 | benjamin  | root@localhost      |                     | <-!!
+----+-----------+---------------------+---------------------+

first_name 이 benjamin 이라는 유저를 입력하였으며 입력을 실행한 유저가 이번에는 root@localhost 로 기록된 것을 확인 할 수 있습니다.

마지막으로 SELECT * 로 조회하면 invisible columns 속성 컬럼이 출력이 되지 않는 것을 확인 할 수 있습니다.

select *
    -> from tb_test_demo;
+----+-----------+----------+
| id | firstname | lastname |
+----+-----------+----------+
|  1 | jade      | jung     |
|  2 | daniel    | miller   |
|  3 | liam      | harris   |
|  4 | benjamin  | martin   |
+----+-----------+----------+


이와 같이 일반적으로 출력이 되지 않는 invisible columns 속성 컬럼을 사용하여 이와 같이 audit 정보를 담아두는 방법도 invisible columns 을 활용하는 한가지 방법이며, 컬럼의 구성과 트리거의 작성 내용에 따라서 더 많은 또는 유용한 정보를 남길수도 있습니다.
           

JSON 타입을 활용

MySQL 5.7.8 버전 부터 native JSON data type 을 지원하며, 그에 따라서 하나의 레코드내에서 여러가지 타입의 값이 있는 형태로 저장할 수 있게 되었으며, native JSON data type 을 지원함에 따라서 "문서 유효성 검사", "효율적인 액세스 지원", "성능", "편리성" 등의 기능을 제공하며, json_extract 나 json_pretty 와 같은 JSON 타입을 위한 함수도 지원되고 있습니다.

이러한 JSON 을 활용하는 예제를 살펴보도록 하겠습니다. 먼저 이전에 사용한 테이블을 삭제후 다시 생성 하도록 겠습니다.



테이블 재생성

drop table tb_test_demo;

CREATE TABLE `tb_test_demo` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `firstname` varchar(20) DEFAULT NULL,
  `lastname` varchar(20) DEFAULT NULL,
  `audit_info` json DEFAULT NULL invisible,
     -- <--!!! 
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

이번에는 테이블 생성시 JSON 타입의 컬럼을 추가하였습니다(audit_info)

이번에는 트리거 2개를 생성을 진행하도록 하겠으며 하나는 다소 복잡한 내용을 담고 있습니다.


트리거 생성

create trigger tb_test_demo_insert before insert on tb_test_demo 
         for each row 
          set new.audit_info=json_object('created_at', now(), 
                               'created_by', session_user());


DELIMITER $$
create trigger tb_test_demo_update before update on tb_test_demo 
         for each row
         BEGIN
            declare modif json default null;            
            declare modif_arr json default null;           
            select json_object('updated_at', now(), 
                    'updated_by', session_user()) into modif;
            if old.id <> new.id then
               select json_merge_patch(modif, 
                      json_object('id', 
                        json_object("old", old.id, 
                                    "new", new.id)
                      )
               ) into modif;
            end if;
            if old.firstname <> new.firstname then
               select json_merge_patch(modif,
                      json_object('firstname', 
                        json_object("old", old.firstname, 
                                    "new", new.firstname)
                      )
               ) into modif;
            end if;
            if old.lastname <> new.lastname then
               select json_merge_patch(modif, 
                      json_object('lastname', 
                        json_object("old", old.lastname, 
                                    "new", new.lastname)
                      )
               ) into modif;
            end if;

            if json_extract(old.audit_info, "$.modifications") 
               is NULL then
              set new.audit_info=json_merge_patch(old.audit_info,
                  json_set(old.audit_info, 
                           "$.modifications", modif));              
            else                
              set new.audit_info=json_merge_patch(old.audit_info,
                  json_array_append(old.audit_info,
                           "$.modifications", modif));
            end if; 
END$$

DELIMITER ;


이제 데이터 2건을 추가 후에 조회를 해보도록 하겠습니다.

-- 데이터 INSERT
insert into tb_test_demo(firstname,lastname)
values('jade','jung'),('ethan','miller');


-- json_pretty 을 통해 JSON 조회
select *,json_pretty(audit_info) 
from tb_test_demo\G

******************** 1. row ********************
                     id: 1
              firstname: jade
               lastname: jung
json_pretty(audit_info): {
  "created_at": "2022-04-17 22:50:32.000000",
  "created_by": "test_user@localhost"
}
******************** 2. row ********************
                     id: 2
              firstname: ethan
               lastname: miller
json_pretty(audit_info): {
  "created_at": "2022-04-17 22:50:32.000000",
  "created_by": "test_user@localhost"
}

생성한 트리거에 동작에 따라서 데이터 입력에 대한 정보가 JSON 타입의 audit_info 컬럼에 기록이 된 것을 확인 할 수 있습니다.

이제 데이터를 업데이트 후에 다시 한번 audit_info 컬럼을 조회해 보도록 하겠습니다.

-- 데이터 갱신
update tb_test_demo 
set firstname='daniel' 
where id=2;


-- json_pretty 을 통해 JSON 조회
mysql> select *,json_pretty(audit_info) 
from tb_test_demo\G
********************* 1. row *********************
                     id: 1
              firstname: jade
               lastname: jung
json_pretty(audit_info): {
  "created_at": "2022-04-17 22:50:32.000000",
  "created_by": "test_user@localhost"
}
********************* 2. row *********************
                     id: 2
              firstname: daniel
               lastname: miller
json_pretty(audit_info): {
  "created_at": "2022-04-17 22:50:32.000000",
  "created_by": "test_user@localhost",
  "modifications": {
    "firstname": {
      "new": "daniel",
      "old": "ethan"
    },
    "updated_at": "2022-04-17 22:50:47.000000",
    "updated_by": "test_user@localhost"
  }
}


이 테이블 역시도 SELECT * 로 조회를 하여도 invisible columns 컬럼이기 때문에 출력은 되지 않습니다

select * from tb_test_demo;
+----+-----------+----------+
| id | firstname | lastname |
+----+-----------+----------+
|  1 | jade      | jung     |
|  2 | daniel    | miller   |
+----+-----------+----------+


위에서 확인 되는 내용과 같이 두번째 생성한 트리거인 "tb_test_demo_update" 에 의해서 UPDATE 실행시 보다 많은 정보가 남겨진 것을 확인 할 수 있습니다.

audit 또는 데이터 변경의 tracking 관점에서 변경시 많은 정보가 필요할 것 입니다. 그에 따라서 이와 같이 JSON 을 통해서 다양한 정보를 남기는 것도 invisible columns 속성 컬럼을 사용하는 좋은 방법이라고 생각 됩니다.
          

Reference

Reference URL
lefred.be/table-audit-information
lefred.be/table-audit-information-part2
dev.mysql.com/json


관련된 다른 글

 

 

 

 

 

 

 

         

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