What one can and should optimize |
번역: 최영봉 편집: 황미영
자료제공: DATABASE.SARANG.NET
|
printable version |
MySQL을 위한 하드웨어 최적화 |
- 큰 테이블(2G가 넘는)이 필요하다면, 알파나 스팍 또는 IA64 등의 64bit 하드웨어를 고려해 보는 게 좋다. MySQL은 내부적으로 64bit 정수를 많이 사용하고 있으므로, 64bit CPU를 사용하면 좀더 나은 퍼포먼스를 기대할 수 있다.
- 거대한 데이터베이스를 위한 최적화는 보통 램, 빠른 디스크, CPU 순으로 진행된다.
- 더 많은 램은 사용되는 대부분의 key 페이지들을 램에 보관함으로써 빠른 key 갱신을 가능하게 한다.
- 안전한 트랜잭션을 사용하지 않거나 큰 디스크를 사용하고 파일 검사를 오랫동안 하는 일을 피하고 싶다면 UPS를 사용하여 전원 오류가 발생한 경우에도 시스템을 안전하게 종료시킬 수 있도록 하는 것도 좋은 생각이다.
- 하나의 전용 데이터베이스 서버를 가진 시스템이라면 1G 이더넷을 고려해 볼 필요가 있다. 네트워크 지연은 처리능력만큼 중요하다.(Latency is as important as throughput.)
|
디스크 최적화 |
- 시스템, 프로그램, 임시 파일들을 위한 전용 디스크를 갖춰라. (내용이) 자주 변경되는 경우라면 갱신 기록과 트랜잭션 기록 파일을 별도의 디스크에 배치한다.
- 데이터베이스 디스크에 있어서는 빠른 탐색 시간(seek time)이 요건이다. 큰 테이블에서 하나의 레코드를 찾기 위해 소요되는 탐색 횟수는 다음과 같이 추정해 볼 수 있다.
log(row_count) / log(index_block_length/3*2/(key_length + data_ptr_length))+1
예를 들어, 500,000개의 레코드를 가지고 있고 medium int 형 필드로 인덱싱하고 있는 테이블의 경우라면 log(500000) / log(1024/3*2/(3+4))+1 = 4 번의 탐색이 필요하다. 여기서 인덱스는 500,000 * 7 * 3/2 = 5.2M 정도의 크기가 될 것이다. 실제로는 대부분의 블록들이 버퍼에 저장되므로 아마도 1~2번 정도의 탐색이 필요하게 된다.
- 쓰기의 경우 새로운 키를 넣을 위치를 찾기 위해 위에서처럼 4번의 탐색이 필요하지만, 통상적으로 인덱스를 갱신하기 위해 2번의 탐색이 더 필요하다.
- 매우 큰 데이터베이스에 경우, 디스크 탐색 속도에의해 성능이 좌우되는데, 탐색 수는 더 많은 데이터를 얻을 때마다 N log N 씩 증가한다.
- 데이터베이스들과 테이블들을 다른 디스크들에 분할해 넣어라. MySQL에서는 이를 위해 심볼릭 링크를 사용할 수 있다.
- Striping disks(RAID 0와 같은)는 읽기와 쓰기 양면에서 처리능력을 증가시킨다.
- 미러링을 동반하는 Striping disk(RAID 0+1)는 읽기/쓰기 성능을 향상시키고 안전성을 제공한다. 쓰기는 약간 느리다.
- 임시파일 또는 쉽게 갱신될 수도 있는 데이터에 대해서 미러링이나 RAID(RAID 0는 예외)를 사용하지 않는다.
- Linux를 사용한다면 부팅할 때 hdparm -m16 -d1 명령을 디스크에 적용하여 다중 섹터 읽기/쓰기와 DMA 사용이 가능하도록 한다. 이는 반응 시간을 5~50%까지 증가시킨다.
- Linux를 사용한다면 디스크를 마운트할 때 async(기본값이다)와 noatime 옵션을 부여하여 마운트한다.
- 일부 특정 응용프로그램의 경우 아주 특수한 테이블을 램디스크에 저장하는 것도 한 방법이 된다. 그러나 보통은 필요 없다.
|
운영체제 최적화 |
- 스왑을 제거한다. 메모리 문제가 있다면 시스템이 적은 메모리를 사용하도록 설정하기 보다는 메모리를 증설하는 것이 좋다.
- 데이터에 대해서 NFS 디스크를 사용하지 않는다. (NFS locking 문제에 봉착할 수 있다.)
- 시스템과 SQL 서버를 위해 open file 한계 수치를 증가시킨다. (safe_mysql 스크립트에 ulimit -n #을 추가한다.)
- 프로세스와 쓰레드의 개수 제한을 늘려준다.
- 상대적으로 큰 테이블을 사용할 일이 드물다면, 파일시스템이 파일을 여러 실린더에 분산시켜 저장하지 않도록 설정한다.(솔라리스)
- 큰 파일을 지원하는 파일시스템을 사용한다.(솔라리스)
- 어떤 파일시스템을 사용하는 것이 좋을지 선택한다. 리눅스의 Reiserfs 는 파일 열기, 읽기, 쓰기에 있어서 (ext2보다) 빠르다. 파일 검사도 단지 수 초 밖에 안 걸린다.
|
API 선택 |
- PERL
- OS 와 데이터베이스들간의 이식성 우수하다.
- 빠른 프로토타이핑에 적합하다.
- DBI/DBD 인터페이스를 사용하는 것도 한 방법이다.
- PHP
- PERL 보다 익히기 쉬운 언어다.
- PERL 보다 자원을 적게 사용. 때문에 웹서버에 내장시키기에 좋다.
- PHP4로 업그레이드하여 더 나은 속도를 얻는 것도 한 방편이다.
- C
- MySQL 본래의 인터페이스이다.
- 더 빠르고 더 많은 제어가 가능하다.
- 저 수준. 때문에 (프로그래머가) 더 많은 일을 해야 한다.
- C++
- 고 수준. 코딩에 더 많은 시간이 필요하다.
- (MySQL C++ API는) 여전히 개발 단계에 있다.
- ODBC
- 윈도우즈와 유닉스에서 동작한다.
- 거의 대부분의 다른 SQL 서버로 이식 가능하다.
- 느리다. MyODBC는 단순한 pass-through 드라이버이지만 본연의 인터페이스에 비해 19% 정도 느리다.
- 같은 일을 수행하는 많은 다른 도구들이 있다. 작업을 어렵게 하는 한 가지는 많은 ODBC 드라이버들이 제각기 다른 부분에서 상이한 버그들을 가지고 있다는 점이다.
- 문제 발생 소지가 많다. 마이크로소프트는 정기적으로 인터페이스를 변경한다.
- 미래가 불확실하다.(마이크로소프트는 ODBC보다 OLE 쪽에 더 많은 비중을 두고 있다.)
- JDBC
- 이론적으로 OS, 데이터베이스 간의 이식성이 우수하다.
- (브라우저와 같은)웹 클라이언트 상에서 동작할 수 있다.
- Python + others
- 좋을 것이다. 그러나 우리는 사용하지 않는다.
|
응용프로그램 최적화 |
- 우선은 문제 해결에 집중하는 것이 필요하다.
- 응용프로그램을 제작할 때 다음 중 무엇이 가장 중요한지를 결정하는 것이 필요하다:
- 속도
- OS 간의 이식성
- SQL 서버들 간의 이식성
- persistent connection을 사용한다.
- 응용프로그램 측의 캐싱은 SQL 서버의 부하를 감소시킨다.
- 응용프로그램 상에서 쓰이지 않는 컬럼은 쿼리하지 않는다.
- SELECT * FROM table_name... 과 같은 쿼리를 사용하지 않는다.
- 응용프로그램의 모든 부분에 대하여 벤치마킹을 시도한다. 그러나 대부분의 노력을 부하의 가장 유력한 요인일 것 같은 부분의 응용프로그램들에 집중하는 것이 좋다. 이를 모듈 단위로 수행하면 발견한 병목구간을 빠른 '더미 모듈'로 대체하고 나서 다음 병목구간을 찾는 일로 넘어가는 식으로 일을 진행할 수 있다.
- 일련 작업 중에 많은 변경이 이루어진다면 LOCK TABLES을 이용한다. 예를 들면, 여러 개의 UPDATE 또는 DELETES 문장을 집합적으로 수행하는 경우 등.
|
이식성이 중요한 응용프로그램이라면 |
- Perl DBI/DBD
- ODBC
- JDBC
- Python (또는 범용 SQL 인터페이스를 가진 다른 언어들) 등을 사용한다.
- 모든 대상 SQL 서버들이 갖추고 있는, 또는 쉽게 다른 구문으로 모사할 수 있는 SQL 구문만 사용한다. www.mysql.com 의 crash-me 페이지를 보면 도움이 될 것이다.
- 다른 OS나 SQL서버들에 없는 기능들을 제공하기 위해 wrapper 프로그램을 제작하여 사용한다.
|
보다 빠른 속도가 요구된다면 |
- 병목구간(bottleneck)을 (CPU, 디스크, 메모리, SQL 서버, OS, API, 또는 응용프로그램에서) 찾아내서 제거하는 일에 집중한다.
- 더 빠른 속도와 유연성을 제공하는 MySQL의 확장기능을 사용한다.
- SQL 서버에 관한 지식을 더 많이 습득하여 문제를 해결하기 위한 가장 빠른 SQL 구문을 사용하고 병목요소를 사전에 제거한다.
- 테이블 레이아웃과 쿼리들을 최적화한다.
- select 속도를 증가시키기 위해 replication을 사용한다.
- 데이터베이스가 느린 네트워크로 연결되어 있다면, 압축된 클라이언트/서버 프로토콜을 사용한다.
- 응용프로그램의 초기 버전이 이식성에 있어서 부실하더라도 걱정할 필요 없다. 문제를 먼저 해결하고 나서 나중에 언제든지 최적화할 수 있다.(Don't be afraid to make the first version of your application not perfectly portable; when you have solved your problem, you can always optimize it later.)
|
MySQL 최적화 |
- 컴파일러와 컴파일 옵션을 충분히 고려하여 선택한다.
- 가장 훌륭한 MySQL 시작 옵션을 찾는다.
- MySQL 매뉴얼을 찾아보고 Paul DuBois 의 MySQL 서적을 읽는다.
- EXPLAIN SELECT, SHOW VARIABLES, SHOW STATUS, SHOW PROCESSLIST 명령을 사용한다.
- 쿼리 옵티마이저가 동작하는 방식을 공부해 둔다.
- 테이블을 관리한다.(myisamchk, CHECK TABLE, OPTIMIZE TABLE)
- MySQL 확장기능을 사용하여 속도를 증진시킨다.
- 특정 함수가 많은 곳에서 자주 사용될 것이라면 MySQL 사용자 정의 함수(UDF)로 직접 제작한다.
- 정말 필요한 경우가 아니라면, 테이블 수준 또는 컬럼 수준에서 GRANT 를 사용하지 않는다.
- MySQL 고객 지원 서비스에 비용을 지불하고 문제 해결을 위한 도움을 받는다 :)
|
MySQL의 컴파일 및 설치 |
- 자신의 시스템에서 사용 가능한 최상의 컴파일러를 선택함으로 보통 10~30% 정도 성능 향상을 기대할 수 있다.
- Intel 기반의 리눅스 시스템이라면 MySQL을 pgcc(펜티엄급에 최적화된 버전의 gcc)로 컴파일 한다. 그러나, (컴파일된) 바이너리는 인텔 펜티엄 CPU에서만 동장할 것이다.
- MySQL 매뉴얼에서 권하는 플랫폼 별 최적화 옵션을 사용한다.
- 통상적으로 특정 CPU를 위한 본연의 컴파일러(Sparc을 위한 Sun Workshop과 같은)를 사용하면 gcc 보다 더 나은 성능을 기대할 수 있다. 그러나, 항상 그런 것은 아니다.
- MySQL을 사용하려는 한 가지 문자셋만 지정하여 컴파일한다.
- mysqld 실행파일을 정적으로 컴파일(--with-mysqld-ldflags=-all-static)하고 strip sql/mysqld 명령으로 최종 실행파일에서 디버그 코드를 제거한다.
- MySQL이 C++ 예외처리를 하지 않으면, 즉 예외처리 지원 옵션을 빼고 컴파일하면 성능이 크게 향상된다.
- 운영체제가 네이티브 쓰레드(native thread)를 지원한다면 mit-pthreads 라이브러리 대신 네이티브 쓰레드를 사용하도록 한다.
- 생성된 실행파일을 MySQL 벤치마크 테스트로 테스트해 본다.
|
유지 보수 |
- 가능하면 정기적으로 OPTIMIZE table 을 실행한다. 이는 특히 자주 갱신되는 가변크기 레코드들에 대해 중요하다.
- 정기적으로 myisamchk -a 명령을 사용하여 테이블들의 key 분산 상태를 갱신한다. 이 작업을 수행하기 전에 반드시 MySQL을 셧다운해야 한다는 점을 잊지 않는다.
- 파일들이 조각난 상태라면 다른 디스크로 모두 복사하고 기존의 디스크를 깨끗이 한 후 다시 파일을 옮기는 일도 시도해 볼만한 가치가 있다.
- 문제가 발생한다면, 테이블을 myisamchk나 CHECK table 명령으로 검사한다.
- MySQL의 상태를 mysqladmin -i10 processlist extended-status 명령으로 모니터한다.
- MySQL GUI 클라이언트를 사용하면 프로세스 목록과 상태를 다른 윈도우에서 모니터할 수 있다.
- mysqladmin debug 명령을 사용해서 잠금(lock)과 성능에 관한 정보를 얻는다.
|
SQL 최적화 |
사용하는 것이 좋은 것에만 SQL을 사용하고 그렇지 않은 곳에서는 다른 것을 사용한다. SQL는 다음과 같은 곳에 사용한다.
- WHERE 절에 의존하여 행을 찾는 경우
- 테이블들을 JOIN 할 경우
- GROUP BY
- ORDER BY
- DISTINCT
다음과 같은 일에는 사용하지 않는다.
- 데이터(date 와 같은)의 유효성을 검증하는 경우
- 계산기로 사용
Tips
- key를 폭 넓게 사용한다.
- key는 검색에는 좋지만, key 컬럼에 대해 insert 나 update를 수행하는 데는 좋지 않다.
- 데이터를 제3의 보통 데이터베이스 형식(in the 3rd normal database form)으로 유지하되, 속도를 중시한다면 정보의 중복이나 요약 테이블(summary tables)을 생성하는 일을 기피할 필요는 없다.
- 큰 테이블에 대해서 GROUP BY를 남용하는 대신 그 테이블에 대한 요약 테이블을 생성하고 이 테이블에 대해 쿼리하는 것이 낫다.
- UPDATE table set count=count+1 where key_column=constant 와 같은 문장은 매우 빠르다!
- 기록 테이블(log tables)에 관한 한, 정기적으로 요약 테이블을 만드는 것이 요약테이블을 그대로 두는 것 보다 나을 것이다.
- INSERT에서 디폴트 값(default values)의 잇점을 십분 활용한다.
|
SQL 서버들 간의 속도 차이 (단위:초) |
key 이용 2,000,000 행 읽기 |
NT |
Linux |
mysql |
367 |
249 |
mysql_odbc |
464 |
|
db2_odbc |
1,206 |
|
imformix_odbc |
121,126 |
|
ms-sql_odbc |
1,634 |
|
oracle_odbc |
20,800 |
|
solid_odbc |
877 |
|
sybase_odbc |
17,614 |
|
|
Linux |
mysql |
381 |
206 |
mysql_odbc |
619 |
|
db2_odbc |
3,460 |
|
informix_odbc |
2,692 |
|
ms-sql_odbc |
4,012 |
|
oracle_odbc |
11,291 |
|
solid_odbc |
1,801 |
|
sybase_odbc |
4,802 |
|
위의 테스트는 MySQL의 경우 8M 캐시를 사용하도록 설정한 것이고 다른 데이터베이스들은 설치 기본값을 이용하였다. |
중요한 MySQL 기동 옵션들 |
back_log |
접속 수가 많다면 변경한다. |
thread_cache_size |
접속 수가 많다면 변경한다. |
key_buffer_size |
인덱스 페이지를 위한 풀(pool) 크기. 큰 수치를 지정하는 것도 가능하다. |
bdb_cache_size |
BDB 테이블들에 의해 사용되는 레코드와 키 캐시 크기. |
table_cache |
많은 테이블을 가지고 있거나 동시 접속 수가 많다면 변경한다. |
delay_key_write |
모든 키 쓰기 동작을 버퍼링할 필요가 있다면 지정한다. |
log_slow_queries |
시간이 많이 걸리는 쿼리를 찾을 때 사용한다. |
max_heap_table_size |
GROUP BY 절에서 사용된다. |
sort_buffer |
ORDER BY 와 GROUP BY 절에서 사용된다. |
myisam_sort_buffer_size |
REPAIR TABLE 문에서 사용된다. |
join_buffer_size |
키 없이 join 할 때 사용된다. | |
테이블 최적화 |
|
MySQL이 데이터를 저장하는 방식 |
- 데이터베이스는 디렉토리로 저장된다.
- 테이블은 파일로 저장된다.
- 컬럼은 가변 길이나 고정 길이 유형으로 파일 안에 저장된다. BDB 테이블에서 데이터는 페이지에 저장된다.
- 메모리 기반의 테이블도 지원된다.
- 데이터베이스와 테이블들은 다른 디스크로부터 심볼릭 링크될 수 있다.
- Windows용 MySQL은 .sym 파일을 이용하여 데이터베이스에 대한 내부적인 심볼릭 링크를 지원한다.
|
MySQL 테이블 유형들 |
- HEAP 테이블: 고정된 수의 레코드만 가지는 테이블로서 오직 메모리에만 저장되며 HASH 인덱스로 인덱스된다.
- ISAM 테이블: MySQL 3.22에서 사용된 구식 B-tree 테이블 유형이다.
- MyISAM 테이블: ISAM 테이블의 새 버전으로 많은 확장 기능들을 가지고 있다.
- 바이너리 호환성
- NULL 컬럼에 대한 인덱싱
- 가변 크기 테이블의 파편화(fragmentation)가 ISAM 테이블 보다 적음
- 거대 파일 지원
- 인덱스 압축 향상
- 키 통계 향상
- 더 향상되고 빠른 auto_increment 지원
- Sleepycat의 Berkeley DB(BDB) 테이블: 안전한 트랜잭션 지원(BEGIN WORK / COMMIT | ROLLBACK)
|
MySQL 레코드 유형(ISAM/MyISAM 테이블과 관련하여서만) |
- MySQL은 모든 컬럼들이 고정 크기 유형이라면 (VARCHAR, BLOB, TEXT 컬럼이 없다면) 테이블을 고정 크기 테이블로 생성한다. 그렇지 않다면, 가변 크기 유형의 테이블로 만든다.
- 고정 크기 유형은 동적 크기 유형에 비해 속도가 빠르며 안전하다.
- 동적 크기 레코드 유형은 대개 보다 적은 공간을 사용하지만 테이블의 갱신이 자주 발생한다면 파편화가 가중되기 마련이다.
- 어떤 경우에는 주 테이블의 속도를 향상시키기 위해 모든 VARCHAR, BLOB, TEXT 컬럼들을 다른 테이블로 옮기는 것도 유용할 때가 있다.
- myisampack(ISAM 테이블에서는 pack_isam)을 사용하면 읽기 전용, 압축된 테이블을 만들 수 있다. 느린 디스크를 사용할 때는 디스크 사용량을 최소화하는 것이 좋은 한 방법이 된다. 압축된 테이블은 더 이상 갱신되지 않는 로그 테이블 등에 사용하면 최상이다.
|
MySQL 캐시들 (한 번 적재되어 모든 쓰레드가 공유) |
- 키 캐시: key_buffer_size, 기본값은 8M
- 테이블 캐시: table_cache, 기본값은 64
- 쓰레드 캐시: thread_cache_size, 기본값은 0
- 호스트명 캐시: 컴파일할 때 변경 가능, 기본값은 128
- 메모리에 맵핑된 테이블(Memory mapped tables): 현재는 압축된 테이블을 위해서만 사용된다.
MySQL은 행(raw) 캐시를 가지고 있지 않다. 그러나, 운영체제에게 이 일을 시킬 수 있다! |
MySQL 버퍼 변수들 (공유되지 않으며 실행 중 적재됨) |
- sort_buffer: ORDER BY / GROUP BY 절에서
- record_buffer: 테이블을 스캔할 때
- join_buffer_size: 키 없이 join을 수행할 때
- myisam_sort_buffer_size: REPAIR TABLE에서
- net_buffer_length: SQL 문장을 읽을 때와 결과 값을 버퍼링할 때
- tmp_table_size: 임시 결과값을 위한 HEAP-table-size
|
MySQL 테이블 캐시가 동작하는 방식 |
- MyISAM 테이블의 열려 있는 각각의 인스턴스는 인덱스 파일과 데이터 파일을 사용한다. 만일 어떤 테이블이 두 개의 쓰레드에 의해 사용되거나 같은 쿼리에서 두 번 사용되면, MyISAM이 인덱스 파일은 공유하지만 데이터 파일은 또 하나의 인스턴스를 위해 추가로 열게 된다.
- 캐시 안의 모든 테이블이 사용 중이라면 그 캐시는 일시적으로 테이블 캐시 크기보다 커진다. 이러한 상황이 발생하면, 그 다음 방면된 테이블이 닫히게 된다.
- mysqld 변수 Opend_tables를 검사해 보면 테이블 캐시가 너무 작은지 아닌지를 알 수 있다. 이 값이 높으면 테이블 캐시를 늘려줘야 한다!
|
MySQL 확장들 / 속도 증진 최적화 |
- 최적화된 테이블 유형을 사용한다.(HEAP, MyISAM, BDB 테이블)
- 데이터를 위한 최적의 컬럼을 사용한다.
- 가능한 한 고정 크기 레코드를 사용한다.
- 다른 잠금 유형(lock types)를 사용한다.(SELECT HIGH_PRIORITY, INSERT LOW_PRIORITY)
- Auto_increment
- REPLACE (REPLACE INTO table_name VALUES (...))
- INSERT DELAYED
- LOAD DATA INFILE / LOAD_FILE()
- 한번에 많은 레코드를 추가하기 위해서는 다중 레코드 INSERT를 사용한다.
- SELECT INTO OUTFILE
- LEFT JOIN, STRAIGHT JOIN
- IS NULL 과 접목된 LEFT JOIN 사용
- 일부 경우, ORDER BY 는 키를 사용할 수 있다.
- 하나의 인덱스에 있는 컬럼들만 쿼리할 경우에는 쿼리를 수행하기 위해 그 인덱스 트리만 사용하게 된다.
- 조인은 보통 subselect 보다 빠르다. (대부분의 SQL 서버들에서 그러하다.)
- LIMIT
- SELECT * from table1 WHERE a > 10 LIMIT 10, 20
- DELETE * from table1 WHERE a > 10 LIMIT 10
- foo IN (상수 목록) 구문은 매우 최적화되어 있다.
- GET_LOCK() / RELEASE_LOCK()
- LOCK TABLES
- INSERT 와 SELECT 는 동시에 실행 될 수 있다.
- 작동하고 있는 서버로 읽어 들일 수 있는 UDF 함수들
- 압축된 읽기 전용 테이블들
- CREATE TEMPORARY TABLE
- CREATE TABLE .. SELECT
- MyISAM 테이블을 RAID와 사용하면 하나의 파일을 여러개의 파일들로 나누어 일부 파일시스템의 2G 제한을 넘어서는 것이 가능하다.
- Delayed_keys
- 리플리케이션(replication)
|
MySQL이 인덱스를 사용할 경우 |
- >, >=, =, <, <=, 키에 대해 IF NULL 과 BETWEEN을 사용할 때
SELECT * FROM table_name WHERE key_part1=1 and key_part2 > 5; SELECT * FROM table_name WHERE key_part1 IS NULL;
- 와일드카드 문자로 시작하지 않는 LIKE 절을 사용할 때
SELECT * FROM table_name WHERE key_part1 LIKE 'jani%'
- 조인을 수행하면서 다른 테이블들로부터 레코드를 가져올 때
SELECT * from t1, t2 where t1.col=t2.key_part;
- 특정 인덱스에 대해서 MAX() 나 MIN() 값을 구할 때
SELECT MIN(key_part2), MAX(key_part2) FROM table_name where key_part1=10;
- 키의 접두부에 대해 ORDER BY 나 GROUP BY 절을 수행할 때
SELECT * FROM foo ORDER BY key_part1, key_part2, key_part3;
- 쿼리에 사용되는 모든 컬럼이 한 개의 키의 부분(part)일 경우
SELECT key_part3 FROM table_name WHERE key_part1=1; |
MySQL이 인덱스를 사용하지 않을 경우 |
- MySQL은 테이블 전체를 스캔하는 더 빠를 것이라고 판단되면 인덱스를 사용하지 않는다. 예를 들어, key_part1이 1과 100사이의 값을 고르게 가지고 있다면, 다음과 같은 쿼리에서 인덱스를 사용하는 것은 좋지 않다.
SELECT * FROM table_name where key_part1 > 1 and key_part1 < 90;
- HEAP 테이블을 사용하고 있으며, 모든 키 부분들에 대해서 = 로 검색하지 않을 경우
- HEAP 테이블에 대해 ORDER BY 절로 쿼리할 경우
- 맨 처음의 키 부분을 사용하지 않을 경우
SELECT * FROM table_name WHERE key_part2 = 1;
- 와일드카드 문자로 시작하는 LIKE 를 사용할 경우
SELECT * FROM table_name WHERE key_part1 LIKE '%jani%'
- 하나의 인덱스에 대해서 검색하면서 다른 인덱스에 대해서는 ORDER BY 를 적용할 때
SELECT * FROM table_name WHERE key_part1 = # ORDER BY key2; |
EXPLAIN 사용법 익히기 |
지나치게 느리다고 생각되는 모든 쿼리 문장에 대해 EXPLAIN 을 사용한다. mysql> explain select t3.DateOfAction, t1.TransactionID
-> from t1 join t2 join t3
-> where t2.ID = t1.TransactionID and t3.ID = t2.GroupID
-> order by t3.DateOfAction, t1.TransactionID;
+-------+--------+---------------+---------+---------+------------------+------+---------------------------------+
| table | type | possible_keys | key | key_len | ref | rows | Extra |
+-------+--------+---------------+---------+---------+------------------+------+---------------------------------+
| t1 | ALL | NULL | NULL | NULL | NULL | 11 | Using temporary; Using filesort |
| t2 | ref | ID | ID | 4 | t1.TransactionID | 13 | |
| t3 | eq_ref | PRIMARY | PRIMARY | 4 | t2.GroupID | 1 | |
+-------+--------+---------------+---------+---------+------------------+------+---------------------------------+
유형 ALL과 범위는 잠재적인 문제점을 알리고 있다. |
SHOW PROCESSLIST 사용법 익히기 |
현재 진행 상황을 파악하기 위해서는 SHOW processlist 를 사용한다. +----+-------+-----------+----+---------+------+--------------+-------------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-------+-----------+----+---------+------+--------------+-------------------------------------+
| 6 | monty | localhost | bp | Query | 15 | Sending data | select * from station,station as s1 |
| 8 | monty | localhost | | Query | 0 | | show processlist |
+----+-------+-----------+----+---------+------+--------------+-------------------------------------+
mysql에서 KILL을 사용하거나 명령행에서 mysqladmin을 사용하여 불필요한(runaway) 쓰레드들을 없앨 수 있다. |
MySQL이 쿼리를 해결하는 방법을 찾는 방법 |
다음 명령들을 실행해서 결과를 이해하도록 노력한다.
- SHOW VARIABLES;
- SHOW COLUMNS FROM ... \G
- EXPLAIN SELECT ... \G
- FLUSH STATUS;
- SELECT ...;
- SHOW STATUS;
|
MySQL은 이런 경우 극히 우수하다 |
- 로그 기록시
- 많은 연결이 이루어 질 때, 연결 속도가 매우 빠르다.
- SELECT 와 INSERT 를 동시에 사용하는 곳에서.
- update를 시간이 오래 걸리는 select 문과 결합하지 않을 때
- 대부분의 select/update 문이 고유한 키들을 사용할 때
- 많은 테이블을 장시간 잠금(lock) 충돌 없이 사용할 때
- 크기가 큰 테이블을 가지고 있을 때 (MySQL 은 매우 컴팩트한 테이블 포맷을 사용한다.)
|
MySQL 사용에 있어서 피해야 할 것들 |
- 테이블을 UPDATE 또는 삭제된 행을 테이블에 대해 INSERT하면서 시간이 오래 걸리는 SELECT 절들과 결합시키는 일
- WHERE 절에 올 수 있는 것들에 대한 HAVING
- 키를 사용하지 않은, 또는 충분히 유니크하지 않은 키를 사용한 JOIN
- 컬럼 유형이 서로 다른 컬럼들에 대해 JOIN 수행
- 온전한 키 전체가 아닌 키의 부분에 대해서만 '=' 로 비교연산할 때 HEAP 테이블을 사용
- MySQL monitor 에서 UPDATE 나 DELETE를 사용하면서 WHERE 절을 생략하는 일. 만약 자신이 이런 경향이 있다면, mysql 클라이언트 프로그램을 실행할 때 --i-am-a-dummy 옵션을 추가하기 바란다.
|
MySQL의 독특한 잠금들(locks) |
- 내장된 테이블 잠금
- LOCK TABLES (모든 테이블 유형에 대해 동작함)
- GET_LOCK() / RELEASE_LOCK()
- Page locks (BDB 테이블에 대해서)
- ALTER TABLE 역시 BDB 테이블에 대해 테이블 잠금을 수행함
- LOCK TABLES 는 다중 읽기 작업 또는 한개의 쓰기 작업을 허용한다.
- 보통 WRITE 잠금은 READ 잠금 보다 우선 순위가 높다. 쓰기 작업이 무한정 대기 상태에 놓이게 되는 경우를 피하기 위해서다(to avoid starving the writers). 그리 중요하지 않은 쓰기 작업은 LOW_PRIORITY 키워드를 사용하여 lock handler가 읽기 작업에 먼저 허가를 내어 주도록 하는 것도 한 방법이다.
UPDATE LOW_PRIORITY SET value=10 WHERE id=10; |
문제를 쉽게 해결하기 위해 MySQL로부터 더 많은 정보를 얻는 기법들 |
MySQL 만의 기능들을 항상 주석처리함으로써 쿼리의 이식성 높일 수 있다.
SELECT /*! SQL_BUFFER_RESULTS */ ...
- SELECT SQL_BUFFER_RESULTS ...
MySQL이 임시 결과 세트를 만들도록 강제한다. 임시 세트가 만들어지면, 그 테이블들에 대한 모든 잠금이 해제된다. 이는 테이블 잠금으로 인해 문제가 발생했을 때나 쿼리 결과를 클라이언트로 전송하는데 오랜 시간이 소요되는 경우에 도움이 된다.
- SELECT SQL_SMAIL_RESULT ... GROUP BY ...
결과 세트가 적은 수의 레코드만 가지게끔 하도록 옵티마이저에게 지시한다.
- SELECT SQL_BIG_RESULT ... GROUP BY ...
결과 세트가 많은 수의 레코드를 가지도록 옵티마이저에게 지시한다.
- SELECT STRAIGHT_JOIN ...
옵티마이저가 FROM 절에 나타난 순서대로 테이블을 join 하도록 강제한다.
- SELECT ... FROM table_name [USE INDEX (index_list) | IGNORE INDEX (index_list)] table_name2
MySQL이 특정 인덱스들을 사용하거나 무시하도록 강제한다.
|
트랜잭션 사용예 |
- MyISAM 테이블에 대해 트랜잭션을 수행하는 방법:
mysql> LOCK TABLES trans READ, customer WRITE; mysql> select sum(value) from trans where customer_id=some_id; mysql> update customer set total_value=sum_from_previous_statement where customer_id=some_id; mysql> UNLOCK TABLES;
- Berkeley DB 테이블에 대해 트랜잭션을 수행하는 방법:
mysql> BEGIN WORK; mysql> select sum(value) from trans where customer_id=some_id; mysql> update customer set total_value=sum_from_previous_statement where customer_id=some_id; mysql> COMMIT;
- 다음과 같이 함으로써 여러 트랜잭션들 간의 간섭을 방지할 수 있다는 점에 주목할 필요가 있다:
UPDATE customer SET value=value+new_value WHERE customer_id=some_id; |
REPLACE 사용예 |
- REPLACE는 테이블의 이전 레코드가 새 레코드와 같은 고유 인덱스 값을 가지고 있다면 예전 레코드가 먼저 삭제되고 새 레코드가 추가된다는 점만 제외하면 INSERT와 똑같이 작동한다.
다음과 같이 하는 대신,
SELECT 1 FROM t1 WHERE key=# IF found-row LOCK TABLES t1 DELETE FROM t1 WHERE key1=# INSERT INTO t1 VALUES (...) UNLOCK TABLES t1; ENDIF
다음과 같이 한다.
REPLACE INTO t1 VALUES (...) |
일반적인 팁 |
- 프라이머리 키는 짧은 것을 사용한다. 테이블 조인할 때는 문자열형 보다는 숫치형을 사용한다.
- 여러 부분으로 구성된 키를 사용할 때는 첫 번째 부분이 가장 많이 사용되는 키이어야 한다.
- 의심스러울 때는, 앞부분이 더 많이 중복된 컬럼을 사용해서 보다 나은 키 압축 효과를 얻는다.
- 클라이언트를 실행 중이고 MySQL 서버가 같은 머신에 있다면, TCP/IP 대신에 유닉스 소켓을 사용하여 서버에 연결하는 것이 좋다.(이렇게 하면 7.5% 정도까지 효율이 증진된다.) MySQL 서버에 접속할 때 호스트이름이나 localhost를 지정하지 않으면 유닉스 소켓으로 접속하게 된다.
- 가능하다면 --skip-locking(일부 운영체제에서는 이 것이 기본값이다)을 사용한다. 이는 외부적인 잠금을 사용하지 않게 되고 퍼포먼스가 향상된다.
- 긴 키를 사용하기 보다는 응용프로그램 수준에서 해시된 값을 사용한다.
SELECT * FROM table_name WHERE hash=MD5(concat(col1,col2)) AND col_1='constant' AND col_2='constant';
- Store BLOB's that you need to access as files in files. 데이터베이스에는 파일이름만 저장한다.
- 레코드들의 큰 부분을 지우는 것 보다 레코드 전체를 지우는 것이 더 빠르다.
- SQL이 충분히 빠르지 않다면, 데이터에 접근하는 더 낮은 수준의 인터페이스를 점검해 본다.
|
MySQL 3.23을 사용할 때 얻을 수 있는 이점 |
- MyISAM; 이식이 수월한 거대 테이블 유형
- HEAP; 메모리 상의 테이블
- Berkeley DB; Sleepycat에서 제공하는 트랙잰션이 가능한 테이블
- 대폭 확대된(풀린) 제한들
- 동적 문자셋
- 더 많이 제공되는 STATUS 변수들
- CHECK table, REPAIR table
- 더 빠른 GROUP BY 절과 DISTINCT 절
- 최적화된 LEFT JOIN ... IF NULL
- CREATE TABLE ... SELECT
- CREATE TEMPORARY table_name (...)
- 임시적인 HEAP에서 MyISAM 테이블로의 자동 변환
- 리플리케이션
- mysqlhotcopy 스크립트
|
실제 작업에서 중요한 기능들 |
- 진보된 트랜잭션
- 오류로부터 안전한 리플리케이션
- 텍스트 검색
- 많은 테이블의 삭제 (이 작업 후에 많은 테이블의 갱신이 이루어진다.)
- 너 나은 키 캐시
- 원자화된 RENAME (RENAME TABLE foo as foo_old, foo_new as foo)
- 쿼리 캐시
- MERGE TABLES
- 향상된 GUI 클라이언트
|