실행중인 프로세스가 갑자기 죽으며 Core 파일이 생성

Core 파일이 계속 생성되는 경우 Disk Full로 장애 발생

Core 파일 분석 후 이슈를 해소하여 시스템 안정성 확보 

Core 파일 생성을 위한 설정

1. ulimit -c unlimited
2. ulimit -a 로 확인
- 'core file size          (blocks, -c) unlimited' 면 설정 완료
- 만약 core file의 size를 제한하고 싶다면 'unlimited'가 아닌 kb단위의 size를 지정해주면 된다.

분석 방법

1. debug 대상 파일 확인

프로그램이 여러개가 돌고 있는 환경이라면 특정 dump가 어떤 프로그램이 만든것인지 확인 필요

file [core dump file]

* /proc/sys/kernel/core_pattern 에서 정의된 형태로 생성

   %p : pid

   %u : uid

   %g : gid

   %s : signal number

   %t : dump time(unix)

   %h : hostname

   %e : process

 

2. debugging

gdb [core발생 프로그램] [core file]

이후, gdb interface로 화면이 전환되면서 core에 관한 간략한 정보가 뜬다.
그리고 다음의 command를 입력하면 상세한 정보를 확인하는데 도움을 준다.

- bt(back trace) : core 발생 직전의 call stack 확인
- bt full : 전체 call stack 확인
- f[스택프레임 번호] : 특정 stack 확인
- list : stack의 code 보기
- info local : stack의 수행 당시 변수 값 확인
- info arg : stack의 argument 확인
- where : 간략화


의심되는 stack frame 분석

 info frame : 보다 상세한 정보 제공

 stack frame at 0x : 해당 함수 sp

 called by frame at 0x : 해당 함수를 호출한 함수의 SP(ex. main)

 Arglist at 0x : 해당 함수가 호출하는 함수의 bp

 args : 인자 정보

 saved registers : rip, ebp..

   (EX) (gdb) f [frame num] + (gdb) info frame

         (gdb) info frame [frame num]

 

해당 stack의 argument/local value 확인

 info frame 보다 간결하지만 인간친화적

 

  (EX) (gdb) info arg

        (gdb) info local

 

3. process debugging

gdb [프로세스명] [pid]

실행중인 프로세스를 gdb로 붙어서 확인

 

참고

https://incredible-larva.tistory.com/entry/core-dump-%EB%B6%84%EC%84%9D%EC%9D%84-%EC%9C%84%ED%95%9C-gdb-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC

https://blog.naver.com/PostView.nhn?isHttpsRedirect=true&blogId=hymne&logNo=221841282483 

현상

threads 및 connection 증가

mysqldump FLUSH TABLES 지속

해결방안

* 고비용 쿼리 실행 시점과 backup 실행 시점을 겹치지 않도록 조정

* master 장비에서 백업이 실행되고 있다면, 이중화 구성 후 Slave 장비에서 백업 실행

* 고비용 쿼리 튜닝

* 장비 성능 점검 후 메모리 증설
* Flush method 변경 및 Swap 강제 해제

# /etc/my.cnf
innodb_flush_method = O_DIRECT
 
# shell
swapoff -a
swapon -a

* Kernel parameter 변경

# /etc/sysctl.conf
vm.swappiness = 60 -> 0
 
# shell
swapoff -a
swapon -a

FOUND_ROWS 적용 전

페이징 처리를 위해 limit 사용 시 전체 게시물의 수를 알기 위해 쿼리를 한번 더 해야 한다.

 

1. 해당 조건을 만족하는 게시물의 전체 수 조회
2. 그 페이지에서 보여줄 내용 조회

FOUND_ROWS 적용 후

SQL_CALC_FOUND_ROWS & FOUND_ROWS

 

SELECT 최상단에 SQL_CALC_FOUND_ROWS 라는 옵션만 주고 FOUND_ROWS() 를 사용하면 된다.

쿼리는 마찬가지로 두번이지만 두 번째 쿼리는 훨씬 빠르다.

mysql> SELECT SQL_CALC_FOUND_ROWS * FROM tbl_enchovy WHERE id < 100 LIMIT 10;
mysql> SELECT FOUND_ROWS();

첫번째 쿼리 검색 결과 : 10개

두번째 쿼리 카운트 값 : WHERE조건에 해당되는 Rows 카운트 ( 1~100사이에 누락된 번호가 없다면 99을 리턴 )

 

주의사항

1. SQL_CALC_FOUND_ROWS 키워드는 UNION의 첫 번째 SELECT에 나타나야 한다.
2. FOUND_ROWS()의 값은 UNION ALL을 사용하는 경우에만 정확합니다. ALL이 없는 UNION을 사용하면 중복 제거가 발생하고 FOUND_ROWS()의 값은 근사치만 된다.
3. UNION에 LIMIT가 없는 경우 SQL_CALC_FUND_ROWS는 무시되고 UNION을 처리하기 위해 생성된 임시 테이블의 행 수를 반환한다.

4. 복제시) FOUND_ROWS() 은 statement-based 형태로는 안정적으로 복제되지 않습니다, 이경우 row-based 형태로 자동 변경되어 복제가 됩니다.

 

※ MySQL 4.7.16 이전 버전에는 버그가 있어, 원하는 결과를 리턴하지 않음

* 5.7.16 이전 버전의 Query중 SybQuery가 있다면, SubQuery 내의 결과 Row 수를 FOUND_ROWS()로 Retrun
* 5.7.16 버그 픽스 이후는 전체쿼리문을 하나의 구문으로 보고 결과 ROW수를 FOUND_ROWS()로 return
  FOUND_ROWS() 함수의 기능은 이전 SELECT 된 쿼리의 ROWS를 RETURN 하는 함수(즉, 5.7버전의 기능이 정확)

    

FOUND_ROWS() 함수는 MySQL 8.0.17에서 더 이상 사용되지 않으며 향후 버전의 MySQL에서 제거될 것으로 예상된다..

참고

https://string.tistory.com/93

에러 로그

Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: ‘log event entry exceeded max_allowed_packet; Increase max_allowed_packet on master; the first event ‘binlog.000201’ at 5480571

분석

slave max_allowed_packet size (  1 M )

max_allowed_packet
서버에 전달 또는 서버로부터 받게 되는 패킷의 최대 길이
MySQL에서의 Packet이라 함은 Single SQL 문 또는 Replication이 이루어지는 Master에서 Slave로의 Binary Log Event를 의미한다.
MySQL 서버의 DEFAULT max_allowed_packet size 는 1 M 이다.

에러 로그는 master의 bin log packet이 허용할 수 있는 최대치를 초과했다는 에러 내용

해결 방법

mysql> set global max_allowed_packet = 1024*1024*100
mysql> set session max_allowed_packet = 1024*1024*100

재시작시 설정값 저장을 위해 my.cnf 파일도 함께 수정

현상

Windows Cluster 서비스 시작이 되지 않을때

에러 로그

The Cluster service cannot be started. An attempt to read configuration data from the Windows registry failed with error '2'. Please use the Failover Cluster Management snap-in to ensure that this machine is a member of a cluster. If you intend to add this machine to an existing cluster use the Add Node Wizard. Alternatively, if this machine has been configured as a member of a cluster, it will be necessary to restore the missing configuration data that is necessary for the Cluster Service to identify that it is a member of a cluster. Perform a System State Restore of this machine in order to restore the configuration data.

 Cluster Service 시작시 에러

해결

administration 권한으로 Cmd 오픈 후, 아래 명령어 실행하여 클러스터 초기화 진행

Cluster Node ServerName /ForceCleanup

아래 명령어로 클러스터 추가

Start-DatabaseAvailabilityGroup DAG1 -MailboxServer SiteA-Mbx1

해소되지 않는 경우 클러스터 신규 구성을 추천..

 

참고

http://www.lab365.in/2017/03/the-cluster-service-cannot-be-started.html

현상

MySQL 재시작 후 Table 내 AUTO_INCREMENT 값이 감소하는 현상이 발생

(예. 재시작 전: Auto_Increment 4000 , 재시작 후 Auto_Increment 3800)

원인

요약: MySQL InnoDB에서는 마지막 AUTO_INCREMENT을 메모리에만 저장

상세:

MySQL에서 InnoDB 엔진의 경우 AUTO_INCREMENT 값을 메모리에 저장하고 Insert 시 증가

 * 이슈 발생의 경우 tb_user 에서 data를 삭제 시 동일한 구조의 tb_user_del 값으로 insert 후 삭제하게 되는데

Table 의 AUTO_INCREMENT 값이 4000인 상태에서 SEQ가 3800~4000까지의 data를 tb_user_del 로 insert하고 삭제한 후 MySQL를 재시작하게 되면서 또 다시 SEQ가 3800부터 insert됨
(SEQ는 tb_user와 tb_user_del 모두 PK로 설정됨) 

재시작 이후 SEQ가 3800~4000인 data를 tb_user_del에 insert하면서 PK충돌이 일어나게 됨

해결 방법

AUTO_INCREMENT 를 MySQL 재시작시에도 유지하기 위해서는 엔진을 MyIsam으로 변경하거나 마지막 AUTO_INCREMENT값을 따로 기록을 하여야 함.

(발생한 이슈 해결을 위하여, tb_user_del의 PK를 nonclustered index로 변경하여 PK충돌을 해결하였음)

 

MySQL 성능 최적화 작업 중 하나로 DISK I/O를 줄이는 방법중 하나

파일 포멧을 Barracuda로 설정

Barracuda 이전의 파일 포멧은 Antelope에는 Row 포멧이 'Compact'만 제공되었으나,

Barracuda 파일 포멧에서는 'Compact'이외에 'Dynamic','Compressed'이 추가됨.(기본은 'Compact')

 

Row 포맷에 따라 가변 길이 컬럼(VARCHAR, TEXT, BLOB)의 처리와 압축 기능 사용이 결정된다.
InnoDB에서는 VARCHAR, TEXT, BLOB을 완전히 동일한 타입으로 인식하고 처리하는 점 참고하자.

Compact  가변 길이 컬럼의 767 byte 까지만 B-Tree에 저장하고, 나머지는 Off-Page에 저장
Dynamic  Page 사이즈와, Row 사이즈를 고려하여, 길이가 긴 컬럼들은 전체를 Off-Page에 저장하되, B-Tree에는 20 byte의 포인터만 저장
Compressed  Dynamic과 거의 유사하지만 KEY_BLOCK_SIZE 옵션을 통해 16KB의 데이터 Page를 압축 가능
 (KEY_BLOCK_SIZE = 1, 2, 4, 8, 16 기본값 : 8)
 KEY_BLOCK_SIZE = 16인 경우 기본적으로 데이터 Page를 압축하지 않지만, VARCHAR, TEXT, BLOB 컬럼만 선택적으로 압축

기존 Compact Row 포맷에서 VARCHAR, TEXT, BLOB 컬럼을 많이 포함하고 있는 테이블의 경우, 데이터 Page가 VARCHAR, TEXT, BLOB 데이터들로 채워져서, Page 내 의미있는 데이터의 밀도가 낮아지고 Range 스캔시 읽어야 할 Page가 많아져 성능 저하를 일으키는 문제가 있었다.
하지만 Barracuda 파일 포맷에서는 이런 테이블들을 선택적으로 Dynamic 또는 Compressed로 변경해서 I/O를 최적화 할 수 있는 장점이 있다.

 

my.cnf
innodb_file_per_table
innodb_file_format=barracuda
동적으로 MySQL 서버에 설정
SET GLOBAL innodb_file_format=barracuda;
SET GLOBAL innodb_file_per_table=ON;
테이블 생성시 압축 옵션 추가
CREATE TABLE tblTest_compressed
(
  id int(11) NOT NULL AUTO_INCREMENT,
  aa int(11) DEFAULT NULL,
  bb varchar(100)  DEFAULT NULL,
  PRIMARY KEY (id)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
 

참고

http://jason-heo.github.io/mysql/2014/03/05/innodb-barracuda.html

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=seuis398&logNo=70094098699

MySQL Blocking Query 

Version 5.7

select blocking.trx_mysql_thread_id                       as blocking_processlist_id
      ,max(proc.USER)                                          as blocking_user
      ,max(proc.HOST)                                          as blocking_host
      ,max(proc.DB)                                            as blocking_db
      ,max(ifnull(blocking.trx_query, 'Null'))                 as blocking_query
      ,max(TIMESTAMPDIFF(second, blocking.trx_started, now())) as blocking_started_sec
      ,max(ifnull(blocking.trx_started, 'Null'))               as blocking_started_time
      ,max(blocking.trx_state)                                 as blocking_state
      ,'Null'                                             as blocking_command
      ,max(concat(blocking.trx_id, ' (', ifnull(waiting.blocking_lock_id, 'Null'), ')')) as trx_id
      ,max(ifnull(lock_info.lock_mode, 'Null'))                                          as lock_mode
      ,max(ifnull(lock_info.lock_type, 'lock')) as lock_type
      ,if(max(waiting_info.trx_started is not null)
      ,concat('\nlock waiting into : ', count(*)
      ,substring(group_concat('\n\t'
                 ,'waiting ', now() - waiting_info.trx_started, '(sec) : '
                 ,'query -> ', waiting_info.trx_query
                  order by waiting_info.trx_started), 1, 1000))
      ,'\nlock waiting into : Null') as trx_info
from information_schema.INNODB_TRX blocking
left join information_schema.INNODB_LOCK_WAITS waiting on waiting.blocking_trx_id = blocking.trx_id
left join information_schema.INNODB_TRX waiting_info on waiting_info.trx_id = waiting.requesting_trx_id
left join information_schema.INNODB_LOCKS lock_info on lock_info.lock_trx_id = blocking.trx_id
inner join information_schema.processlist proc on proc.id = blocking.trx_mysql_thread_id
where blocking.trx_state = 'RUNNING'
and blocking.trx_requested_lock_id is null
and blocking.trx_wait_started is null
group by blocking.trx_mysql_thread_id ;

MySQL Status Check Query

 

Version 5.7 & 8.0

select concat('LongQuery', '::', count(*)) as current_value
from information_schema.processlist proc
where proc.command not in ('sleep', 'daemon', 'binlog dump', 'killed')
and proc.id <> connection_id()
and proc.time > 10
union all
select concat('LongTransaction', '::', count(*)) as current_value
from information_schema.innodb_trx trx
where TIMEDIFF(now(), trx.trx_started) > '00:01:00'
union all
select concat('MetaLock', '::', count(*)) as current_value
from performance_schema.metadata_locks ml
where ml.lock_status = 'PENDING'
union all
select concat('LockWait', '::', count(*)) as current_value
from information_schema.INNODB_TRX trx
where trx.trx_state = 'LOCK WAIT'
and trx.trx_wait_started <= date_sub(now(), interval 3 second) ;

MySQL Long Query

Version 5.7 & 8.0

select proc.time, proc.command, proc.id
     , proc.user, proc.host, proc.db, proc.info
from information_schema.processlist proc
where proc.command not in ('sleep', 'daemon', 'binlog dump', 'killed')
and proc.id <> connection_id()
and proc.time > 10
order by proc.time desc ;

MySQL Long Transaction Query

Version 5.7 & 8.0

select trx_mysql_thread_id as 'id'
     , proc.user
     , proc.host
     , proc.command
     , trx.trx_state
     , concat(trx_started, '') as 'start_time'
     , concat(TIMEDIFF(now(), trx_started), '') as 'diff_time'
     , ifnull(trx_query, '.......') as 'query_info'
     , ( select ifnull(esc.SQL_TEXT, '.......')
         from performance_schema.threads thread
       , performance_schema.events_statements_current esc
         where thread.processlist_id = trx.trx_mysql_thread_id
         and esc.thread_id = thread.thread_id) as 'query_info2'
from information_schema.innodb_trx trx
    ,information_schema.processlist proc
where proc.id = trx.trx_mysql_thread_id
and trx.trx_query is null
and TIMEDIFF(now(), trx.trx_started) > '00:01:00'
order by trx_started ;

MySQL Meta Lock Query

Version 5.7 & 8.0

select th.thread_id as 'blocking_thread_id'
     , th.processlist_id as 'blocking_id'
     , th.processlist_user as 'blocking_user'
     , th.processlist_host as 'blocking_host'
     , th.processlist_db as 'blocking_db'
     , th.processlist_command as 'blocking_command'
     , th.processlist_time as 'blocking_time'
     , ifnull(th.processlist_state, '---') as 'blocking_state'
     , ifnull(th.processlist_info, ifnull((select esc.sql_text
                                           from performance_schema.events_statements_current esc
                                           where esc.thread_id = th.thread_id), '---')) as 'blocking_query'
     , (select group_concat(concat('(id : ', waiting.processlist_id, ', waiting : ', waiting.processlist_time, ' sec) -> ', processlist_info)
        order by waiting.processlist_time desc)
        from performance_schema.threads waiting
        where waiting.processlist_state = 'Waiting for table metadata lock') as 'waiting_query'
from (select granted.owner_thread_id
            ,count(*) as cnt
      from performance_schema.metadata_locks pending
          ,performance_schema.metadata_locks granted
      where pending.lock_status   = 'PENDING'
      and granted.object_type   = pending.object_type
      and granted.object_schema = pending.object_schema
      and granted.object_name   = pending.object_name
      and granted.owner_thread_id != pending.owner_thread_id
      and granted.lock_status   = 'GRANTED'
      group by granted.owner_thread_id
      order by 2 desc
      limit 1) as metalock_parent
     ,performance_schema.threads th
where th.thread_id = metalock_parent.owner_thread_id
and th.processlist_time >= 3 ;

MySQL Lock Wait Query

Version 8.0

select TIMESTAMPDIFF(second, trx_waiting.trx_started, now()) as 'waiting_started_sec'
     , concat(trx_waiting.trx_started, '') as 'waiting_started_time'
     , trx_waiting.trx_state as 'waiting_state'
     , waiting_thread.processlist_command as 'waiting_command'
     , waiting_thread.processlist_id as 'waiting_processlist_id'
     , waiting_thread.processlist_user as 'waiting_user'
     , waiting_thread.processlist_host as 'waiting_host'
     , waiting_thread.processlist_db as 'waiting_db'
     , waiting_thread.processlist_info as 'waiting_query'
     , TIMESTAMPDIFF(second, blocking_thread_time.trx_started, now()) as 'blocking_started_sec'
     , concat(blocking_thread_time.trx_started, '') as 'blocking_started_time'
     , blocking_thread_time.trx_state as 'blocking_state'
     , blocking_thread.processlist_command as 'blocking_command'
     , blocking_thread.processlist_id as 'blocking_processlist_id'
     , blocking_thread.processlist_user as 'blocking_user'
     , blocking_thread.processlist_host as 'blocking_host'
     , blocking_thread.processlist_db as 'blocking_db'
     , ifnull(blocking_thread.processlist_info
           , (select esc.SQL_TEXT
              from performance_schema.events_statements_current esc
              where esc.thread_id = blocking_thread.thread_id)) as 'blocking_query'
from information_schema.innodb_trx trx_waiting
    ,performance_schema.data_lock_waits dlw
    ,performance_schema.threads waiting_thread
    ,performance_schema.threads blocking_thread
    ,information_schema.innodb_trx blocking_thread_time
    -- performance_schema.events_statements_current
where trx_waiting.trx_state = 'LOCK WAIT'
and trx_waiting.trx_wait_started <= date_sub(now(), interval 3 second)
and cast(dlw.requesting_engine_transaction_id as char charset utf8mb4) = trx_waiting.trx_id
and waiting_thread.thread_id = cast(dlw.requesting_thread_id as char charset utf8mb4)
and blocking_thread.thread_id = cast(dlw.blocking_thread_id as char charset utf8mb4)
and blocking_thread_time.trx_id = cast(dlw.BLOCKING_ENGINE_TRANSACTION_ID as char charset utf8mb4) ;

테이블 생성시 auto_increment 설정

CREATE TABLE people 
(
    number INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255)
);

 

AUTO_INCREMENT 확인

다음에 삽입될 Auto_increment(number 컬럼)의 값 확인

SHOW TABLE STATUS WHERE name = '테이블 이름';
SHOW TABLE STATUS LIKE '테이블 이름';

mysql> show table status where name = 'people' \G;
*************************** 1. row ***************************
           Name: _people
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 6
 Avg_row_length: 2730
    Data_length: 16384
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: 82
    Create_time: 2021-12-31 15:02:42
    Update_time: 2021-12-31 15:10:03
     Check_time: NULL
      Collation: utf8mb4_general_ci
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)

AUTO_INCREMENT  초기값 변경

AUTO_INCREMENT를 초기화(변경)할 때는 변경할 AUTO_INCREMENT의 값이 해당 컬럼에서 가장 큰 값보다 커야 합니다

ALTER TABLE 테이블 이름 AUTO_INCREMENT=변경할 숫자;

mysql> alter table _people auto_increment=5
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show table status where name = 'people' \G;
*************************** 1. row ***************************
           Name: _people
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 6
 Avg_row_length: 2730
    Data_length: 16384
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: 5
    Create_time: 2021-12-31 15:57:51
    Update_time: 2021-12-31 15:10:03
     Check_time: NULL
      Collation: utf8mb4_general_ci
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)

AUTO_INCREMENT  1부터 순서대로 정렬

레코드 삭제와 업데이트 등과 같은 이유로 AUTO_INCREMENT로 설정된 컬럼의 숫자가 난잡하게 될 수 있습니다. 이때 보기 좋게 1부터 순서대로 정렬할 수 있습니다.

정렬 전 데이터
SET @count=0;
UPDATE 테이블 이름 SET 컬럼 이름=@count:=@count+1;

mysql> SET @count=0;
mysql> UPDATE people SET number=@count:=@count+1;
정렬 후 데이터
# 데이터 정렬 후 AUTO_INCREMENT 값을 변경
ALTER TABLE 테이블 이름 AUTO_INCREMENT=변경할 숫자;

mysql> alter table people AUTO_INCREMENT=4;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

예제

AUTO_INCREMENT5변경

 

 

ex) ALTER TABLE people AUTO_INCREMENT=5;

// people 테이블의 AUTO_INCREMENT 값을 5로 변경

* 변경된 것을 확인(SHOW TABLE STATUS LIKE 'people';)

1) 데이터 삽입

 
 

ex) INSERT INTO people VALUES (NULL, "name_D");

Auto_increment5변경했기 때문에 number에 5삽입됩니다.

삽입을 완료하면 Auto_increment6이 됩니다.

다시 AUTO_INCREMENT4로 변경하려고 하더라도 오류는 발생하지 않지만 number 컬럼의 가장 큰 값5이기 때문에 Auto_increment의 값은 4로 변경되지 않고 6으로 되어있습니다.

 
 

* 현재 5보다 큰 숫자로만 변경 가능

2) 데이터 삭제

 
 

 

ex) DELETE FROM people WHERE number=5;

number가 5인 데이터를 삭제하면 number 컬럼에서 가장 큰 값3이기 때문에 Auto_increment4로 변경 가능합니다.

* 현재 3보다 큰 숫자로 변경 가능

​* 참고

Auto_increment1로 초기화하려면 테이블의 데이터가 없어야 합니다.

 

참고

https://m.blog.naver.com/dldudcks1779/222006115309

 처음과 끝은 각각 ^과 $로 표현할 수 있어요. 예를 들어서, '^a'라면, a로 시작하는 패턴입니다. 그리고 'E$'이라면, E로 끝나는 패턴입니다. 그러면 '^eat$'은 무엇을 의미할까요? "eat"를 찾으라는 거겠네요. 그리고, .은 임의의 문자를 의미하는데요. '^...$'은, 길이가 3인 문자열이 패턴에 매칭된다는 이야기입니다.

 

 

 이 쿼리를 수행해 봅시다. 그러면 문자열이 시작하고 임의의 문자가 3개 나오고 끝납니다. 그 말은, "Tom"이나 "Bob"도 matching이 된다는 소리일 거에요.

 

 

 

 결과는, _name 필드의 문자열 길이가 3인 것들만 출력됩니다.

 

 


 그런데, 우리는 _name이 모음으로 시작하는 것들을 구한다고 해 봅시다. LIKE를 쓰면 대충 10개의 조건절을 and로 연결하거나, 아니면 left 함수로 1개의 문자열을 가져온 것이, IN에 속해있는지. 등 조금 복잡하게 쿼리를 작성해야 할 거에요.

 

 [...] 패턴은 ...에 속하는 문자 집합이 패턴에 걸린다는 의미인데요. 예를 들자면 이런 경우를 봅시다.

 

 

 이 쿼리는 무엇을 의미할까요? 일단 문자열이 시작했다는 ^가 들어와 있고, []가 있네요. 안에 것을 보니까 aAeE가 있는데, 첫 문자의 가능 집합이 a, A, e, E라는 것입니다. 즉, _name 필드가 이 4개의 문자로 시작하는 레코드들을 모두 출력하라는 의미입낟.

 

 

 쿼리의 결과는 다음과 같습니다. 그러면, a, A, e, E라는 문자가, 첫 번째에도 나오고 두 번째에도 나와야 하는 경우는 어떻게 작성해야 할까요? 이 때, {n}이라는 키워드를 씁니다. 해당 패턴이 n회 반복된다.

 

 

 이 문장의 의미는, string이 시작하고 나서, a나 A나 e나 E가 2회 반복되어야 한다는 것입니다. 즉, aa~나 aA~나 ae~나 aE나, ... , EE가 해당 패턴에 걸릴 거에요. 그러면 저 셋 중에는, eat가 있는 필드만 걸리겠네요.

 

 

 하핫. 만약에 1회 이상 2회 미만 그러한 패턴이 나타나는 패턴이 참이 되게 하고 싶다면 어떻게 하면 될까요? {n,m}을 쓰시면 됩니다. 이것은 패턴이 n회 이상, m회 이하 등장한다는 의미입니다. Arie, Asia, eat 셋 중에, 다음 패턴을 만족하는 것이 몇 개나 있을까요?

 

 

 일단, a나 A나 e나 E가 나타나는 패턴이 최소 1회, 최대 1회 반복이 되고, 임의의 문자가 하나 나오면 A?나 A?나 e?나 E?가 저것과 매치가 될 거에요. 그러면, 저 셋 중에서 만족하는 것이 없어요. 그런데, 아래와 같은 경우에는 이야기가 달라집니다.

 

 

 보시면 패턴이 1회 이상 2회 이하 반복되고, 임의의 문자 하나가 들어오고 끝나면 만족을 하는데요.

 

 

 eat는 만족합니다. e가 aAeE 패턴에 만족하고, a 또한 aAeE 조건에 만족합니다. 2회 반복되었고, 다음에 t는 임의의 문자 중 하나입니다. 개행을 제외한. 따라서 eat가 있는 레코드만 출력이 됩니다. [^...]은 무엇을 의미할까요? ...에 속하지 않는 문자 집합을 의미합니다.

 

 

 위 쿼리를 실행해 봅시다. 그러면 문자열이 시작하고, 첫 번째 문자가 a나 A가 아니여야 합니다. _name 필드가 그러한 것만 고르는 query입니다. 한 번 실행을 해 봅시다.

 

 

 Asia랑 Anie는 빠졌음을 알 수 있어요.

 

 


 *과 +이 남았네요. *은 패턴이 0번 이상, +은 1번 이상 나타난다는 것을 의미합니다. 예를 들어서, 다음과 같은 것을 생각해 봅시다.

 

 

 이것은 문자열이 시작했을 때, A,a,E,e가 최소 0회 이상 나오는 패턴과 매칭이 됩니다.

 

 

 실행 결과는 아래와 같이 나옵니다. 시작 문자가 어느 것이던 상관 없다는 겁니다. 그런데 +가 붙으면 이야기가 달라지는데요. 이것은 최소 1회 이상 문제의 패턴이 반복되어야 한다는 겁니다.

 

 

 쿼리를 이렇게 작성해 봅시다.

 

 

 그러면 A나 a나 E나 e로 시작하는 것들만 나올 거에요. 마지막으로, |가 있는데요. 이것은 or를 의미합니다.

 

 

 다음 쿼리는 A로 시작하거나, B로 시작하는 패턴을 찾으라는 겁니다.

 

 

 결과는 3개의 row가 리턴됩니다.

 

 


 그러면 _name이 모음으로 시작하면서, 모음으로 끝나는 것들은 어떻게 구하면 좋을까요? 일단, 저는 다음과 같은 쿼리를 수행해 보겠습니다. 당연하게도, 길이가 1짜리면서 모음인 데이터를 추가한 겁니다.

 

 

 다음에, 쿼리를 생각해 봅시다. 일단 문자열이 시작해야 하고, [aeiouAEIOU] 라는 pattern이 들어와야 할 겁니다. 다음에 임의의 문자가 0개 이상 오고, [aeiouAEIOU] 패턴이 온 다음에 끝나야 겠지요.

 

 

 그러면, 이런 식으로 작성할 수 있을 겁니다. 그런데, 이 경우 길이가 1인 데이터는 매칭이 되지 않을 겁니다.

 

 

 결과를 확인해 보면, "A"라는 것은 matched가 되지 않았음을 알 수 있어요. 그러면 어떻게 작성하면 좋을까요?

 

 

 이렇게 작성하면 어떻게 수행이 될까요? 일단, 첫 패턴이 모음인지 검사합니다. 그 다음에 괄호 안으로 들어갈 건데요. 판단 state를 그려보면 아래와 같습니다.

 

 

 보시면 모음이 나온 후에 바로 Terminate가 되거나, 아니면 임의의 문자가 0회 이상 나온 후에, 모음으로 끝나는 패턴만 regexp에 걸리게끔 하고 있어요. 조금 어려운 예제였습니다. 그러면, 모음으로 시작하거나, 모음으로 끝나는 건 어떻게 작성하면 좋을까요?

 

 

 Conditions를 괄호로 묶고 |를 붙이면 됩니다. 어떤 조건인가요? ^[모음들]이거나, [모음들]$이겠지요. 이 둘을 괄호로 묶어준 다음에 |로 연결해 주시면 됩니다. 정규 표현식을 연습하는 사이트는 여러 곳이 있습니다. 거기에서 연습해 보셔도 좋을 듯 싶습니다.

 

참고

https://codingdog.tistory.com/entry/mysql-regexp-%EB%B3%B5%EC%9E%A1%ED%95%9C-%ED%8C%A8%ED%84%B4-%EB%A7%A4%EC%B9%AD%EC%9D%84-%ED%95%B4-%EB%B4%85%EC%8B%9C%EB%8B%A4

 

mysql regexp : 복잡한 패턴 매칭을 해 봅시다.

 저번에 LIKE 연산자를 배웠습니다. 이번에는 조금 더 복잡한 패턴 매칭에 대해서 다뤄보겠습니다. [관련글] LIKE 연산자 알아봅시다.  눈치 채신 분도 있으실지 모르겠습니다. 자바에서 파싱할

codingdog.tistory.com


to Top