'MySQL'에 해당되는 글 2건

  1. 2019.01.05 MySQL Transaction
  2. 2019.01.05 MySQL 계층구조 질의 1
MySQL/Transaction2019. 1. 5. 18:57

MySQL Transaction


MySQL의 저장엔진

MyISAM : MySQL 5.1까지의 기본 저장엔진. Transaction 지원안됨

InnoDB : MySQL 5.5까지의 기본 저장엔진. Transaction 지원함. 

MySQL 5.5 이후에는 테이블을 디폴트로 설정으로 생성하면 저장엔진은 InnoDB 으로 설정된다



지원하는 모든 저장엔진 확인하기 ( MySQL8 )

SHOW ENGINES;




현재 생성된 테이블에 적용된 저장엔진의 종류 확인하기

SHOW CREATE TABLE [테이블 이름];


'emp', 

'CREATE TABLE `emp` (\n

  `id` int(11) NOT NULL AUTO_INCREMENT,\n

  `ename` varchar(255) COLLATE utf8_bin DEFAULT NULL,\n

  `parent_id` int(11) DEFAULT \'0\',\n

  PRIMARY KEY (`id`)\n

) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COLLATE=utf8_bin'



자동커밋 설정

SET AUtOCOMMIT=1;   // 각 SQL 문장 실행시 마다 즉시 영구반영

SET AUTOCOMMIT=0;   // 각 SQL 문장 실행시에 즉시 반영 안되고 COMMIT, ROLLBACK 명령시에 영구반영됨



MySQL은 디폴트로 SET AUTOCOMMIT=1; 상태이기 때문에 모든 문장마다 자동으로 COMMIT 이 실행되어 영구적으로 반영이 되고 있는 상태이다



현재의 AUTOCOMMIT 설정 상태 확인

SELECT @@AUTOCOMMIT;



MySQL에서 다수개의 SQL 문장으로 한개의 트랜잭션을 설정하려면 다음과 같이 할 수 있다


START TRANSACTION; 명령을 사용하는 경우

SET AUTOCOMMIT=1; 으로 설정된 디폴트 상태에서 아래처럼 특정 구간에서만 일시적인 트랜잭션을 설정할 수 있다

START TRANSACTION 이 실행되면,  SET AUTOCOMMIT=0 으로 자동설정되고 마지막에 COMMIT이나, ROLLBACK을 호출하면 다시 자동으로 SET AUTOCOMMIT=1 으로 설정된다

START TRANSACTION;      // 첫번째 트랜잭션

sql1, sql2, ...                    // 다수개의 SQL문장을 실행한다

COMMIT;                       // 혹은 ROLLBACK;                         // 첫번째 트랜잭션 완료


START TRANSACTION;     // 두번째 트랜잭션

sql1, sql2, ...                   // 다수개의 SQL문장을 실행한다

COMMIT;                       // 혹은 ROLLBACK;                         // 두번째 트랜잭션 완료

위의 방법을 사용하여 트랜잭선을 실행하고 나면(COMMIT, ROLLBACK 할 때) 다시 자동커밋으로 복원된다

그러므로 이후에는 SQL 한문장마다 커밋이 실행되어 실수로 COMMIT 깜박 잊더라도 모든 SQL 문장이 영구적으로 반영된다


START TRANSACTOIN; 명령을 사용하지 않는 경우

SETAUTOCOMMIT=0; 으로 설정을 변경하여 각 문장이 반영되지 않도록 한 경우

SET AUTOCOMMIT=0;     // 자동커밋을 해제한다

sql1 실행, sql2 실행, ...

COMMIT;                      // 혹은 ROLLBACK;                    // 첫번째 트랜잭션 완료

sql1 실행, sql2 실행, ...

COMMIT;                      // 혹은 ROLLBACK;                    // 두번째 트랜잭션 완료

SET AUTOCOMMIT=1;    // 원래의 자동커밋으로 복원한다

위의 방법을 사용할 때 주의할 점은 마지막에 SET AUTOCOMMIT=1; 부분을 설정하지 않으면 이후의 SQL문장이 반영되지 않기 때문에 작업을 끝내기 전에 반드시 COMMIT; 명령을 해야만 실수로 작업내용을 잃지 않게 된다

 


ROLLBACK으로 회복할 수 없는 작업(DDL)

ROLLBACK명령은 DML에 해당하는 SQL 문장을 취소하므로 아래와 같은 DDL에는 효과가 없으므로 주의해야 한다

CREATE ~

ALTER ~

DROP ~



START TRANSACTION 명령으로 트랜잭션을 설정하는 예

START TRANSACTION;                        // 트랜잭션 시작

DELETE FROM emp WHERE id != -100;   // 아이디가 음수가 아니면 모두 삭제, 다른 세션에서 emp 수정 불가

SELECT * FROM emp;                          // 모든 내용이 삭제된 것을 확인함

ROLLBACK;                                       // 트랜잭션 취소

SELECT * FROM emp;                          // 데이터가 다시 복원된 것을 확인함 



START TRANSACTION을 사용하지 않고 트랜잭션을 설정하는 예

SET AUTOCOMMIT=0;

DELETE FROM emp WHERE id != -100;

SELECT * FROM emp;

ROLLBACK;

SET  AUTOCOMMIT=1;

SELECT * FROM emp;



MySQL Workbench에서 트랜잭션 테스트

START TRANSACTION;


SELECT @num:=num, @title:=title, @regdate:=regdate, @hit:=hit

FROM bbs3 WHERE num=4;


DELETE FROM bbs3 WHERE num=4;


INSERT INTO delete_history VALUES(@num,@title,@regdate,NOW(),@hit);


SELECT * FROM bbs3;


ROLLBACK; # COMMIT;




PHP에서 MySQL 트랜잭션을 사용하는 예


MySQL에서 테이블 생성

CREATE TABLE bbs3 

(

   num INT AUTO_INCREMENT PRIMARY KEY,

   title VARCHAR(50) NOT NULL,

   regdate DATETIME,

   hit INT DEFAULT 0

);

INSERT INTO bbs3 VALUES

(NULL, '1번째 글', '2005-10-24 12:25:12', 0),

(NULL, '2번째 글', '2006-11-23 13:24:24', 0),

(NULL, '3번째 글', '2007-12-22 14:23:36', 0),

(NULL, '4번째 글', '2008-01-21 15:22:45', 0),

(NULL, '5번째 글', '2009-02-20 16:21:58', 0);


SELECT * FROM bbs3;


CREATE TABLE delete_history 

(

   num INT AUTO_INCREMENT PRIMARY KEY,

   title VARCHAR(50) NOT NULL,

   regdate DATETIME,

   deldate DATETIME,

   hit INT DEFAULT 0

);



transaction01.php

<!doctype html>

<html>

<head>

<meta charset="utf-8">

<title>트랜잭션 테스트</title>

</head>

<body>

<?php


  $host='fdb24.awardspace.net';

  $user='2915954_sampledb';

  $pass='2915954_sampledb_pwd';

  $db='2915954_sampledb';


  $conn = mysqli_connect($host,$user,$pass,$db);

  if ($conn->connect_error) {

    die("Connection failed: " . $conn->connect_error);

  }

  if($conn) {

    echo "Connection successful"; 

    echo "<br>";

  }

   

  mysqli_query($conn, "set session character_set_connection=utf8;");

  mysqli_query($conn, "set session character_set_results=utf8;");

  mysqli_query($conn, "set session character_set_client=utf8;");

  

  

  // 트랜잭션 시작 (MYSQLI_TRANS_START_READ_ONLY 도 가능)

  // 데이터 읽기만 할 때와 수정, 삭제할 때를 구분하여 지정함

  mysqli_begin_transaction($conn, MYSQLI_TRANS_START_READ_WRITE);

  

  $sql1 = "SELECT @num:=num, @title:=title, @regdate:=regdate, @hit:=hit

        FROM bbs3 WHERE num=4";

  $sql2 = "DELETE FROM bbs3 WHERE num=4";

  $sql3 = "INSERT INTO delete_history VALUES(@num,@title,@regdate,NOW(),@hit)";

  

  $result1 = mysqli_query($conn, $sql1);

  $result2 = mysqli_query($conn, $sql2);

  $result3 = mysqli_query($conn, $sql3);

  

  if( mysqli_num_rows($result1) > 0 && $result2 && $result3

  {

       mysqli_commit($conn);

       echo "트랜잭션 성공<br>";

  }else

  {       

       mysqli_rollback($conn);

       echo "트랜잭션 실패<br>";

  }

  

  // 위에서 실행된 트랜잭션이 정상적으로 작동했는지 확인해본다

  

  $result_set = mysqli_query($conn, "SELECT * FROM bbs3");

  

  if (mysqli_num_rows($result_set) > 0) {

      while($row = mysqli_fetch_assoc($result_set)) { 

        echo $row["num"]."<br>"; 

      }

  } else {

    echo "0 results";

  }


  mysqli_close($conn);

?>

</body>

</html>



트랜잭션과 LOCK IN SHARE MODE, FOR UPDATE

# SELECT 문장 오른쪽에 LOCK IN SHARE MODE, FOR UPDATE 를 사용할 수 있다

# LOCK IN SHARE MODE : 트랜잭션이 종료할 때까지 다른 세션에서 현재 행을 변경하지 못하고 읽기만 가능

# FOR UPDATE : 트랜잭션이 종료할 때까지 현재행을 다른 세션에서는 읽을 수도 없고 변경할 수도 없다


SELECT col FROM player WHERE id=1 LOCK IN SHARE MODE

SELECT col FROM player WHERE id=1 FOR UPDATE;


Posted by cwisky
MySQL/Hierarchical Query2019. 1. 5. 08:58

WITH RECURSIVE, UNION을 사용하여 계층구조 질의를 작성하기 위한 기초 정리

원문참조 : https://dev.mysql.com/doc/refman/8.0/en/with.html


MySQL의 WITH 절은 WITH절에 포함된 서브커리의 결과집합을 일단 이름을 붙여서 메모리에 저장하고 WITH 절 외부의 메인 커리에서 그 이름을 이용하여 마치 테이블처럼 사용할 수 있다. 그런 점에서 가상의 테이블이라고 설명할 수도 있다


# UNION 결과 : 1,2 출력

SELECT 1 AS num

UNION

SELECT 2 AS num;


# WITH, UNION 결과 : 1,2 출력

WITH test AS

(

SELECT 1 AS num

UNION ALL

SELECT 2 AS num

)

SELECT * FROM test;


# WITH, UNION 결과 : 1,2,2,2,... (emp테이블의 행수만큼 2가 반복됨)

WITH test AS

(

SELECT 1 AS num

UNION ALL

SELECT 2 AS num FROM emp # 이 문장은 emp 테이블의 행수만큼 반복된다

)

SELECT * FROM test;


# 서브커리에서 바깥 WITH 테이블 참조. 결과: RECURSIVE 가 없으므로 오류발생

WITH test AS

(

    SELECT 1 AS num

    UNION

    SELECT num+1 AS num FROM test WHERE num<10

)

SELECT * FROM test;


# 내부에 UNION 이 없으므로 오류

# Recursive Common Table Expression은 UNION을 포함해야 함

WITH RECURSIVE test AS

(

SELECT num+1 AS num FROM test WHERE num<10

)

SELECT * FROM test;


# RECURSIVE 문장에 정지조건이 없으므로 오류

WITH RECURSIVE test AS

(

    SELECT 1 AS num

    UNION

    SELECT num+1 AS num FROM test

)

SELECT * FROM test;


# 오류가 없는 정상적인 문장

# Recursive 내부에 UNION을 사용하고 정지조건도 사용한 경우

WITH RECURSIVE test AS

(

    SELECT 1 AS num

    UNION

    SELECT num+1 AS num FROM test WHERE num<10

)

SELECT * FROM test;


# Recursive 내부에는 한개 이상의 Non-Recursive문장이 포함되어야 하므로 아래의 문장은 오류

# Non-Recursive 문장이란 내부에서 바깥 WITH RECURSIVE 테이블을 참조하지 않는 문장을 의미함

WITH RECURSIVE test AS

(

    SELECT 1 AS num FROM test WHERE num<10

    UNION

    SELECT num+1 AS num FROM test WHERE num<10

)

SELECT * FROM test;


WITH RECURSIVE test AS

(    # Non-Recursive 문장( 첫번째 루프에서만 실행됨 )

    SELECT 1 AS num FROM emp # 첫 루프에서 emp 테이블의 행 수만큼 자체 반복됨

    UNION ALL

    # Recursive 문장(읽어 올 때마다 행의 위치가 기억되어 다음번 읽어 올 때 다음 행으로 이동함)

    SELECT num+1 AS num FROM test WHERE num<10

)

SELECT * FROM test;



다수개의 결과집합에 대한 이름을 붙일 때는 다음과 같은 형식을 사용한다

WITH name1 AS

(

   SELECT ~ (Sub Query)

),

name2 AS

(

   SELECT ~ (Sub Query)

)

SELECT ~ (Main Query)



WITH RECURSIVE 관련 정리

  • 메모리 상에 가상의 테이블을 저장
  • 반드시 UNION 사용
  • 반드시 비반복문도 최소한 1개 요구됨
  • 서브커리에서 바깥의 가상의 테이블을 참조하는 문장(반복문)이 반드시 필요함
  • 반복되는 문장은 반드시 정지조건이 요구됨
  • 가상의 테이블을 구성하면서 그 자신(가상의 테이블)을 참조하여 값을 결정할 때 유용함



상세한 해석 연습

아래의 문장에서 사용된 emp 테이블에는 총 10개의 행이 저장되어 있다고 가정한다

아래의 문장을 MySQL8에서 실행하면 1~10 사이의 숫자가 순서대로 10개씩 총 100개 출력된다

왜 그러한 결과가 나오는지 논리적으로 설명하고 그림으로 그릴 수 있는 정도까지 연습해보기 바란다


WITH RECURSIVE test AS

(    # Non-Recursive 문장( 첫번째 루프에서만 실행됨 )

    SELECT 1 AS num FROM emp # 첫 루프에서 emp 테이블의 행 수만큼 자체 반복됨

    UNION ALL

    # Recursive 문장(읽어 올 때마다 행의 위치가 기억되어 다음번 읽어 올 때 다음 행으로 이동함)

    SELECT num+1 AS num FROM test WHERE num<10

)

SELECT * FROM test;



아래의 계층구조 질의에서 사용할 함수

REPEAT(str, count)

 - str 문자열을 count 수만큼 반복하여 결합한다

 - SELECT REPEAT(' * ', 10);


CONCAT(exp1, exp2, ......)

 - 2개 이상의 파라미터를 문자열로 결합한다

 - 첫번째 파라미터는 필수이다


SELECT CONCAT ( REPEAT( ' ', 10 ), title ) AS title FROM bbs;



CREATE TABLE emp

(

    id INT AUTO_INCREMENT PRIMARY KEY, 

    ename VARCHAR(255), 

    parent_id INT DEFAULT 0 REFERENCES id

);


INSERT INTO emp VALUES

(1, 'name1', 0),

(2, 'name2', 1),

(3, 'name3', 1),

(4, 'name4', 3),

(5, 'name5', 0),

(6, 'name6', 3),

(7, 'name7', 4),

(8, 'name8', 5),

(9, 'name9', 8),

(10, 'name10',8);


WITH RECURSIVE tmp1 AS

(

    SELECT id, ename, parent_id, 

    ename AS path, 1 AS lvl

    FROM emp WHERE parent_id=0

    UNION ALL

    SELECT e.id, e.ename, e.parent_id, 

    CONCAT(t.path,',',e.ename) AS path, t.lvl+1 AS lvl 

    FROM tmp1 t JOIN emp e ON t.id=e.parent_id

)

SELECT id, CONCAT(REPEAT(' ', lvl*4), ename) ename, parent_id, path, lvl 

FROM tmp1 

ORDER BY path# path 임시컬럼을 사용하여 정렬하면 계층구조로 정렬된다



Workbench에서 위의 문장을 실행한 결과



Posted by cwisky