2010년 12월 25일 토요일

SYSDATE() 와 NOW()의 차이점

MySQL 내부적으로 현재 날짜 및 시간 정보를 리턴해주는 Built-in함수로 SYSDATE()와 NOW() 2개가 있는데,
내부적으로 SYSDATE()와 NOW()의 작동 방식은 쿼리의 실행 계획에 상당한 영향을 미칠 정도로 크다.

메뉴얼의 내용을 다시 한번 확인해보자.
-- // -- MySQL 메뉴얼 --------------------------------------------------------------------
SYSDATE() returns the time at which it executes.
This differs from the behavior for NOW(), which returns a constant time that indicates the time at which the statement began to execute.
(Within a stored function or trigger, NOW() returns the time at which the function or triggering statement began to execute.)

mysql> SELECT NOW(), SLEEP(2), NOW();
+---------------------+----------+---------------------+
| NOW()               | SLEEP(2) | NOW()               |
+---------------------+----------+---------------------+
| 2010-12-13 14:52:34 |        0 | 2010-12-13 14:52:34 |
+---------------------+----------+---------------------+

mysql> SELECT SYSDATE(), SLEEP(2), SYSDATE();
+---------------------+----------+---------------------+
| SYSDATE()           | SLEEP(2) | SYSDATE()           |
+---------------------+----------+---------------------+
| 2010-12-13 14:52:43 |        0 | 2010-12-13 14:52:45 |
+---------------------+----------+---------------------+
-- // -- MySQL 메뉴얼 --------------------------------------------------------------------

메뉴얼에도 자세히 적혀 있듯이,
SYSDATE() 함수는 트랜잭션이나 쿼리 단위에 전혀 관계 없이, 그 함수가 실행되는 시점의 시각을 리턴해주지만,  
NOW()는 하나의 쿼리 단위로 동일한 값을 리턴하게 된다.

즉, 아래와 같은 쿼리가 있다고 가정하면,
SELECT fd1, fd2, fd3 FROM tab1 WHERE fd_dt>SYSDATE();
만약, 이 테이블이 레코드가 너무 많아서 처음 레코드부터 끝까지 스캔하는데 1시간이 걸린다면, 
처음 레코드를 비교할 때에는 SYSDATE()의 리턴값이 '2010-12-13 00:00:00' 였다면,
마지막 레코드를 비교할 때에는 SYSDATE()의 리턴값이 '2010-12-13 -1:00:00'가 되어 첫번째 레코드와 마지막 레코드의 비교 시점이 달라지게 된다.
(참고로, SYSDATE()와 NOW()의 이러한 차이는 Stored-Function의 DETERMINISTIC 과 NOT DETERMINISTIC의 차이와 거의 흡사해 보인다.)

즉, 쿼리의 비교 값으로 사용된 NOW() 함수는 상수(Constant)이지만, SYSDATE() 함수는 상수가 아닌 것이다.
이로 인해서, 아래와 같은 전체적인 쿼리의 실행 계획을 바꿔버리게 된다.

CREATE TABLE user (
  id mediumint(9) NOT NULL AUTO_INCREMENT,
  name varchar(20) DEFAULT NULL,
  age tinyint(3) unsigned DEFAULT NULL,
  sex enum('MALE','FEMALE') DEFAULT NULL,
  regdt datetime NOT NULL,
  PRIMARY KEY (id),
  KEY ix_regdt (regdt)
) ENGINE=InnoDB;

mysql> explain select * from user where regdt>now();
+----+-------------+-------+-------+----------+---------+------+------+
| id | select_type | table | type  | key      | key_len | ref  | rows |
+----+-------------+-------+-------+----------+---------+------+------+
|  1 | SIMPLE      | user  | range | ix_regdt | 8       | NULL |    1 |
+----+-------------+-------+-------+----------+---------+------+------+
1 row in set (0.00 sec)

mysql> explain select * from user where regdt>sysdate();   
+----+-------------+-------+------+------+---------+------+------+
| id | select_type | table | type | key  | key_len | ref  | rows |
+----+-------------+-------+------+------+---------+------+------+
|  1 | SIMPLE      | user  | ALL  | NULL | NULL    | NULL | 4822 |
+----+-------------+-------+------+------+---------+------+------+
1 row in set (0.00 sec)

SYSDATE()로 비교되는 컬럼에 인덱스가 준비되어 있든지 아니든지에 관계없이 FULL-TABLE-SCAN을 사용할 수 밖에 없는 구조인 것이다.

이러한 예상하지 못한 문제를 해결하기 위해서, 
  • SYSDATE()의 사용 금지
  • 또는 --sysdate-is-now 옵션을 설정하여 MySQL Server 기동
--sysdate-is-now 옵션이 활성화되면, SYSDATE()는 NOW() 함수와 동일하게 작동하게 된다.

아마도, 초기 SYSDATE()의 작동 방식은 대부분의 SQL 문장을 작성하는 사람의 의도와는 어긋나는 경우가 많을 것으로 생각되며,
기본적으로는 SYSDATE()의 작동 방식을 비활성화(--sysdate-is-now 옵션 활성화)하는 것이 예측하지 못한 문제를 막는 방법일 것으로 보인다.

댓글 없음:

댓글 쓰기