시스템 성능에 큰 영향을 미치는 상위 8개 INIT.ORA 파라미터
=========================================================
Technical Bulletins No. 17104 (http://211.106.111.2:8880/bulletin/list.jsp)
PURPOSE ——-
이 문서는 init.ora의 어떠한 parameter들이 database성능에 많은 영향을 미치는지에 대해 기술한다.
Explanation ———–
다음에 열거된 파리미터는 각각 데이터베이스 튜닝에 영향을 미치는 것들이다.
DB_BLOCK_BUFFERS
SHARED_POOL_SIZE
SORT_AREA_SIZE
DBWR_IO_SLAVES
ROLLBACK_SEGMENTS
SORT_AREA_RETAINED_SIZE
DB_BLOCK_LRU_EXTENDED_STATISTICS
SHARED_POOL_RESERVE_SIZE
1. DB_BLOCK_BUFFERS
이 파라미터는 모든 버젼의 오라클에서 사용되며, Oracle block 크기를 단위로 지정하게 된다.
이 값은 사용자가 요청하는 데이터를, 메모리 영역에 저장해 둘 수 있는
공간의 크기를 지정하므로 튜닝시 매우 중요한 역할을 한다.
db_block_buffers 값은 SGA 캐쉬 영역에 존재하는 버퍼의 갯수를 지정 하는데 사용되며,
적절한 캐쉬 크기는 실제 디스크 I/O를 줄이는데 도움이 된다.
캐쉬 영역이 적절하게 지정되어 있는지 여부는 buffer cache hit ratio로 측정 가능하며,
일반적으로 90% 이상의 값을 유지하도록 하는 것이 바람직하다.
buffer cache hit ratio는 다음 SQL을 사용하여 조회 가능하다.
SELECT ROUND(((1-(SUM(DECODE(name, ’physical reads’, value,0))/
(SUM(DECODE(name, ’db block gets’, value,0))+
(SUM(DECODE(name, ’consistent gets’, value, 0))))))*100),2) || ’%' ”Buffer Cache Hit Ratio”
FROM V$SYSSTAT;
실행 결과는 다음과 같은 형식으로 나타나게 된다.
Buffer Cache Hit Ratio:
97.63%
만약 hit ratio가 90% 미만이라면,
hit ratio 가 90% 이상을 유지할 정도로 buffer cache의 크기를 늘려주는 것이 바람직하다.
이 값이 작을 경우 사용된 데이터가,
다른 데이터를 처리할 메모리 영역을 확보시키기 위해 메모리에서 삭제된 후,
다시해당 데이터가 요청될 경우 충분한 cache를 확보하였을 때
피할 수 있는 물리 I/O 가 발생하게 된다.
그러나 만약 이 값을 가용한 메모리 크기에 비해 너무 크게 지정할 경우에는
OS 에서 swapping이 발생하게 되어 시스템이 hang 상태까지 갈 수 있다.
2. SHARED_POOL_SIZE
SHARED_POOL_SIZE는 모든 버젼의 오라클에서 사용되는 파라미터로, 단위는 byte 단위이다.
이 영역은 data dictionary나, stored procedure, 그리고 각종 SQL statement가 저장된다.
SGA 영역가운데 많은 비중을 차지하는 shared_pool_size는 다시 dictionary cache
및 library cache 영역으로 나뉘어 지며,
db_block_buffers와 마찬가지로 너무 크거나, 작게 잡지 않도록 하여야 한다.
SHARED_POOL_SIZE 값이 적절한지 여부는 data dictionary cache 및
library cache 의 hitratio로 측정할 수 있다.
SQL 처리에는 data dictionary가 여러차례 참조되므로,
data dictionary 조회시 디스크 I/O가 적게 발생하도록 하면, 성능 향상에 도움이 된다.
Data dictionary cache hit ratio는 다음 SQL에 의해 측정 가능하다.
SELECT (1-(SUM(getmisses)/SUM(gets))) * 100 ”Hit Ratio”
FROM V$ROWCACHE;
결과는 다음과 같이 생성된다.
Hit Ratio
95.40%
Data dictionary cache hit ratio는 90% 이상을 유지하는 것이 바람직 하지만,
인스턴스 구동 직후에는 캐쉬영역에 데이터가 저장되지 않으므로
대략 85% 가량을 유지 하도록 하는 것이 바람직하다.
Library cahce 영역은 공유 SQL 영역 및 PL/SQL 영역으로 나뉘어 진다.
SQL이 실행될 경우,
문장은 먼저 parsing 되어야 하는데, library cache는 SQL 및 PL/SQL을 미리 저장해 두어,
실제 parsing이 발생하는 빈도를 줄이는 역할을 한다.
OLTP 업무의 경우, 동일한 SQL이 여러차례 수행되므로
적절한 cache 영역을 확보함으로써 성능 향상을 기대할 수 있다.
- 물론 bind variable을 사용하여야만 공유가능한 SQL이 생성된다.
SHARED_POOL_SIZE 값이 적을 경우는 물론이거니와,
너무 이 값을 크게 지정해도 문제가 된다. SHARED_POOL_SIZE가 너무 클 경우,
새로운 SQL 수행시 가용한 메모리 영역을 찾아 내기 위한 latch contention 의 가능성이 높아지게 된다.
V$SGASTAT을 조회하여 free memory를 조사할 수 있으며,
메모리가 낭비되고 있는지 여부도 확인 가능하다.
SELECT name, bytes/1024/1024 ”Size in MB”
FROM V$SGASTAT
WHERE name=’free memory’;
실행 결과는 다음과 같다.
NAME Size in MB
Free memory 39.6002884
이 결과는 shared pool에 39M 공간이 사용되지 않고 있으며,
만약 shared pool의 크기를 70M 로 지정하였다면,
절반 이상의 메모리 공간이 사용되지 않고 낭비되고 있음을 의미한다.
3. SORT_AREA_SIZE
SORT_AREA_SIZE에 대해서는 흔히 잘못된 이해를 하게된다.
대부분의 사용자들은 이 값이 모든 사용자들이
sort 작업에 사용하게 되는 공용 메모리 영역의 크기로 이해를 하는데,
실제로는 사용자 프로세스 별로 사용하게 되는 sort 영역의 크기를 나타낸다.
앞에서 살펴본 두개의 파라미터와 달리, SORT_AREA_SIZE는 SGA영역에 속하지 않는다.
만약 sort_area_size 값이 너무 작다면,
sort 작업 대부분이 사용자의 temporary tablespace에서 디스크를 사용하여 이루어 지게 된다.
SQL 처리시 order by 나, group by 등을 사용할 경우에는 sort 작업이 발생하나.
index 생성등에도 sort가 발생한다.
메모리 sort는 디스크 sort에 비해 훨씬 좋은 성능을 보이므로,
지속적으로 SORT_AREA_SIZE 값을 모니터하여 튜닝을 하는것이 바람직하다.
하지만, 이 값을 너무 크게 지정할 경우,
swapping이 발생하면서 시스템 성능이 급격하게 저하될 수 있다.
* SORT_AREA_SIZE는 세션별로도 지정가능하며, 지정하기 위해서는
ALTER SESSION 권한이 있어야 한다. 특정 세션에서 시스템상의
모든 메모리를 사용하도록 할 경우 시스템 성능이 급격히 저하
될 수도 있다.
4. DBWR_IO_SLAVES
DBWR_IO_SLAVES는 SORT_AREA_SIZE와 마찬가지로 사용자들이 흔히 잘못 이해하는 파라미터로,
Oracle 8 이후 버젼에서 사용된다.
이 파라미터는 Oracle 8 이전에 사용되던 DB_WRITERS 파라미터를 대체한다.
Oracle 8에서는 DB_WRITER_PROCESSES 라는 파라미터가 DB_WRITERS를 대체하지만,
DBWR_IO_SLAVES 파라미터와 함께 사용할 경우 아직까지도 문제점들이 발생한다.
DBWR_IO_SLAVES는 slave writer process가 - OS에서 지원할 경우 -
asynchronous I/O를 수행하도록 허용한다.
DB_WRITERS 및 DBWR_IO_SLAVES 관련 자료는 METALINK에 많이 올라와 있으며,
DB_WRITERS 와 DBWR_IO_SLAVES 는 동시에 사용하 지 못한다는 것을 이해하는 것이 중요하다.
* 참조
5. ROLLBACK_SEGMENTS
이 파라미터는 모든 버젼의 오라클에서 사용되며,
인스턴스 기동중에 온라인 상태로 사용할 rollback segment를 지정한다.
만약 파라미터에서 지정한 rollback segment가 존재하지 않는 것이라면 ora-1534 에러가 발생하며,
데이터베이스는 mount까지만 되고 open 되 지는 않는다.
Rollback segment는 트랜잭션에서 발생하는 변경사항을 기록하여,
트랜잭션이 rollback 되어야 할 경우 이전 상태로 돌리기 위한 각종
정보를 저장하는 영역이다. - Windows 의 undo 기능과 유사함.
Rollback segment는 여러 extent들로 구성되는데,
extent는 round-robin 방식으로 순환되며 사용된다.
즉, 현재 사용되는 extent가 full이 나는 경우 다음 extent를 사용하는 식으로 사용된다.
Rollback segment는 read consistency를 제공해 주고, 트랜잭션을 undo 시킬수 있고,
recovery에 사용되는 등, 데이터베이스에서 매우 중요한 역할을 수행한다.
Read consistency는 업무적으로도 매우 중요한데, 한 사용자 (1번 사용자) 가 데이터를 읽는동안,
다른 사용자가 (2번 사용자) 그 데이터에 변경을 가한다면,
2번 사용자가 데이터 변경을 일관성 있게 종료하기 전가지 1번 사용자는 이전 상태의 데이터,
즉 이전에 commit 된 상태의 데이터를 사용하여야만 데이터 일관성및 정합성이 보장된다.
RBS의 적정 크기는 다른 문제와 마찬가지로 데이터베이스 내에서
사용되는 일반적인 트랜잭션 레벨에 따라 다르다.
RBS extent의 크기와 관련해서는 오라클에서는
extent size와 관련된 (initial,next 값)권고 사항이 존재한다.
Rollback segment의 갯수와 관련해서는,
rollback segment간의 contention 이 발생하지 않도록 조정해 주는 것이 중요하다.
모든 트랜잭션은 RBS의 헤더에 존재하는 트랜잭션 테이블에 정보가 저장된다.
모든 트랜잭션이 이 테이블의 내용을 변경하여야 하므로, contention이 발생할 수 있다.
한 시점에 한개의 트랜잭션이 한개의 rollback segment를 사용하도록 하는
것이 일반적인 원칙이다. 오라클에서는 4개의 트랜잭션당 한개의
rollback segment를 사용하는 것을 권고하지만,
절대적인 기준이 아니라 상대적인 기준으로 보는 것이 바람직하다.
rollback segment간 contention을 조사하기 위해서는 v$waitstat을 조회하면 된다.
다음 query로 rollback segment간 contention을 조회해 볼 수 있다.
SELECT a. name, b.extents, b.rssize, b.xacts, b.waits,
b. gets, optsize, status
FROM V$ROLLNAME A, V$ROLLSTAT B
WHERE a.usn = b.usn;
실행결과는 대략 다음과 같은 형식으로 나타난다.
NAME EXTENTS RSSIZE XACTS WAITS GETS OPTSIZE STATUS
SYSTEM 4 540672 1 0 51 ONLINE
RB1 2 10240000 0 0 427 10240000 ONLINE
RB2 2 10240000 1 0 425 10240000 ONLINE
RB3 2 10240000 1 0 422 10240000 ONLINE
RB4 2 10240000 0 0 421 10240000 ONLINE
위의 질의를 처리한 결과로 ”xacts” ( 트랜잭션의 줄임말 ) 가 계속해서 1 이상이 경우,
rollback segment의 갯수를 늘려주는 것이 contention이 발생할 가능성을 줄여준다.
만약 wait 갯수가 0보다 크고, 특별한 사항에서만 나타나는 것이 아니라 항상 비슷한 상황이라면,
이 경우에도 rollback segment의 갯수를 늘려주는 편이 낫다.
* Rollback segment의 적정 갯수 도출관련 자료는 , 참조
* Rollback segment의 생성, 최적화 관련 자료는 , 참조
6. SORT_AREA_RETAINED_SIZE
init.ora 파일에서 지정하는 sort 작업 관련된 파라미터로 SORT_AREA_RETAINED_SIZE 도 있다.
이 값은 sort 가 끝난 후에도 유지하고자 하는 SORT_AREA_SIZE를 나타낸다.
이 파라미터는 SORT_AREA_SIZE 값과 같거나 적게 지정되어야 한다.
SORT_AREA_RETAINED_SIZE는 SORT_AREA_SIZE와 마찬가지로 적절한 값이 지정되어야 하는데,
소트작업을 수행하기 위해 할당된 메모리 영역이 소트 작업이 끝난 후가 아니라 세션이 종료될 때
까지 유지될 수 있기 때문이다.
SORT_AREA_SIZE 값은 다른 파라미터와 마찬가지로 시스템에 가용한 실제 메모리 크기 이내에서 조정되어야 한다.
일반적으로 권고되는 SORT_AREA_SIZE 값은 65k 에서 1M 사이 에서 결정된다.
7. DB_BLOCK_LRU_EXTENDED_STATISTICS
Oracle 8i 부터는 사용되지 않는 파라미터로, SGA의 buffer cache 값을
증가시키거나 감소시킬 경우 미치는 영향을 예측하기 위한 각종 통계 정보를
수집하는 작업을 활성화 시키거나 비 활성화 시킬 수 있다.
사용자는 DB_BLOCK_BUFFERS 값을 바꾸어 시스템을 재 기동 시키지 않고도,
alter system 명령으로 buffer cache 크기를 조정할 수 있게 해 주시만,
내부적으로는 DB_BLOCK_BUFFERS 값은 데이터 베이스 재 기동시에만 바뀔 수 있다.
통계정보는 X$KCBRBH 테이블에 저장된다.
이 값을 0 이상으로 지정하면 DB_BLOCK_BUFFERS 값을 추가하거나
혹은 추가한 것처럼 simulate 시킬 수 있다.
기능상으로는 튜닝에 많은 도움을 줄 것 처럼 보이나,
많은 문제점을 안고 있는 것으로 알려져 있으므로 오라클에서는
production 환경에서는 사용하지 않도록 권고하고 있다.
8. SHARED_POOL_RESERVE_SIZE
sahred pool의 일정 부분을 larget object을 위해 할당하도록 지정하는 파라미터로,
기본적으로는 shared_pool_size의 5% 정도가 사용된다.
파라미터 값은 byte 단위로 지정한다.
이 파라미터를 지정할 때 유의해야 할 점은 shared pool의 대부분의 영역이
large object에 의해 사용되지 않도록 하고,
large object는 별도의 영역에서 처리되도록 지정하는
것이 관건이다.
[MYSQL] LIKE vs INSTR()
안녕하세요?좀더 다양한 테스트를 해보질 않아서
단정짓기는 어렵지만 우선 테스트한 결과(단순 막노동?)를 올립니다.column 길이가 극히 짧은 것만 테스트한 경우이므로
다소 틀릴 수 도 있습니다.- 8 bytes 이하 <— 본 테스트 포함
- 9 ~ 16 bytes <— 본 테스트 포함
- 17 ~ 32 bytes
- 33 ~ 64 bytes <— 본 테스트 포함(일부)
- 65 ~ 128 bytes
- 129 ~ 256 bytes
- 257 ~ 512 bytes
- 513 - 1024 bytes <– 보통 게시물 평균 크기
- 1025 - 2048 bytes
- 2049 - 4096 bytes
- 4097 bytes 이상이와 같이 여러가지 경우에 대해서 좀더 테스트해 봐야 합니다.
혹시 아래와 비슷한 방법으로 테스트해 주실분 있나요?
——————————–
[MYSQL] LIKE vs INSTR()
0. 배경
1. 영문 검색어 테스트
1-1. 앞 부분 검색
1-2. 중간 부분 검색
1-3. 끝 부분 검색2. 한글 검색어 테스트
2-1. 앞 부분 검색
2-2. 중간 부분 검색
2-3. 끝 부분 검색3. 결과 비교(표)
3-1. 영문 검색어 결과
3-2. 한글 검색어 결과4. 결론
5. 후기
—————————————
0. 배경
TRUE 인 경우만 테스트한 경우임.
… cols LIKE ‘%한글검색어%’
… BINARY cols LIKE ‘%한글검색어%’웹 게시판에서, 후자의 경우 속도가 약 3 배, 또는 그 이상 빠름
(평균 레코드가 길이가 1KB, text 자료형에서 검색)마찬가지로,
… cols LIKE ‘%숫자형조합%’
… BINARY cols LIKE ‘%숫자형조합%’숫자의 검색도 BINARY 로 검색할 경우 빠름.
(정수형만 테스트해 보았음)1. 영문 검색어 테스트
1-1. 앞 부분 검색
1) 대소문자 구별시
DO BENCHMARK(1000000, BINARY ‘MSIEdddddd’ LIKE ‘MSIE%’);
0.47DO BENCHMARK(1000000, INSTR(’MSIEdddddd’,'MSIE’));
0.272) 대소문자 구별없이
DO BENCHMARK(1000000, ‘MSIEdddddd’ LIKE ‘MSIE%’);
0.66DO BENCHMARK(1000000, INSTR(LOWER(’MSIEdddddd’),LOWER(’MSIE’)));
1.861-2. 중간 부분 검색
1) 대소문자 구별시
DO BENCHMARK(1000000, BINARY ‘dddMSIEdddddd’ LIKE ‘%MSIE%’);
0.60DO BENCHMARK(1000000, INSTR(’dddMSIEdddddd’,'MSIE’));
0.442) 대소문자 구별없이
DO BENCHMARK(1000000, ‘dddMSIEdddddd’ LIKE ‘%MSIE%’);
1.15DO BENCHMARK(1000000, INSTR(LOWER(’dddMSIEdddddd’),LOWER(’MSIE’)));
2.151-3. 끝 부분 검색
1) 대소문자 구별시
DO BENCHMARK(1000000, BINARY ‘dddMSIE’ LIKE ‘%MSIE’);
0.56DO BENCHMARK(1000000, INSTR(’dddMSIE’,'MSIE’));
0.432) 대소문자 구별없이
DO BENCHMARK(1000000, ‘dddMSIE’ LIKE ‘%MSIE’);
1.22DO BENCHMARK(1000000, INSTR(LOWER(’dddMSIE’),LOWER(’MSIE’)));
1.772. 한글 검색어 테스트
2-1. 앞부분 검색
1) 대소문자 구별시
DO BENCHMARK(10000000, BINARY ‘한글 테스트’ LIKE ‘한글%’);
Query OK, 0 rows affected (4.70 sec)DO BENCHMARK(10000000, INSTR(’한글 테스트’,'한글’));
Query OK, 0 rows affected (2.73 sec)2) 대소문자 구별없이
DO BENCHMARK(10000000, ‘한글 테스트’ LIKE ‘한글%’);
Query OK, 0 rows affected (6.60 sec)DO BENCHMARK(10000000, INSTR(’한글 테스트’,LOWER(’한글’)));
Query OK, 0 rows affected (7.48 sec)DO BENCHMARK(10000000, INSTR(LOWER(’한글 테스트’),’한글’));
Query OK, 0 rows affected (12.23 sec)DO BENCHMARK(10000000, INSTR(LOWER(’한글 테스트’),LOWER(’한글’)));
Query OK, 0 rows affected (17.25 sec)2-2. 중간 부분 검색
1) 대소문자 구별시
DO BENCHMARK(10000000, BINARY ‘테스트 한글 테스트’ LIKE ‘%한글%’);
Query OK, 0 rows affected (6.63 sec)DO BENCHMARK(10000000, INSTR(’테스트 한글 테스트’,'한글’));
Query OK, 0 rows affected (5.52 sec)2) 대소문자 구별없이
DO BENCHMARK(10000000, ‘테스트 한글 테스트’ LIKE ‘%한글%’);
Query OK, 0 rows affected (19.50 sec)DO BENCHMARK(10000000, INSTR(’테스트 한글 테스트’,LOWER(’한글’)));
Query OK, 0 rows affected (10.60 sec)DO BENCHMARK(10000000, INSTR(LOWER(’테스트 한글 테스트’),’한글’));
Query OK, 0 rows affected (18.39 sec)DO BENCHMARK(10000000, INSTR(LOWER(’테스트 한글 테스트’),LOWER(’한글’)));
Query OK, 0 rows affected (23.25 sec)2-3. 끝 부분 검색
1) 대소문자 구별시
DO BENCHMARK(10000000, BINARY ‘테스트 한글’ LIKE ‘%한글’);
Query OK, 0 rows affected (6.40 sec)DO BENCHMARK(10000000, INSTR(’테스트 한글’,'한글’));
Query OK, 0 rows affected (5.51 sec)2) 대소문자 구별없이
DO BENCHMARK(10000000, ‘테스트 한글’ LIKE ‘%한글’);
Query OK, 0 rows affected (19.51 sec)DO BENCHMARK(10000000, INSTR(’테스트 한글’,LOWER(’한글’)));
Query OK, 0 rows affected (10.60 sec)DO BENCHMARK(10000000, INSTR(LOWER(’테스트 한글’),’한글’));
Query OK, 0 rows affected (15.39 sec)DO BENCHMARK(10000000, INSTR(LOWER(’테스트 한글’),LOWER(’한글’)));
Query OK, 0 rows affected (20.32 sec)3. 결과 비교(표)
각 5번 테스트 최상,최하 버리고 중간값 선택
3-1. 영문 검색어 결과
+———–+———-+——————-+——————-+——————–+
| | | 대소문자 구별 (O) | 대소문자 구별 (X) | |
| 구 분 | 테스트 |———+———+———+———| 비고 |
| | | LIKE | INSTR() | LIKE | INSTR() | |
|———–+———-+———+———+———+———+——————–|
| 앞부분검색| 1,000,000| 0.47 | 0.27 | 0.66 | 1.86 | |
| |10,000,000| 4.69 | 2.72 | 6.55 | 18.58 | |
|———–+———-+———+———+———+———+——————–|
| 중간 부분| 1,000,000| 0.60 | 0.44 | 1.15 | 2.15 | |
| |10,000,000| 5.90 | 4.38 | 11.43 | 21.66 | |
| |10,000,000| 8.38 | 18.65 | 37.46 | 53.59 | 51 bytes |
|———–+———-+———+———+———+———+——————–|
| 뒷부분검색| 1,000,000| 0.56 | 0.43 | 1.22 | 1.77 | |
| |10,000,000| 5.65 | 4.35 | 11.08 | 17.53 | |
|———–+———-+———+———+———+———+——————–|
| 결과 | winner | | winner | | |
+———————-+———+———+———+———+——————–+
*주) 단위 초(seconds), 값이 작을수록 우세영문 검색은 대소문자를 구별하는 경우에 최단 시간이 걸림.
대소문자를 구별하는 검색에서는 검색할 데이터 분포가 중요한데,
앞부분 검색에서는 절대적(길이에 상관없이)으로 INSTR() 함수가 빠르지만,
나머지는 비슷하거나 BINARY … LIKE 연산이 월등함.
즉,
검색 대상 길이가 길어지고 뒤쪽으로 검색할 수록 확실히 BINARY … LIKE 연산이 더 빠름.대소문자를 구별하지 않을 경우에서는,
모든 검색에서 LIKE 연산이 약 1.5 배 이상 우세함.최악의 경우는 INSTR(LOWER(…),LOWER(…)) 로써 이것은 LIKE 연산보다
절대적으로 느림.웹 게시판과 같은 검색에서는, 대부분 대소문자를 구별하지 않고 검색하는
경우가 많으므로 영문 검색은 LIKE 연산이 더 유리함.3-2. 한글 검색어 결과
+———–+———-+——————-+—————————————-+
| | | 대소문자 구별 (O) | 대소문자 구별 (X) |
| 구 분 | 테스트 |———+———+———+———+———+———-|
| | | LIKE | INSTR() | LIKE |INSTR(,L)|INSTR(L,)|INSTR(L,L)|
|———–+———-+———+———+———+———+———+———-|
| 앞부분검색|10,000,000| 4.70 | 2.73 | 6.60 | 7.48 | 12.23 | 17.25 |
|———–+———-+———+———+———+———+———+———-|
| 중간 부분|10,000,000| 6.63 | 5.52 | 19.50 | 10.60 | 18.39 | 23.25 |
| |10,000,000| 21.40 | 29.62 | 95.39 | 35.19 | 66.47 | 71.71 |
|———–+———-+———+———+———+———+———+———-|
| 뒷부분검색|10,000,000| 6.40 | 5.51 | 19.51 | 10.60 | 15.39 | 20.32 |
|———–+———-+———+———+———+———+———+———-|
| 결과 | winner | | |(winnner)| | |
+———————-+———+———+———+———+———+———-+
*주) 단위 초(seconds), 값이 작을수록 우세한글은 외관적으로 대소문자를 구별하지는 않지만, MySQL 의 내부적 연산에서,
대소문자를 구별하도록 실행할 경우, 모든 면에서 항상 우세함.
(*** 이것은 ‘한글’뿐만 아니라 ‘숫자’ 자료형의 경우도 그대로 적용됨 ***)일례로, 앞의 표의 ‘중간 부분’ 검색에서 BINARRY … LIKE 는 LIKE 보다
약 3 배 이상 빠르다는 것을 알 수 있고 INSTR() 함수 역시 마찬가지임.영문검색과 마찬가지로 앞부분 검색을 제외하고, 검색 대상 길이가 길어지고
뒤쪽으로 검색할 수록 확실히 BINARY … LIKE 연산이 더 빠름.역시 최악의 경우는 최악의 경우는 INSET(LOWER(…),LOWER(…)) 임.
4. 결론
앞의 검색 테스트와 그 결과에서 알 수 있듯이, LIKE 연산이 대부분
유리하지만, LIKE 연산이 더 유리한가 아니면 INSTR() 연산이 더 유리한가에
대한 확답은 없습니다.이것은, 검색할 타겟(대부분 columns)의 자료형이 어떤 문자열(문자셋)과
어떤 형태로 분포되어 있느냐에 따라서 속도차이가 날 뿐입니다.그러나,
대부분 웹 게시판 같은 경우는 찾고자하는 단어 배열 형태가 무작위로 분포되어
있고, 또한 사용자 검색어 역시 무작위 임의의 단어있기 때문에 앞에서
테스트한 ‘중간부분 검색’이 실제 실무에서 적용가능한 방법임을
시사하고 있습니다.검색할 column 역시, 대부분 32 또는 255 bytes(특이한 경우 제외) 이상이라는
점에서 다름과 같은 방법을 권장합니다.
+—————-+————————+——————————+
| column 형태 | 권장 (32 bytes 이상) | 예제 |
|—————-+————————+——————————|
| 대문자만 | BINARY str LIKE substr | BINARY cols LIKE ‘%KWD%’ |
| 소문자만 | BINARY str LIKE substr | BINARY cols LIKE ‘%kwd%’ |
| 대+소문자 | str LIKE substr | cols LIKE ‘%kWd%’ |
|—————-+————————+——————————|
| 한글만 | BINARY str LIKE substr | BINARY cols LIKE ‘%한글%’ |
| 한글+대문자 | BINARY str LIKE substr | BINARY cols LIKE ‘%한글KWD%’ |
| 한글+소문자 | BINARY str LIKE substr | BINARY cols LIKE ‘%한글kwd%’ |
| 한글+대+소문자 | str LIKE substr | cols LIKE ‘%한글kWd%’ |
+—————-+————————+——————————+
*주) column length 가 255 bytes 이상, 중간 검색이라는 가정
*주) ‘kwd’ 는 사용자가 검색하는 임의의 단어PHP 적용 예)
< ?php
…
$kwd = ‘사용자 임의 검색어’; // add quoted string$binary = ”; // 초기값
if(!preg_match(’/[a-zA-Z]/’,$kwd)) // 영문문자가 들어가 있는 않는 경우
{ $binary = ‘BINARY’; }$sql = “SELECT … WHERE $binary board.text LIKE ‘%$kwd%’ …”;
…
? >만약, 검색할 column 이 32 bytes 이하이고, 또한 검색위치가 중간이 아닌
앞이거나 뒤쪽이라면, 앞의 결과표를 보고 적절한 방법을 선택해야 합니다.5. 후기
없따앙…….
EOF
25가지 SQL작성법
동일한 정보는 다른 비즈니스 데이터 원천으로부터 검색될 수 있다. 이러한 원천에 익숙해야 한다.
당신은 당신의 데이터베이스 안의 데이터의 크기와 분포를 반드시 알아야 한다.
또한 SQL을 작성하기 전에 비즈니스 개체 안의 관계와 같은 데이터 모델을 전체적으로 이해해야
한다. 이러한 이해는 당신이 여러 테이블에서 정보를 검색하는데 있어서 보다 좋은 쿼리를 작성할
수 있다. DESIGNER/2000과 같은 CASE TOOLS은 다른 비즈니스와 데이터베이스 객체사이의 관계
를 문서화 하는데 좋은 역할을 한다.
2.실제 데이터를 가지고 당신의 쿼리를 검사하라.
대부분의 조직은 개발, 검사, 제품의 3가지 데이터베이스 환경을 가진다. 프로그래머는
어플리케이션을 만들고 검사하는데 개발 데이터베이스 환경을 사용하는데, 이 어플리케이션이
제품 환경으로 전환되기 전에 프로그래머와 사용자에 의해 검사 환경하에서 보다 엄격하게 검토되어
야 한다.
SQL이 검사 환경하에서 테스트될 때, 검사 데이터베이스가 가지고 있는 데이터는 제품 데이터베이스
를 반영해야 한다. 비실제적인 데이터를 가지고 테스트된 SQL문은 제품 안에서는 다르게 작동할 수
있다. 엄격한 테스트를 보장하기 위해서는, 검사 환경하에서의 데이터 분포는 반드시 제품 환경에서
의 분포와 밀접하게 닮아야 한다.
3.동일한 SQL을 사용하라.
가능한한 BIND VARIABLE, STORED PROCEDURE, PACKAGE의 이점을 활용하라. IDENTICAL SQL문
의 이점은 PARSING이 불필요하기에 데이터베이스 서버안에서 메모리 사용의 축소와 빠른 수행을
포함한다. 예로서 아래의 SQL 문은 IDENTICAL하지 않다.
SELECT * FROM EMPLOYEE WHERE EMPID = 10;
SELECT * FROM EMPLOYEE WHERE EMPID = 10;
SELECT * FROM EMPLOYEE WHERE EMPID = 20;
그러나 I_EMPID라고 이름 주어진 BIND VARIABLE을 사용하면 SQL 문은 이렇게 된다.
SELECT * FROM EMPLOYEE WHERE EMPID = :I_EMPID;
4.주의 깊게 인덱스를 사용하라.
테이블상에 모든 필요한 인덱스는 생성되어야 한다. 하지만 너무 많은 인덱스는 성능을 떨어뜨릴
수 있다. 그러면 어떻게 인덱스를 만들 칼럼을 선택해야 하는가?
*최종 사용자에 의해 사용되는 어플리케이션 SQL과 쿼리의 WHERE 절에서 빈번하게 사용되는 칼럼
에 인덱스를 만들어야 한다.
*SQL 문에서 자주 테이블을 JOIN하는데 사용되는 칼럼은 인덱스되어야 한다.
*같은 값을 가지는 ROW가 적은 비율을 가지는 칼럼에 인덱스를 사용하라.
*쿼리의 WHERE 절에서 오직 함수와 OPERATOR로 사용되는 칼럼에는 인덱스를 만들면 안된다.
*자주 변경되거나 인덱스를 만들때 얻는 효율성보다 삽입, 갱신, 삭제로 인해 잃는 효율성이 더 큰
칼럼에는 인덱스를 만들면 안된다. 이러한 OPERATION은 인덱스를 유지하기 위한 필요 때문에 느려
진다.
*UNIQUE 인덱스는 더 나은 선택성 때문에 NONUNIQUE 인덱스보다 좋다. PRIMARY KEY 칼럼에
UNIQUE 인덱스를 사용한다. 그리고 FOREIGN KEY 칼럼과 WHERE 절에서 자주 사용되는 칼럼에는
NONUNIQUE 인덱스를 사용한다.
5.가용한 인덱스 PATH를 만들어라
인덱스를 사용하기 위해서는 기술한 SQL문을 이용할 수 있는 식으로 SQL을 작성하라. OPTIMIZER는
인덱스가 존재하기 때문에 인덱스를 사용하는 ACESS PATH를 사용할 수 없다. 따라서 ACCESS PATH
는 반드시 SQL이 사용할 수 있게 만들어 져야 한다. SQL HINT를 사용하는 것은 인덱스 사용을
보증해주는 방법중 하나이다. 특정 ACCESS PATH를 선택하기 위한 다음의 힌트를 참고 하라
6.가능하면 EXPLAIN과 TKPROF를 사용하라
만약 SQL문이 잘 다듬어지지 않았다면 비록 오라클 데이터베이스가 잘 짜여져 있어도 효율성이 떨어
질 것이다. 이럴 경우 EXPLAIN TKPROF에 능숙해져야 한다. EXPALIN PLAN은 SQL이 사용하는
ACCESS PATH를 발견할 수 있게 해주고 TKPROF는 실제 PERFORMANEC의 통계치를 보여준다.
이 TOOL은 오라클 서버 소프트웨어에 포함되어 있고 SQL의 성능을 향상시켜 준다.
7.OPTIMIZER를 이해하라.
SQL은 RULE-BASED나 COST-BASED중 하나를 이용해서 기동된다.기존의 소프트웨어는 RULE BASED
방식을 채택하고 있다. 그리고 많은 오라클 소프트웨어가 이러한 방식을 오랫동안 사용해 왔다.
그러나 새로 출시된 소프트웨어에 대해서는 COST BASED 방식의 OPTIMIZER를 고려해야 한다.
오라클은 새로 출시되는 프로그램을 COST BASED방식으로 업그레이드 시켜왔으며 이러한 방식은
시스템을 훨씬 더 안정적으로 만들었다. 만약 COST BASED방식의 OPTIMIZER를 사용한다면 반드시
ANALYZE 스키마를 정기적으로 사용해야 한다. ANALYZE스키마는 데이터베이스 통계를 데이터 사전
테이블에 기록하는 역할을 수행하며 그렇게 되면 COST BASED OPTIMIZER가 그것을 사용하게
된다. SQL은 COST BASED OPTIMIZER를 사용할 때만 잘 조정될 수 있다. 만약 RULE BASED에서
COST BASED로 바꾸고 싶다면 데이터베이스를 사용하는 모든 소프트웨어의 모든 SQL문의 성능을
평가해 보아야 한다.
8.지엽적으로 동작하더라도 전역적으로 생각하라
항상 주의할 것은 하나의 SQL문을 조정하기 위해 생긴 데이터베이스안의 변화는 다른 응용프로그램
이나 다른 사용자가 이용하는 다른 명령문에 영향을 미친다는 사실이다.
9.WHERE절은 매우 중요하다.
비록 인덱스가 가용하다고 해도 다음의 WHERE 절은 그 인덱스 ACCESS PATH 를 사용하지 않는다.
(즉 COL1 과 COL2는 같은 테이블에 있으며 인덱스는 COL1에 만들어진다.)
COL1 > COL2
COL1 < COL2
COL1 > = COL2
COL1 <= COL2
COL1 IS NULL
COL1 IS NOT NULL.
인덱스는 NULL값을 갖는 칼럼에는 ROWID를 저장하지 않는다. 따라서 NULL값을 갖는 ROW를 검색할
때는 인덱스를 사용하지 못한다.
COL1 NOT IN (VALUE1, VALUE2 )
COL1 != EXPRESSION
COL1 LIKE ‘%PATTERN’.
이럴 경우 THE LEADING EDGE OF THE INDEX(?) 는 작동되지 않고 인덱스가 사용되지 못하게 한
다. 한편 COL1 LIKE ‘PATTERN %’이나 COL1 LIKE ‘PATTERN % PATTERN%’ 는 한정된 인덱스
스캔을 수행하기 때문에 인덱스를 사용할 수 있다.
NOT EXISTS SUBQUERY
EXPRESSION1 = EXPRESSION2.
인덱스된 컬럼을 포함하는 표현(EXPRESSION), 함수, 계산(CALCULATIONS)은 인덱스를 사용하지
못한다. 다음의 예에서 보면 UPPER SQL 함수를 사용하면 인덱스 스캔을 사용할 수 없고
FULL TABLE SCAN으로 끝나고 만다.
SELECT DEPT_NAME
FROM DEPARTMENT
WHERE UPPER(DEPT_NAME) LIKE ‘SALES%’;
10.레코드 필터링을 위해서는 HAVING보다는 WHERE를 사용하라
인덱스가 걸려있는 칼럼에는 GROUP BY와 같이 HAVING절을 사용하지 마라. 이 경우 인덱스는 사용
되지 않는다. 또한 WHERE절로 된 ROW를 사용하지 마라. 만약 EMP테이블이 DEPTID컬럼에 인덱스
를 가지고 있다면 다음 질의는 HAVING 절을 이용하지 못한다.
SELECT DEPTID,
SUM(SALARY)
FROM EMP
GROUP BY DEPTID
HAVING DEPTID = 100;
그러나 같은 질의가 인덱스를 사용하기 위해 다시 씌여질 수 있다.
SELECT DEPTID,
SUM(SALARY)
FROM EMP
WHERE DEPTID = 100
GROUP BY DEPTID;
11. WHERE 절에 선행 INDEX 칼럼을 명시하라.
복합 인덱스의 경우, 선행 인덱스가 WHERE절에 명시되어 있다면 쿼리는 그 인덱스 를 사용할 것이
다. 다음의 질의는 PART_NUM과 PRODUCT_ID 칼럼에 있는 PRIMARY KEY CONSTRAINT에 기초한
복합 인덱스를 이용할 것이다.
SELECT *
FROM PARTS
WHERE PART_NUM = 100;
반면, 다음의 쿼리는 복합인덱스를 사용하지 않는다.
SELECT *
FROM PARTS
WHERE PRODUCT_ID = 5555;
같은 요청(REQUEST)이 인덱스를 이용하기 위해 다시 씌어 질 수 있다. 다음 질의의 경우,
PART_NUM컬럼은 항상 0 보다 큰 값을 가질것이다.
SELECT *
FROM PARTS
WHERE PART_NUM > 0
AND PRODUCT_ID = 5555;
12.인덱스 SCAN과 FULL TABLE SCAN을 평가하라.
한 행(ROW)의 15% 이상을 검색하는 경우에는 FULL TABLE SCAN이 INDEX ACESS PATH보다 빠르
다. 이런 경우, SQL이 FULL TABLE SCAN을 이용할 수 있도록 여러분 스스로 SQL을 작성하라.
다음의 명령문은 비록 인덱스가 SALARY COLUMN에 만들어져 있어도 인덱스 SCAN을 사용하지 않을
것이다. 첫 번째 SQL에서, FULL HINT를 사용한다면 오라클은 FULL TABLE SCAN을 수행할 것이
다. 인덱스의 사용이 나쁜 점이 더 많다면 아래의 기술을 이용해서 인덱스 수행을 막을수 있다.
SELECT * –+FULL
FROM EMP
WHERE SALARY = 50000;
SELECT *
FROM EMP
WHERE SALARY+0 = 50000;
다음의 명령문은 비록 인덱스가 SS# COLUMN에 있어도 인덱스 SCAN을 사용하지 않을 것이다.
SELECT *
FROM EMP
WHERE SS# || ‘ ‘ = ‘111-22-333′;
오라클이 불분명한 데이터 변환을 수행해야 하는 경우 인덱스가 항상 사용되지않는 것은 아니다.
다음의 예를 보면, EMP 칼럼에 있는 SALARY는 숫자형 칼럼이고 문자형이 숫자값으로 변환된다.
SELECT *
FROM EMP
WHERE SALARY = ‘50000′;
테이블의 행이 15%이거나 그보다 작을 경우 인덱스 스캔은 보다 잘 수행 될 것이다. 왜냐 하면 인덱
스 스캔은 검색된 행(ROW)하나 하나 마다 다중의 논리적인 읽기 검색(READ)을 할 것이기 때문이
다. 그러나 FULL TABLE SCAN은 하나의 논리적 인 읽기 검색 영역 안의 BLOCK에 있는 모든 행들을
읽을 수 있다. 그래서 테이블의 많은 행들에 접근해야 하는 경우에는 FULL TABLE SCAN이 낫다.
예로 다음의 경우를 보자. 만약 EMP TABLE과 그 테이블의 모든 인덱스에 대해 ANALYZE라는 명령어
가 수행된다면, 오라클은 데이터 사전인 USER_TABLES와 USER_INDEXES에 다음과 같은 통계치를
산출해 낸다.
TABLE STATISTICS:
NUM_ROWS = 1000
BLOCKS = 100
INDEX STATISTICS:
BLEVEL = 2
AVG_LEAF_BLOCKS_PER_KEY = 1
AVG_DATA_BLOCKS_PER_KEY = 1
이러한 통계치에 근거해서, 아래에 보이는 것이 각각의 다른 SCAN에 대한 논리적인 읽기(READ)-즉
ACESS된 BLOCK이 될 것이다.
USE OF INDEX TO RETURN ONE ROW = 3
(BLEVEL+(AVG_LEAF_BLOCKS_PER_KEY - 1) +
AVG_DATA_PER_KEY
FULL TABLE SCAN = 100
(BLOCKS)
USE OF INDEX TO RETURN ALL ROWS = 3000
(NUM_ROWS * BLOCKS ACCESSED TO RETURN ONE ROW USING INDEX)
13. 인덱스 스캔에 ORDER BY를 사용하라
오라클의 OPTIMIZER는 , 만약 ORDER BY라는 절이 인덱스된 칼럼에 있다면 인덱스 스캔을 사용할
것이다. 아래의 질의는 이러한 점을 보여 주는 것인데 이 질의는 비록 그 칼럼이 WHERE 절에 명시
되어 있지 않다고 해도 EMPID컬럼에 있는 가용한 인덱스를 사용할 것이다. 이 질의는 인덱스로부
터 각각의 ROWID를 검색하고 그 ROWID를 사용하는 테이블에 접근한다.
SELECT SALARY
FROM EMP
ORDER BY EMPID;
만약 이 질의가 제대로 작동하지 않는다면, 당신은 위에서 명시되었던 FULL HINT를 사용하는 같은 질의를 다시 작성함으로써 다른 대안들을 이용해 볼 수 있다.
14. 자신의 데이터를 알아라
내가 이미 설명한 것처럼, 당신은 당신의 데이터를 상세하게 알고 있어야 한다.예를 들어 당신이
BOXER라는 테이블을 가지고 있고 그 테이블이 유일하지 않은 인덱스를 가진 SEX라는 컬럼과
BOXER_NAME이라는 두 개의 테이블을 가지고 있다고 가정해 보자. 만약 그 테이블에 같은 수의
남자, 여자 복서가 있다면 오라클이 FULL TABLE SCAN을 수행하는 경우 다음의 질의가 훨씬 빠를
것이다.
SELECT BOXER_NAME
FROM BOXER
WHERE SEX = ‘F’;
당신은 다음과 같이 기술함으로써 질의가 FULL TABLE SCAN을 수행하는지를 확실하게 해둘수 있다.
SELECT BOXER_NAME –+ FULL
FROM BOXER
WHERE SEX = ‘F’;
만약 테이블에 980 명의 남성 복서 데이터가 있다면, 질의는 인덱스 SCAN으로 끝나기 때문에
아래형식의 질의가 더 빠를 것이다.
SELECT BOXER_NAME –+ INDEX (BOXER BOXER_SEX)
FROM BOXER
WHERE SEX = ‘F’;
이 예는 데이터의 분포에 대해 잘 알고 있는 것이 얼마나 중요한 가를 예시해 준다. 데이터가 많아지고(GROW) 데이터 분포가 변화하는 것처럼 SQL 도 매우 다양할 것이다. 오라클은 OPTIMIZER 가 테이블에 있는 데이터의 분포를 잘 인식하고 적절한 실행 계획을 선택하도록 하기 위해 오라클 7.3 에 HISTOGRAMS라는 기능을 추가했다.
15. KNOW WHEN TO USE LARGE-TABLE SCANS.
작거나 큰 테이블에서 행들을 추출할 때, 전체 테이블의 검색은 인텍스를 사용한 검색보다 성능이
더 좋을 수도 있다. 매우 큰 테이블의 인덱스 검색은 수많은 인덱스와 테이블 블록의 검색이 필요할
수도 있다. 이러한 블록들이 데이터베이 스 버퍼 캐쉬에 이동되면 가능한한 오래도록 그곳에 머무른
다. 그래서 이러한 블록들이 다른 질의등에 필요하지 않을 수도 있기 때문에, 데이터베이스 버퍼
히트 비율이 감소하며 다중 사용자 시스템의 성능도 저하되기도 한다. 그러나 전체 테이블 검색에
의해서 읽혀진 블록들은 데이터베이스 버퍼 캐쉬에서 일찍 제거가 되므로 데이터베이스 버퍼 캐쉬
히트 비율은 영향을 받지 않게 된다.
16. MINIMIZE TABLE PASSES.
보통, SQL질의시 참조하는 테이블의 숫자를 줄임으로 성능을 향상시킨다. 참조되는 테이블의 숫자
가 적을수록 질의는 빨라진다. 예를 들면 NAME, STATUS, PARENT_INCOME, SELF_INCOME의 네개
의 컬럼으로 이루어진 학생 테이블 에서 부모님에 의존하는 학생과 독립한 학생의 이름과 수입에
대해서 질의시, 이 학생 테이블을 두번 참조하여 질의하게 된다..
SELECT NAME, PARENT_INCOME
FROM STUDENT
WHERE STATUS = 1
UNION
SELECT NAME, SELF_INCOME
FROM STUDENT
WHERE STATUS = 0;
( NAME이 프라이머리 키이며, STATUS는 독립한 학생의 경우는 1, 부모님에 의존적인 학생은 0으로
표시한다)
위의 같은 결과를 테이블을 두번 참조하지 않고도 질의 할 수 있다.
SELECT NAME,PARENT_INCOME*STATUS + SELF_INCOME(1-STATUS)
FROM STUDENT;
17. JOIN TABLES IN THE PROPER ORDER.
다수의 테이블 조인시 테이블들의 조인되는 순서는 매우 중요하다. 전반적으로, 올바른 순서로 테이
블이 조인되었다면 적은 수의 행들이 질의시 참조된다. 언제나 다수의 조인된 테이블들을 질의시
우선 엄격하게 조사하여 행들의 숫자를 최대한으로 줄인다. 이러한 방법으로 옵티마이저는 조인의
차후 단계에서 적은 행들을 조사하게 된다. 뿐만 아니라, 여러 조인을 포함하는 LOOP JOIN에서는
가장 먼저 참조되는 테이블(DRIVING TABLE)이 행들을 최소한으로 리턴하도록 해야한다. 그리고,
마스터와 상세 테이블 조인시에는(예를 들면 ORDER & ORDER LINE ITEM TABLES) 마스터 테이블
을 먼저 연결 시켜야 한다. 규칙에 근거한 옵티마이저의 경우에는 FROM CLAUSE의 마지막 테이블
이 NESTED LOOP JOIN의 DRIVING TABLE이 된다. NESTED LOOP JOIN이 필요한 경우에는 LOOP
의 안쪽의 테이블에는 인텍스를 이용하는 것을 고려할 만하다. EXPLAIN PLAN과 TKPROF는 조인
타입, 조인 테이블 순서, 조인의 단계별 처리된 행들의 숫자들을 나타낸다.
비용에 근거한 옵티마이저의 경우에는 WHERE CLAUSE에 보여지는 테이블의 순서는 옵티마이저가
가장 최적의 실행 계획을 찾으려고 하는 것과 상관 없다. 조인되는 테이블의 순서를 통제하기 위해
서 ORDERED HINT를 사용하는 것이 낫다.
SELECT ORDERS.CUSTID, ORDERS.ORDERNO,
ORDER_LINE_ITEMS.PRODUCTNO –+ORDERED
FROM ORDERS, ORDER_LINE_ITEMS
WHERE ORDERS.ORDERNO = ORDER_LINE_ITEMS.ORDERNO;
18. USE INDEX-ONLY SEARCHES WHEN POSSIBLE.
가능하다면, 인덱스만을 이용하여 질의를 사용하라. 옵티마이저는 오직 인덱스만을 찾을 것이다.
옵티마이저는 SQL을 만족시키는 모든 정보를 인덱스에서 찾을수 있을때,인덱스만을 이용할 것이다.
예를들면, EMP테이블이 LANME과 FNAME의 열에 복합 인덱스를 가지고 있다면 다음의 질의는
인덱스만은 이용할 것이다.
SELECT FNAME
FROM EMP
WHERE LNAME = ‘SMITH’;
반면에 다음의 질의는 인덱스와 테이블을 모두 참조한다.
SELECT FNAME , SALARY
FROM EMP
WHERE LNAME = ‘SMITH’;
19. REDUNDANCY IS GOOD.
WHERE CLAUSE에 가능한한 많은 정보를 제공하라. 예를 들면 WHERE COL1 = COL2 AND COL1 = 10
이라면 옵티마이저는 COL2=10이라고 추론하지만, WHERE COL1 = COL2 AND COL2 = COL3이면
COL1=COL3이라고 초론하지는 않는다.
20. KEEP IT SIMPLE, STUPID.
가능하면 SQL문을 간단하게 만들라. 매우 복잡한 SQL문은 옵티마이저를 무력화시킬 수도 있다.
때로는 다수의 간단한 SQL문이 단일의 복잡한 SQL문보다 성능이 좋을 수도 있다.
오라클의 비용에 근거한 옵티마이저는 아직은 완벽하지않다. 그래서 EXPLAIN PLAN에 주의를
기울여야 한다. 여기서 비용이란 상대적인 개념이기에 정확히 그것이 무엇을 의미하는지 알지
목한다. 하지만 분명한 것은 적은 비용이 보다 좋은 성능을 의미한다는 것이다.
종종 임시 테이블을 사용하여 많은 테이블들을 포함하는 복잡한 SQL 조인을 쪼개는 것이 효율적일
수도 있다. 예를 들면, 조인이 대량의 데이터가 있는 8개의 테이블을 포함할 때, 복잡한 SQL을 두
세개의 SQL로 쪼개는 것이 낫을 수 있다. 각각의 질의는 많아야 네개정도의 테이블들을 포함하며
중간 값을 저장 하는 것이 낫을 수 있다.
21. YOU CAN REACH THE SAME DESTINATION IN DIFFERENT WAYS.
많은 경우에, 하나 이상의 SQL문은 의도한 같은 결과를 줄 수 있다. 각각의 SQL은 다른 접근 경로
를 사용하며 다르게 수행한다. 예를들면, MINUS(-) 산술자는 WHERE NOT IN (SELECT ) OR
ERE NOT EXISTS 보다 더 빠르다.
예를들면, STATE와 AREA_CODE에 각각 다른 인덱스가 걸려 있다. 인덱스에도 불구하고 다음의 질의
는 NOT IN의 사용으로 인해 테이블 전체를 조사하게된다.
SELECT CUSTOMER_ID
FROM CUSTOMERS
WHERE STATE IN (’VA’, ‘DC’, ‘MD’)
AND AREA_CODE NOT IN (804, 410);
그러나 같은 질의가 다음 처럼 쓰여진다면 인덱스를 사용하게 된다
SELECT CUSTOMER_ID
FROM CUSTOMERS
WHERE STATE IN (’VA’, ‘DC’, ‘MD’)
MINUS
SELECT CUSTOMER_ID
FROM CUSTOMERS
WHERE AREA_CODE IN (804, 410);
WHERE절에 OR을 포함한다면 OR대신에 UNION을 사용할 수 있다. 그래서, SQL 질의를 수행하기
전에 먼저 실행계획을 조심스럽게 평가해야 한다. 이러한 평가는 EXPLAIN PLAN AND TKPROF를 이
용하여 할 수 있다.
22. USE THE SPECIAL COLUMNS.
ROWID AND ROWNUM 열을 이용하라. ROWID를 이용하는 것이 가장 빠르다.
예를들면, ROWID를 이용한 UPDATE는 다음과 같다.
SELECT ROWID, SALARY
INTO TEMP_ROWID, TEMP_SALARY
FROM EMPLOYEE;
UPDATE EMPLOYEE
SET SALARY = TEMP_SALARY * 1.5
WHERE ROWID = TEMP_ROWID;
ROWID값은 데이터베이스에서 언제나 같지는 않다. 그래서, SQL이나 응용 프로그램이용시 ROWID값
을 절대화 시키지 말라. 리턴되는 행들의 숫자를 제한 시키기위해 ROWNUM을 이용하라. 만약에 리턴
되는 행들을 정확히 모른다면 리턴되는 행들의 숫자를 제한하기위해 ROWNUM을 사용하라
다음의 질의는 100개 이상의 행들을 리턴하지는 않는다.
SELECT EMPLOYE.SS#, DEPARTMENT.DEPT_NAME
FROM EMPLOYEE, DEPENDENT
WHERE EMPLOYEE.DEPT_ID = DEPARTMENT.DEPT_ID
AND ROWNUM < 100;
23.함축적인 커서대신 명시적인 커서를 사용하라.
함축적 커서는 여분의 FETCH를 발생시킨다. 명시적 커서는 DECLARE, OPEN, FETCH와 CLOSE
CURSOR문을 사용하여 개발자에 의해서 생성된다. 함축 커서는 DELETE, UPDATE, INSERT와
SELECT문을 사용하면 오라클에 의해서 생성된다.
24.오라클 병렬 쿼리 옵션을 찾아서 이용하라.
병렬 쿼리 옵션을 사용하면, 보다 빠른 성능으로 SQL을 병렬로 실행할 수 있다.
오라클 7에서는, 오직 FULL TABLE SCAN에 기반한 쿼리만이 병렬로 수행될 수 있다.
오라클 8에서는, 인덱스가 분할되어있다면 INDEXED RANGE SCANS에 기반한 쿼리도 병렬로 처리될
수 있다. 병렬 쿼리 옵션은 다수의 디스크 드라이버를 포함하는 SMP와 MPP SYSTEM에서만 사용될
수 있다.
오라클 서버는 많은 우수한 특성을 가지고 있지만, 이러한 특성의 존재만으로는 빠른 성능을 보장하
지 않는다. 이러한 특성을 위해서 데이터베이스를 조정해야하며 특성을 이용하기 위해 특별하게 SQL
을 작성해야 한다. 예를 들면, 다음의 SQL은 병렬로 수행될 수 있다.
SELECT * –+PARALLEL(ORDERS,6)
FROM ORDERS;
25.네트웍 소통량을 줄이고 한번에 처리되는 작업량을 늘려라.
ARRAY PROCESSING과 PL/SQL BLOCK을 사용하면 보다 나은 성능을 얻을 수 있고 네트웍 소통량을
줄인다. ARRAY PROCESSING은 하나의 SQL문으로 많은 ROW를 처리할 수 있게 한다. 예를 들면,
INSERT문에서 배열을 사용하면 테이블내의 1,000 ROW를 삽입할 수 있다. 이러한 기술을 사용하면
주요한 성능 향상을 클라이언트/서버와 배치시스템에서 얻어질 수 있다.
복합 SQL문은 과도한 네트웍 소통을 유발할 수 있다. 그러나 만일 SQL문이 단일 PL/SQL 블록안에
있다면, 전체 블록은 오라클 서버에 보내져서 그곳에서 수행되고, 결과는 클라이언트의
APPLICATION에게 돌아온다.
개발자와 사용자는 종종 SQL을 데이터베이스에서 데이터를 검색하고 전송하는 간단한 방법으로 사용
한다. 때때로 직접적으로 SQL을 작성하지 않고 코드 발생기를 사용하여 작성한 APPLICATION은 심
각한 성능 문제를 일으킨다. 이러한 성능감퇴는 데이터베이스가 커지면서 증가한다.
SQL은 유연하기 때문에, 다양한 SQL문으로 같은 결과를 얻을 수 있다. 그러나 어떤 문은 다른 것보
다 더 효율적이다. 여기에 기술된 팁과 기법을 사용하면 빠르게 사용자에게 정보를 제공할 수 있는
APPLICATION과 리포트를 얻을 수 있다.
휴.. 긁어서 붙여놓고 보니까 정말 길당.. ^^;
ps. 오라클을 기준으로 한 설명이지만, mysql에서 sql을 사용할때도 적용되는 이야기입니다.
mysql Optimizing Queries with EXPLAIN
EXPLAINtbl_name
Or:
EXPLAIN [EXTENDED] SELECTselect_options
The EXPLAIN statement can be used either as a synonym for DESCRIBE or as a way to obtain information about how MySQL executes a SELECT statement:
EXPLAINis synonymous withtbl_nameDESCRIBEortbl_nameSHOW COLUMNS FROM.tbl_name
When you precede a
SELECTstatement with the keywordEXPLAIN, MySQL displays information from the optimizer about the query execution plan. That is, MySQL explains how it would process theSELECT, including information about how tables are joined and in which order.
This section describes the second use of EXPLAIN for obtaining query execution plan information. For a description of the DESCRIBE and SHOW COLUMNS statements, see Section 12.3.1, “DESCRIBE Syntax”, and Section 12.5.4.3, “SHOW COLUMNS Syntax”.
With the help of EXPLAIN, you can see where you should add indexes to tables to get a faster SELECT that uses indexes to find rows. You can also use EXPLAIN to check whether the optimizer joins the tables in an optimal order. To force the optimizer to use a join order corresponding to the order in which the tables are named in the SELECT statement, begin the statement with SELECT STRAIGHT_JOIN rather than just SELECT.
If you have a problem with indexes not being used when you believe that they should be, you should run ANALYZE TABLE to update table statistics such as cardinality of keys, that can affect the choices the optimizer makes. See Section 12.5.2.1, “ANALYZE TABLE Syntax”.
EXPLAIN returns a row of information for each table used in the SELECT statement. The tables are listed in the output in the order that MySQL would read them while processing the query. MySQL resolves all joins using a single-sweep multi-join method. This means that MySQL reads a row from the first table, and then finds a matching row in the second table, the third table, and so on. When all tables are processed, MySQL outputs the selected columns and backtracks through the table list until a table is found for which there are more matching rows. The next row is read from this table and the process continues with the next table.
When the EXTENDED keyword is used, EXPLAIN produces extra information that can be viewed by issuing a SHOW WARNINGS statement following the EXPLAIN statement. This information displays how the optimizer qualifies table and column names in the SELECT statement, what the SELECT looks like after the application of rewriting and optimization rules, and possibly other notes about the optimization process.
Each output row from EXPLAIN provides information about one table, and each row contains the following columns:
id
The
SELECTidentifier. This is the sequential number of theSELECTwithin the query.
select_type
The type of
SELECT, which can be any of those shown in the following table:
SIMPLE
Simple SELECT(not usingUNIONor subqueries)
PRIMARY
Outermost SELECT
UNION
Second or later SELECTstatement in aUNION
DEPENDENT UNION
Second or later SELECTstatement in aUNION, dependent on outer query
UNION RESULT
Result of a UNION.
SUBQUERY
First SELECTin subquery
DEPENDENT SUBQUERY
First SELECTin subquery, dependent on outer query
DERIVED
Derived table SELECT(subquery inFROMclause)
UNCACHEABLE SUBQUERY
A subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query
DEPENDENTtypically signifies the use of a correlated subquery. See Section 12.2.8.7, “Correlated Subqueries”.
“DEPENDENT SUBQUERY” evaluation differs from
UNCACHEABLE SUBQUERYevaluation. For “DEPENDENT SUBQUERY”, the subquery is re-evaluated only once for each set of different values of the variables from its outer context. ForUNCACHEABLE SUBQUERY, the subquery is re-evaluated for each row of the outer context. Cacheability of subqueries is subject to the restrictions detailed in Section 6.5.4.1, “How the Query Cache Operates”. For example, referring to user variables makes a subquery uncacheable.
table
The table to which the row of output refers.
type
The join type. The different join types are listed here, ordered from the best type to the worst:
The table has only one row (= system table). This is a special case of the
constjoin type.
The table has at most one matching row, which is read at the start of the query. Because there is only one row, values from the column in this row can be regarded as constants by the rest of the optimizer.
consttables are very fast because they are read only once.
constis used when you compare all parts of aPRIMARY KEYorUNIQUEindex to constant values. In the following queries,tbl_namecan be used as aconsttable:SELECT * FROM
tbl_nameWHEREprimary_key=1;SELECT * FROM
tbl_name
WHEREprimary_key_part1=1 ANDprimary_key_part2=2;
eq_ref
One row is read from this table for each combination of rows from the previous tables. Other than the
systemandconsttypes, this is the best possible join type. It is used when all parts of an index are used by the join and the index is aPRIMARY KEYorUNIQUEindex.
eq_refcan be used for indexed columns that are compared using the=operator. The comparison value can be a constant or an expression that uses columns from tables that are read before this table. In the following examples, MySQL can use aneq_refjoin to processref_table:SELECT * FROM
ref_table,other_table
WHEREref_table.key_column=other_table.column;SELECT * FROM
ref_table,other_table
WHEREref_table.key_column_part1=other_table.column
ANDref_table.key_column_part2=1;
ref
All rows with matching index values are read from this table for each combination of rows from the previous tables.
refis used if the join uses only a leftmost prefix of the key or if the key is not aPRIMARY KEYorUNIQUEindex (in other words, if the join cannot select a single row based on the key value). If the key that is used matches only a few rows, this is a good join type.
refcan be used for indexed columns that are compared using the=or<=>operator. In the following examples, MySQL can use arefjoin to processref_table:SELECT * FROM
ref_tableWHEREkey_column=expr;SELECT * FROM
ref_table,other_table
WHEREref_table.key_column=other_table.column;SELECT * FROM
ref_table,other_table
WHEREref_table.key_column_part1=other_table.column
ANDref_table.key_column_part2=1;
fulltext
The join is performed using a
FULLTEXTindex.
ref_or_null
This join type is like
ref, but with the addition that MySQL does an extra search for rows that containNULLvalues. This join type optimization is used most often in resolving subqueries. In the following examples, MySQL can use aref_or_nulljoin to processref_table:SELECT * FROM
ref_table
WHEREkey_column=exprORkey_columnIS NULL;
See Section 6.2.7, “
IS NULLOptimization”.
index_merge
This join type indicates that the Index Merge optimization is used. In this case, the
keycolumn in the output row contains a list of indexes used, andkey_lencontains a list of the longest key parts for the indexes used. For more information, see Section 6.2.6, “Index Merge Optimization”.
unique_subquery
This type replaces
reffor someINsubqueries of the following form:valueIN (SELECTprimary_keyFROMsingle_tableWHEREsome_expr)
unique_subqueryis just an index lookup function that replaces the subquery completely for better efficiency.
index_subquery
This join type is similar to
unique_subquery. It replacesINsubqueries, but it works for non-unique indexes in subqueries of the following form:valueIN (SELECTkey_columnFROMsingle_tableWHEREsome_expr)
range
Only rows that are in a given range are retrieved, using an index to select the rows. The
keycolumn in the output row indicates which index is used. Thekey_lencontains the longest key part that was used. Therefcolumn isNULLfor this type.
rangecan be used when a key column is compared to a constant using any of the=,<>,>,>=,<,<=,IS NULL,<=>,BETWEEN, orIN()operators:SELECT * FROM
tbl_name
WHEREkey_column= 10;SELECT * FROM
tbl_name
WHEREkey_columnBETWEEN 10 and 20;SELECT * FROM
tbl_name
WHEREkey_columnIN (10,20,30);SELECT * FROM
tbl_name
WHEREkey_part1= 10 ANDkey_part2IN (10,20,30);
index
This join type is the same as
ALL, except that only the index tree is scanned. This usually is faster thanALLbecause the index file usually is smaller than the data file.
MySQL can use this join type when the query uses only columns that are part of a single index.
ALL
A full table scan is done for each combination of rows from the previous tables. This is normally not good if the table is the first table not marked
const, and usually very bad in all other cases. Normally, you can avoidALLby adding indexes that allow row retrieval from the table based on constant values or column values from earlier tables.
possible_keys
The
possible_keyscolumn indicates which indexes MySQL can choose from use to find the rows in this table. Note that this column is totally independent of the order of the tables as displayed in the output fromEXPLAIN. That means that some of the keys inpossible_keysmight not be usable in practice with the generated table order.
If this column is
NULL, there are no relevant indexes. In this case, you may be able to improve the performance of your query by examining theWHEREclause to check whether it refers to some column or columns that would be suitable for indexing. If so, create an appropriate index and check the query withEXPLAINagain. See Section 12.1.2, “ALTER TABLESyntax”.
To see what indexes a table has, use
SHOW INDEX FROM.tbl_name
key
The
keycolumn indicates the key (index) that MySQL actually decided to use. If MySQL decides to use one of thepossible_keysindexes to look up rows, that index is listed as the key value.
It is possible that
keywill name an index that is not present in thepossible_keysvalue. This can happen if none of thepossible_keysindexes are suitable for looking up rows, but all the columns selected by the query are columns of some other index. That is, the named index covers the selected columns, so although it is not used to determine which rows to retrieve, an index scan is more efficient than a data row scan.
For
InnoDB, a secondary index might cover the selected columns even if the query also selects the primary key becauseInnoDBstores the primary key value with each secondary index. IfkeyisNULL, MySQL found no index to use for executing the query more efficiently.
To force MySQL to use or ignore an index listed in the
possible_keyscolumn, useFORCE INDEX,USE INDEX, orIGNORE INDEXin your query. See Section 12.2.7.2, “Index Hint Syntax”.
For
MyISAMandBDBtables, runningANALYZE TABLEhelps the optimizer choose better indexes. ForMyISAMtables, myisamchk –analyze does the same. See Section 12.5.2.1, “ANALYZE TABLESyntax”, and Section 5.9.4, “Table Maintenance and Crash Recovery”.
key_len
The
key_lencolumn indicates the length of the key that MySQL decided to use. The length isNULLif thekeycolumn saysNULL. Note that the value ofkey_lenenables you to determine how many parts of a multiple-part key MySQL actually uses.
ref
The
refcolumn shows which columns or constants are compared to the index named in thekeycolumn to select rows from the table.
rows
The
rowscolumn indicates the number of rows MySQL believes it must examine to execute the query.
Extra
This column contains additional information about how MySQL resolves the query. The following list explains the values that can appear in this column. If you want to make your queries as fast as possible, you should look out for
Extravalues ofUsing filesortandUsing temporary.
Distinct
MySQL is looking for distinct values, so it stops searching for more rows for the current row combination after it has found the first matching row.
Full scan on NULL key
This occurs for subquery optimization as a fallback strategy when the optimizer cannot use an index-lookup access method.
Impossible WHERE noticed after reading const tables
MySQL has read all
const(andsystem) tables and notice that theWHEREclause is always false.
No tables
The query has no
FROMclause, or has aFROM DUALclause.
Not exists
MySQL was able to do a
LEFT JOINoptimization on the query and does not examine more rows in this table for the previous row combination after it finds one row that matches theLEFT JOINcriteria. Here is an example of the type of query that can be optimized this way:SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id
WHERE t2.id IS NULL;
Assume that
t2.idis defined asNOT NULL. In this case, MySQL scanst1and looks up the rows int2using the values oft1.id. If MySQL finds a matching row int2, it knows thatt2.idcan never beNULL, and does not scan through the rest of the rows int2that have the sameidvalue. In other words, for each row int1, MySQL needs to do only a single lookup int2, regardless of how many rows actually match int2.
range checked for each record (index map:N)
MySQL found no good index to use, but found that some of indexes might be used after column values from preceding tables are known. For each row combination in the preceding tables, MySQL checks whether it is possible to use a
rangeorindex_mergeaccess method to retrieve rows. This is not very fast, but is faster than performing a join with no index at all. The applicability criteria are as described in Section 6.2.5, “Range Optimization”, and Section 6.2.6, “Index Merge Optimization”, with the exception that all column values for the preceding table are known and considered to be constants.
Select tables optimized away
The query contained only aggregate functions (
MIN(),MAX()) that were all resolved using an index, orCOUNT(*)forMyISAM, and noGROUP BYclause. The optimizer determined that only one row should be returned.
Using filesort
MySQL must do an extra pass to find out how to retrieve the rows in sorted order. The sort is done by going through all rows according to the join type and storing the sort key and pointer to the row for all rows that match the
WHEREclause. The keys then are sorted and the rows are retrieved in sorted order. See Section 6.2.11, “ORDER BYOptimization”.
Using index
The column information is retrieved from the table using only information in the index tree without having to do an additional seek to read the actual row. This strategy can be used when the query uses only columns that are part of a single index.
Using temporary
To resolve the query, MySQL needs to create a temporary table to hold the result. This typically happens if the query contains
GROUP BYandORDER BYclauses that list columns differently.
Using where
A
WHEREclause is used to restrict which rows to match against the next table or send to the client. Unless you specifically intend to fetch or examine all rows from the table, you may have something wrong in your query if theExtravalue is notUsing whereand the table join type isALLorindex.
Using sort_union(…),Using union(…),Using intersect(…)
These indicate how index scans are merged for the
index_mergejoin type. See Section 6.2.6, “Index Merge Optimization”, for more information.
Using index for group-by
Similar to the
Using indexway of accessing a table,Using index for group-byindicates that MySQL found an index that can be used to retrieve all columns of aGROUP BYorDISTINCTquery without any extra disk access to the actual table. Additionally, the index is used in the most efficient way so that for each group, only a few index entries are read. For details, see Section 6.2.12, “GROUP BYOptimization”.
Using where with pushed condition
This item applies to
NDB Clustertables only. It means that MySQL Cluster is using condition pushdown to improve the efficiency of a direct comparison (=) between a non-indexed column and a constant. In such cases, the condition is “pushed down” to the cluster’s data nodes where it is evaluated in all partitions simultaneously. This eliminates the need to send non-matching rows over the network, and can speed up such queries by a factor of 5 to 10 times over cases where condition pushdown could be but is not used.
Suppose that you have a Cluster table defined as follows:
CREATE TABLE t1 (
a INT,
b INT,
KEY(a)
) ENGINE=NDBCLUSTER;
In this case, condition pushdown can be used with a query such as this one:
SELECT a,b FROM t1 WHERE b = 10;
This can be seen in the output of
EXPLAIN SELECT, as shown here:mysql>
EXPLAIN SELECT a,b FROM t1 WHERE b = 10\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 10
Extra: Using where with pushed condition
Condition pushdown cannot be used with either of these two queries:
SELECT a,b FROM t1 WHERE a = 10;
SELECT a,b FROM t1 WHERE b + 1 = 10;
With regard to the first of these two queries, condition pushdown is not applicable because an index exists on column
a. In the case of the second query, a condition pushdown cannot be employed because the comparison involving the non-indexed columnbis an indirect one. (However, it would apply if you were to reduceb + 1 = 10tob = 9in theWHEREclause.)
However, a condition pushdown may also be employed when an indexed column is compared with a constant using a
>or<operator:mysql>
EXPLAIN SELECT a,b FROM t1 WHERE a<2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: range
possible_keys: a
key: a
key_len: 5
ref: NULL
rows: 2
Extra: Using where with pushed condition
With regard to condition pushdown, keep in mind that:
Condition pushdown is relevant to MySQL Cluster only, and does not occur when executing queries against tables using any other storage engine.
Condition pushdown capability is not used by default. To enable it, you can start mysqld with the
–engine-condition-pushdownoption, or execute the following statement:SET engine_condition_pushdown=On;
Note
Condition pushdown is not supported for columns of any of the
BLOBorTEXTtypes.
Condition pushdown,
Using where with pushed condition, andengine_condition_pushdownwere all introduced in MySQL 5.0 Cluster.
You can get a good indication of how good a join is by taking the product of the values in the rows column of the EXPLAIN output. This should tell you roughly how many rows MySQL must examine to execute the query. If you restrict queries with the max_join_size system variable, this row product also is used to determine which multiple-table SELECT statements to execute and which to abort. See Section 6.5.2, “Tuning Server Parameters”.
The following example shows how a multiple-table join can be optimized progressively based on the information provided by EXPLAIN.
Suppose that you have the SELECT statement shown here and that you plan to examine it using EXPLAIN:
EXPLAIN SELECT tt.TicketNumber, tt.TimeIn,
tt.ProjectReference, tt.EstimatedShipDate,
tt.ActualShipDate, tt.ClientID,
tt.ServiceCodes, tt.RepetitiveID,
tt.CurrentProcess, tt.CurrentDPPerson,
tt.RecordVolume, tt.DPPrinted, et.COUNTRY,
et_1.COUNTRY, do.CUSTNAME
FROM tt, et, et AS et_1, do
WHERE tt.SubmitTime IS NULL
AND tt.ActualPC = et.EMPLOYID
AND tt.AssignedPC = et_1.EMPLOYID
AND tt.ClientID = do.CUSTNMBR;
For this example, make the following assumptions:
The columns being compared have been declared as follows:
Table
Column
Data Type
tt
ActualPC
CHAR(10)
tt
AssignedPC
CHAR(10)
tt
ClientID
CHAR(10)
et
EMPLOYID
CHAR(15)
do
CUSTNMBR
CHAR(15)
The tables have the following indexes:
Table
Index
tt
ActualPC
tt
AssignedPC
tt
ClientID
et
EMPLOYID(primary key)
do
CUSTNMBR(primary key)
The
tt.ActualPCvalues are not evenly distributed.
Initially, before any optimizations have been performed, the EXPLAIN statement produces the following information:
table type possible_keys key key_len ref rows Extra
et ALL PRIMARY NULL NULL NULL 74
do ALL PRIMARY NULL NULL NULL 2135
et_1 ALL PRIMARY NULL NULL NULL 74
tt ALL AssignedPC, NULL NULL NULL 3872
ClientID,
ActualPC
range checked for each record (key map: 35)
Because type is ALL for each table, this output indicates that MySQL is generating a Cartesian product of all the tables; that is, every combination of rows. This takes quite a long time, because the product of the number of rows in each table must be examined. For the case at hand, this product is 74 × 2135 × 74 × 3872 = 45,268,558,720 rows. If the tables were bigger, you can only imagine how long it would take.
One problem here is that MySQL can use indexes on columns more efficiently if they are declared as the same type and size. In this context, VARCHAR and CHAR are considered the same if they are declared as the same size. tt.ActualPC is declared as CHAR(10) and et.EMPLOYID is CHAR(15), so there is a length mismatch.
To fix this disparity between column lengths, use ALTER TABLE to lengthen ActualPC from 10 characters to 15 characters:
mysql>ALTER TABLE tt MODIFY ActualPC VARCHAR(15);
Now tt.ActualPC and et.EMPLOYID are both VARCHAR(15). Executing the EXPLAIN statement again produces this result:
table type possible_keys key key_len ref rows Extra
tt ALL AssignedPC, NULL NULL NULL 3872 Using
ClientID, where
ActualPC
do ALL PRIMARY NULL NULL NULL 2135
range checked for each record (key map: 1)
et_1 ALL PRIMARY NULL NULL NULL 74
range checked for each record (key map: 1)
et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1
This is not perfect, but is much better: The product of the rows values is less by a factor of 74. This version executes in a couple of seconds.
A second alteration can be made to eliminate the column length mismatches for the tt.AssignedPC = et_1.EMPLOYID and tt.ClientID = do.CUSTNMBR comparisons:
mysql>ALTER TABLE tt MODIFY AssignedPC VARCHAR(15),
->MODIFY ClientID VARCHAR(15);
After that modification, EXPLAIN produces the output shown here:
table type possible_keys key key_len ref rows Extra
et ALL PRIMARY NULL NULL NULL 74
tt ref AssignedPC, ActualPC 15 et.EMPLOYID 52 Using
ClientID, where
ActualPC
et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1
do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1
At this point, the query is optimized almost as well as possible. The remaining problem is that, by default, MySQL assumes that values in the tt.ActualPC column are evenly distributed, and that is not the case for the tt table. Fortunately, it is easy to tell MySQL to analyze the key distribution:
mysql>ANALYZE TABLE tt;
With the additional index information, the join is perfect and EXPLAIN produces this result:
table type possible_keys key key_len ref rows Extra
tt ALL AssignedPC NULL NULL NULL 3872 Using
ClientID, where
ActualPC
et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1
et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1
do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1
Note that the rows column in the output from EXPLAIN is an educated guess from the MySQL join optimizer. You should check whether the numbers are even close to the truth by comparing the rows product with the actual number of rows that the query returns. If the numbers are quite different, you might get better performance by using STRAIGHT_JOIN in your SELECT statement and trying to list the tables in a different order in the FROM clause.
MySQL Enterprise. Subscribers to the MySQL Enterprise Monitor regularly receive expert advice on optimization. For more information see http://www.mysql.com/products/enterprise/advisors.html.
Easy Fuzzy Logic with MySql – The end of “no results found
Easy Fuzzy Logic with MySql – The end of “no results found”
As a web programmer I ran into the problem when running a complicated (user) search on MySql that the results are too strict, and thus giving the well known error “no results found”. While good (although not perfect) results exist!
The problem
When a traditional search query is initiated, sql queries are being generated in the terms of:
User search: where tv_manufacturer=”sony” and tv_description =”%widescreen%” and tv_price < 1000;
A user is asking for a Sony television AND that is widescreen AND less then 1000 dollar. This will show very accurate results. But limits the opportunities when (a best matching) TV is $1050. The users would be okay with paying $50 more in real life. But our query won’t allow it. We want to have that (almost perfect match) results shown!
This query can be rewritten by replacing the AND with OR in the query, but by using OR we get inaccurate results because results will show any TV below 1000 dollar OR any Sony OR any widescreen - useless.
The good news is that we can solve this without having to ask a user the factual and nerdy: WIDESCREEN AND (SONY OR 1000 DOLLAR) – way to difficult.
The answer is in what is named ‘fuzzy logic’. Fuzzy logic is more natural and (semi-) intelligent by mathematical logarithms:
User search: a preferably Sony TV with widescreen support for more or less a 1000 dollars, I prefer less. Please.
A few specialists software company’s offer fuzzy logic software, but this is highly tailored to the specific needs of the system.
But mysql has a solution, with a few hacks will result in accurate results.
The solution:
The solution is to be found in the “MATCH AGAINST” function of mysql. It is a text matching system where you can add your preferences, and the query gives points to indicate the score in matching.
Very few people use this, maybe because they are disappointed that it is only matching text. But in this post I will show you how to also integrate a (in the real world less strict) demand like: less then $1000.
We do this by encoding the numbers to a word. In this case the TV price of our tv in the database will be encoded to unique words like “pricemaxthousand”, etc.
All the features of the TV are being stored in a new (text only) column named encodedsqlrow.
So we get this: encodedsqlrow = “sony widescreen pricethousandtotwothousand diagonalthirtyinch”.
With the match against function we can also search “IN BOOLEAN MODE”. This will add ‘preferences’ to every search demand (word) in our query.
The preferences you can give to a demand (word) are in the order of:
+ = Obligated
> = Important
~ = More or less important
- = Without
And last but not least, we can retrieve a score with every results. So the most accurate results can be listed at the top.
With all this together we (a user) can create a search query that will results in more natural human-like picked results.
Creating our query:
if($demandpricemax) < 1000)
$encodedsearch = “>sony +widescreen ~pricemaxthousand”;
Getting the score:
Select tv_manufacturer, MATCH (encodedsqlrow) AGAINST (’$encodedsearch’ IN BOOLEAN MODE) as score
Setting the match search:
WHERE MATCH (encodedsqlrow) AGAINST (’$encodedsearch’ IN BOOLEAN MODE) ORDER BY score DESC
Example Page – intergrated:
For a dutch website I made this function so it matches all studies (1800) against the many demands of a to-be-student. Like he could say: I am searching for a study obligated in Amsterdam with more or less important in the economic field with important average workload important mostly female on a more or less important university.
Many demands, and this will result in accurate results that include studies in Amsterdam although it has mostly male students.
Have any questions or want to bash this text: email adres is on the right hand side of your screen.
Note: the database column (encodedsqlrow) must have an FULLTEXT index (via phpMyAdmin the blue “T” the at ‘actions’. This will make it searchable for the MATCH AGAINST function. Else it won’t work.
Sources:
http://en.wikipedia.org/wiki/Fuzzy_logic
http://www.seattlerobotics.org/encoder/mar98/fuz/flindex.html
http://www.wcc.nl/
http://www.kiesjestudie.nl/l-studietest.html
http://dev.mysql.com/doc/refman/5.0/en/fulltext-boolean.html
http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html
11.8.6. Fine-Tuning MySQL Full-Text Search
MySQL’s full-text search capability has few user-tunable parameters. You can exert more control over full-text searching behavior if you have a MySQL source distribution because some changes require source code modifications. See Section 2.4.15, “MySQL Installation Using a Source Distribution”.
Note that full-text search is carefully tuned for the most effectiveness. Modifying the default behavior in most cases can actually decrease effectiveness. Do not alter the MySQL sources unless you know what you are doing.
Most full-text variables described in this section must be set at server startup time. A server restart is required to change them; they cannot be modified while the server is running.
Some variable changes require that you rebuild the FULLTEXT indexes in your tables. Instructions for doing this are given at the end of this section.
The minimum and maximum lengths of words to be indexed are defined by the
ft_min_word_lenandft_max_word_lensystem variables. (See Section 5.2.3, “System Variables”.) The default minimum value is four characters; the default maximum is version dependent. If you change either value, you must rebuild yourFULLTEXTindexes. For example, if you want three-character words to be searchable, you can set theft_min_word_lenvariable by putting the following lines in an option file:[mysqld]
ft_min_word_len=3
Then you must restart the server and rebuild your
FULLTEXTindexes. Note particularly the remarks regarding myisamchk in the instructions following this list.
To override the default stopword list, set the
ft_stopword_filesystem variable. (See Section 5.2.3, “System Variables”.) The variable value should be the pathname of the file containing the stopword list, or the empty string to disable stopword filtering. After changing the value of this variable or the contents of the stopword file, restart the server and rebuild yourFULLTEXTindexes.
The stopword list is free-form. That is, you may use any non-alphanumeric character such as newline, space, or comma to separate stopwords. Exceptions are the underscore character (“
_”) and a single apostrophe (“‘”) which are treated as part of a word. The character set of the stopword list is the server’s default character set; see Section 9.1.3.1, “Server Character Set and Collation”.
The 50% threshold for natural language searches is determined by the particular weighting scheme chosen. To disable it, look for the following line in
myisam/ftdefs.h:#define GWS_IN_USE GWS_PROB
Change that line to this:
#define GWS_IN_USE GWS_FREQ
Then recompile MySQL. There is no need to rebuild the indexes in this case.
Note
By making this change, you severely decrease MySQL’s ability to provide adequate relevance values for the
MATCH()function. If you really need to search for such common words, it would be better to search usingIN BOOLEAN MODEinstead, which does not observe the 50% threshold.
To change the operators used for boolean full-text searches, set the
ft_boolean_syntaxsystem variable. This variable can be changed while the server is running, but you must have theSUPERprivilege to do so. No rebuilding of indexes is necessary in this case. See Section 5.2.3, “System Variables”, which describes the rules governing how to set this variable.
If you want to change the set of characters that are considered word characters, you can do so in two ways. Suppose that you want to treat the hyphen character (’-') as a word character. Use either of these methods:
Modify the MySQL source: In
myisam/ftdefs.h, see thetrue_word_char()andmisc_word_char()macros. Add‘-’to one of those macros and recompile MySQL.
Modify a character set file: This requires no recompilation. The
true_word_char()macro uses a “character type” table to distinguish letters and numbers from other characters. . You can edit the<ctype><map>contents in one of the character set XML files to specify that‘-’is a “letter.” Then use the given character set for yourFULLTEXTindexes.
After making the modification, you must rebuild the indexes for each table that contains any
FULLTEXTindexes.
If you modify full-text variables that affect indexing (ft_min_word_len, ft_max_word_len, or ft_stopword_file), or if you change the stopword file itself, you must rebuild your FULLTEXT indexes after making the changes and restarting the server. To rebuild the indexes in this case, it is sufficient to do a QUICK repair operation:
mysql>REPAIR TABLEtbl_nameQUICK;
Each table that contains any FULLTEXT index must be repaired as just shown. Otherwise, queries for the table may yield incorrect results, and modifications to the table will cause the server to see the table as corrupt and in need of repair.
Note that if you use myisamchk to perform an operation that modifies table indexes (such as repair or analyze), the FULLTEXT indexes are rebuilt using the default full-text parameter values for minimum word length, maximum word length, and stopword file unless you specify otherwise. This can result in queries failing.
The problem occurs because these parameters are known only by the server. They are not stored in MyISAM index files. To avoid the problem if you have modified the minimum or maximum word length or stopword file values used by the server, specify the same ft_min_word_len, ft_max_word_len, and ft_stopword_file values to myisamchk that you use for mysqld. For example, if you have set the minimum word length to 3, you can repair a table with myisamchk like this:
shell>myisamchk –recover –ft_min_word_len=3tbl_name.MYI
To ensure that myisamchk and the server use the same values for full-text parameters, place each one in both the [mysqld] and [myisamchk] sections of an option file:
[mysqld]
ft_min_word_len=3[myisamchk]
ft_min_word_len=3
An alternative to using myisamchk is to use the REPAIR TABLE, ANALYZE TABLE, OPTIMIZE TABLE, or ALTER TABLE statements. These statements are performed by the server, which knows the proper full-text parameter values to use.
User Comments
| Posted by Attila Nagy on April 19 2004 11:00pm | [Delete] [Edit] |
After changing the stopword file it is not too wise to use the REPAIR TABLE tablename QUICK as mentioned in the documentation when you have lots of records.
I had a table with ca 4 million records, and first I fell for this trap. The repair took more than 10 days. After this I tried DROP INDEX and CREATE INDEX. That took only 40 minutes!
| Posted by James Harvard on July 30 2004 1:25pm | [Delete] [Edit] |
Query cache & min word length changes:
Using REPAIR … QUICK after altering the min word length setting can make it appear that the full text index is not working properly if the query cache is enabled.
After using REPAIR to rebuild the full text index I was confused to still get zero found rows on some queries that should after have returned rows. After using DROP INDEX & CREATE INDEX I got the expected results. My guess is that this is because of the query cache not being flushed for REPAIR as it is for ALTER TABLE (which is what DROP / CREATE INDEX maps to).
I don’t know whether this issue is a candidate for a bug report / feature request or just a documentation update though.
| Posted by John Navratil on May 10 2007 2:58pm | [Delete] [Edit] |
How I added ‘-’ to the list of word characters:
The documentation is weak in two regards: (1) it doesn’t explain how to modify the map and (2) it doesn’t touch on the implications of doing so. I’ll try to solve (1), but cannot begin to speak to (2)
The charsets files exist at the location specified by the “character_sets_dir” system variable (use SHOW VARIABLES to see this) and is typically compiled in as “/usr/share/mysql/charsets”. The name of the file is given by the “character_set_…’ variables. Typically the default is “latin1″. Thus the file I needed to change was /usr/share/mysql/charsets/latin1.xml
The <ctype><map> is the one we are after (other maps are “upper”, “lower”, “unicode” and the various collation maps).
The “ctype” map differs from the others in that is has a leading 0×00 before the character map, the meaning of which is unclear to me. Each entry of the map appears to classify the corresponding character according to the following bitmask:
0×01 Upper-case word character
0×02 Lower-case word character
0×04 Decimal digit
0×08 Printer control (Space/TAB/VT/FF/CR)
0×10 Not-white, not a word
0×20 Control-char (0×00 - 0×1F)
0×40 Space
0×80 Hex digit (0-9, a-f, A-F)
In my case, I needed the dash ‘-’, but nothing else, so I altered the corresponding character position (0×2D - third row, third from the right) from 0×10 (Not-white, not a word) to 0×01 (Upper-case word).
There is little on the web to address this, but some commentary in the forums suggested that this was NOT the way to do this, but rather to write ones own full-text engine as the changing of the <ctype> map has implications for the SQL parser. This may be true, but I suspect SQL parsing would require a stricter classification of characters. The SQL statement “SELECT a-b FROM test” worked for me after this change.
Altering latin1.xml and restarting the server had the desired result.
Finally, there does not appear to be a way to create a new character set or collation without recompiling. If this is true, it might be desirable for the standard distribution to include a “custom” character set for just this sort of thing.
| Posted by Sebastien Salou on July 4 2007 2:20pm | [Delete] [Edit] |
Based on your example with the dash `-`, I had a look to make the single quote `’` (which is a word character by default), a non word character.
Thus,
I had a look on a ascii table, the single quote is corresponding to the hexadecimal value 27.
I opened the file share/mysql/charsets/latin1.xml, I went to the upper map (0×27 is actually on the 3rd rows, 8th col from the left).
I went to this position in the ctype map, and surprised !!! This character is already set to 0×10 Not-white, not a word whereas it is a word character during tests !
From there, I’m pretty lost. Why the single quote is not detected as a non word as it should be ?
Modifying the mysql source in myisam/ftdefs.h works.
I modified the line #define misc_word_char(X) ((X)==’\”)
Is it the only way ?
11.8.2. Boolean Full-Text Searches
MySQL can perform boolean full-text searches using the IN BOOLEAN MODE modifier:
mysql>SELECT * FROM articles WHERE MATCH (title,body)
->AGAINST (’+MySQL -YourSQL’ IN BOOLEAN MODE);
+—-+———————–+————————————-+
| id | title | body |
+—-+———————–+————————————-+
| 1 | MySQL Tutorial | DBMS stands for DataBase … |
| 2 | How To Use MySQL Well | After you went through a … |
| 3 | Optimizing MySQL | In this tutorial we will show … |
| 4 | 1001 MySQL Tricks | 1. Never run mysqld as root. 2. … |
| 6 | MySQL Security | When configured properly, MySQL … |
+—-+———————–+————————————-+
The + and - operators indicate that a word is required to be present or absent, respectively, for a match to occur. Thus, this query retrieves all the rows that contain the word “MySQL” but that do not contain the word “YourSQL”.
Boolean full-text searches have these characteristics:
They do not use the 50% threshold.
They do not automatically sort rows in order of decreasing relevance. You can see this from the preceding query result: The row with the highest relevance is the one that contains “MySQL” twice, but it is listed last, not first.
They can work even without a
FULLTEXTindex, although a search executed in this fashion would be quite slow.
The minimum and maximum word length full-text parameters apply.
The stopword list applies.
The boolean full-text search capability supports the following operators:
+
A leading plus sign indicates that this word must be present in each row that is returned.
-
A leading minus sign indicates that this word must not be present in any of the rows that are returned.
Note: The
-operator acts only to exclude rows that are otherwise matched by other search terms. Thus, a boolean-mode search that contains only terms preceded by-returns an empty result. It does not return “all rows except those containing any of the excluded terms.”
(no operator)
By default (when neither
+nor-is specified) the word is optional, but the rows that contain it are rated higher. This mimics the behavior ofMATCH() … AGAINST()without theIN BOOLEAN MODEmodifier.
> <
These two operators are used to change a word’s contribution to the relevance value that is assigned to a row. The
>operator increases the contribution and the<operator decreases it. See the example following this list.
( )
Parentheses group words into subexpressions. Parenthesized groups can be nested.
~
A leading tilde acts as a negation operator, causing the word’s contribution to the row’s relevance to be negative. This is useful for marking “noise” words. A row containing such a word is rated lower than others, but is not excluded altogether, as it would be with the
-operator.
*
The asterisk serves as the truncation (or wildcard) operator. Unlike the other operators, it should be appended to the word to be affected. Words match if they begin with the word preceding the
*operator.
If a stopword or too-short word is specified with the truncation operator, it will not be stripped from a boolean query. For example, a search for
‘+word +stopword*’will likely return fewer rows than a search for‘+word +stopword’because the former query remains as is and requiresstopword*to be present in a document. The latter query is transformed to+word.
“
A phrase that is enclosed within double quote (“
“”) characters matches only rows that contain the phrase literally, as it was typed. The full-text engine splits the phrase into words, performs a search in theFULLTEXTindex for the words. Prior to MySQL 5.0.3, the engine then performed a substring search for the phrase in the records that were found, so the match must include non-word characters in the phrase. As of MySQL 5.0.3, non-word characters need not be matched exactly: Phrase searching requires only that matches contain exactly the same words as the phrase and in the same order. For example,“test phrase”matches“test, phrase”in MySQL 5.0.3, but not before.
If the phrase contains no words that are in the index, the result is empty. For example, if all words are either stopwords or shorter than the minimum length of indexed words, the result is empty.
The following examples demonstrate some search strings that use boolean full-text operators:
‘apple banana’
Find rows that contain at least one of the two words.
‘+apple +juice’
Find rows that contain both words.
‘+apple macintosh’
Find rows that contain the word “apple”, but rank rows higher if they also contain “macintosh”.
‘+apple -macintosh’
Find rows that contain the word “apple” but not “macintosh”.
‘+apple ~macintosh’
Find rows that contain the word “apple”, but if the row also contains the word “macintosh”, rate it lower than if row does not. This is “softer” than a search for
‘+apple -macintosh’, for which the presence of “macintosh” causes the row not to be returned at all.
‘+apple +(>turnover <strudel)’
Find rows that contain the words “apple” and “turnover”, or “apple” and “strudel” (in any order), but rank “apple turnover” higher than “apple strudel”.
‘apple*’
Find rows that contain words such as “apple”, “apples”, “applesauce”, or “applet”.
‘”some words”‘
Find rows that contain the exact phrase “some words” (for example, rows that contain “some words of wisdom” but not “some noise words”). Note that the “
“” characters that enclose the phrase are operator characters that delimit the phrase. They are not the quotes that enclose the search string itself.
User Comments
| Posted by Christopher Tarnas on April 16 2004 9:18am | [Delete] [Edit] |
Just having a negated term (such as -apple or ~orange) will always return null in boolean mode full text searches.
For example if +apple -macintosh returns:
apple orange
then -macintosh returns the empty set rather than the same or larger result. This seems like buggy behavior.
A workaround is to use NOT MATCH…..
I have found this with both 4.0.16 and 4.0.18
| Posted by Jeff Smith on May 27 2004 1:08pm | [Delete] [Edit] |
Keep in mind that although MATCH() AGAINST() is case-insensitive, it also is basically **accent-insensitive**. In other words, if you do not want _mangé_ to match with _mange_ (this example is in French), you have no choice but to use the BOOLEAN MODE with the double quote operator. This is the only way that MATCH() AGAINST() will make accent-sensitive matches.
E.g.:
SELECT * FROM quotes_table WHERE MATCH (quote) AGAINST (’”mang锑 IN BOOLEAN MODE)
For multiword searches:
SELECT * FROM quotes_table MATCH (quote) AGAINST (’”mangé” “pens锑 IN BOOLEAN MODE)
SELECT * FROM quotes_table MATCH (quote) AGAINST (’+”mangé” +”pens锑 IN BOOLEAN MODE)
Although the double quotes are intended to enable phrase searching, just like any web search engine for example, you can also use them to signify single words where accents and other diacritics matter.
The only drawback to this method seems to be that the asterisk operator is mutually exclusive with the double quote. Or I just haven’t been able to combine both effectively.
| Posted by Rainer Typke on September 8 2004 7:28pm | [Delete] [Edit] |
Be careful with the phrase search when short words are involved!
Words that are shorter than the minimum word length (by default, words with up to 3 characters) are sometimes taken into consideration when you search for phrases, but sometimes not!
Example 1:
A search for the phrase “the creation” will find all records that really contain this phrase, and only those. So, a record containing only “la creation du monde”, even without the accent aigu on the e in creation, won’t be found. This is just fine and what one would expect.
Example 2: A search for the phrase “let it be” won’t find any record, not even records containing something like “The Beatles: Let It Be”. According to the MySQL team, this is not a bug.
I personally find it very counterintuitive to sometimes take short words into consideration for phrase searches, but only if there is at least one properly long word in the search phrase.
| Posted by Vincent Tsang on September 16 2004 1:08am | [Delete] [Edit] |
Why the default value of minimum character length is 4?
there are many words with 4character is meaningful, e.g. see, hot, hat, pie,… Is there any techinical problem when setting this default value to 3?
>Just having a negated term (such as -apple or ~orange) will always return null in boolean mode full text searches.
I have this problem too. Anyone can help?
| Posted by Markus Loponen on October 13 2004 7:56pm | [Delete] [Edit] |
For those of you who interface MySQL with PHP and wonder what the problem is with getting “exact phrases” working properly, here’s the way to go.
$query= “SELECT code, category, header, date FROM articles WHERE MATCH (text,header,summary) AGAINST (’” . stripslashes (str_replace (”"”, “\”", ($_POST['keywords']))) . “‘ IN BOOLEAN MODE)”;
PHP, or some setups or with some browsers, convert double quotes from POST data to their HTML-equivalents even without being asked to do that. The above will fix the issue. Stripslashes() is optional, I prefer to keep it in to keep things looking clean, though the \” doesn’t seem to break the boolean literal search.
| Posted by Rob Thorpe on November 14 2004 6:39pm | [Delete] [Edit] |
It’s also possible to create a prioritized boolean query with the following SQL:
SELECT id, text, MATCH (text) AGAINST (’word1 word2 word3′ in boolean mode)
AS score FROM table1
WHERE MATCH (text) AGAINST (’word1 word2 word3′ in boolean mode) order by score desc;
| Posted by Adam George on December 13 2004 5:32pm | [Delete] [Edit] |
According to the last comment by Rob Thorpe it’s possible to prioritize the boolean query like so:
SELECT id, text, MATCH (text) AGAINST (’word1 word2 word3′ in boolean mode)
AS score FROM table1
WHERE MATCH (text) AGAINST (’word1 word2 word3′ in boolean mode) order by score desc;
I tried this and it failed to work, i.e. all the scores turned out to be ‘1′ even though the number of matches differed from record to record.
| Posted by Brad Satoris on December 13 2004 9:14pm | [Delete] [Edit] |
Boolean searching has two deficiencies: 1) results are not sorted by relevance and; 2) no method by which to weigh certain columns. There is a way around both of these problems. For example, if I have a table of articles and want to weigh the title more heavily than the text, I can do the following:
SELECT *, ( (1.3 * (MATCH(title) AGAINST (’+term +term2′ IN BOOLEAN MODE))) + (0.6 * (MATCH(text) AGAINST (’+term +term2′ IN BOOLEAN MODE))) ) AS relevance FROM [table_name] WHERE ( MATCH(title,text) AGAINST (’+term +term2′ IN BOOLEAN MODE) ) HAVING relevance > 0 ORDER BY relevance DESC;
Here we artificially manipulate the relevancy score to give title more weight by multiplying by the constant 1.3. In the above query, it doesn’t seem to matter whether I have 3 fulltext indexes or just one comprising the title and text columns. From my testing, the results appear to be the same.
| Posted by Joe Laffey on January 5 2005 4:30pm | [Delete] [Edit] |
In response to the note above Posted by Adam George on December 13 2004 7:32pm:
In my tests it would seem that the score returned is an integer equal to the number of words matched. So if you match on 3 words the scores will range from 1 to 3. If you match only on one word, or only one word is matched in any document, then the scores would all be 1.
| Posted by George Gkotsis on January 12 2005 5:50pm | [Delete] [Edit] |
In response to Vincent Tsang:
The default value of minimum character length (which is 4) can be modified. It’s #defined as ft_min_word_len at myisam/ft_static.c of you sources.
Of course you can always use mysql variables (read http://dev.mysql.com/doc/mysql/en/Fulltext_Fine-tuning.html)
Hope this works!
George Gkotsis
| Posted by Richard on February 23 2005 7:11pm | [Delete] [Edit] |
In response to Joe Laffey and Adam George:
To enhance sorting of the results in boolean mode you can do the following:
SELECT id, text, MATCH (text) AGAINST (’word1 word2 word3′)
AS score FROM table1
WHERE MATCH (text) AGAINST (’+word1 +word2 +word3′ in boolean mode) order by score desc;
Using the first MATCH() we get the score in non-boolean search mode (more distinctive). The second MATCH() ensures we really get back only the results we want (with all 3 words). If you want to do ‘any of the words’ search only, it’s better to use non-boolean search instead (unless you are using boolean in order to get rid of 50% treshold limit).
| Posted by kim markegard on June 24 2005 6:31pm | [Delete] [Edit] |
I’m not sure why MATCH/AGAINST uses a different scoring method when in boolean mode and when it’s not. As stated above, if searching 3 terms in boolean mode, the score will be between 1 and 3 (integer). However, if not in boolean mode, the score is a floating point value.
It seems that non-boolean mode returns a “real” relevancy (based on how often each term was found I presume). In boolean mode it only returns how many terms were found. To me, this is not really relevancy. For instance, if searching on 2 terms, one result may have 20 occurrences of each term and another may have only 1 occurrence of each word, yet they will both return “2″ as their relevance.
| Posted by Ben Allfree on September 1 2005 6:20am | [Delete] [Edit] |
This seems to work well for ranking relevance in boolean queries:
select products_id,match(products_model) against (’printer’ ) as Relevance
from products
where match(products_model) against (’+”printer”‘ in boolean mode)
| Posted by Ferenc Fogarasi on October 19 2005 12:24pm | [Delete] [Edit] |
Hi,
if You want to combine the phrase search with the *, simply search for the words separately and apply a having clause.
For example:
If You wish to find `bird cathcing`, `bird cathcer`, `bird cathers`
try this
SELECT column, MATCH( column ) AGAINST (’bird catch’ IN BOOLEAN MODE) AS rank
FROM mytable
WHERE MATCH( column ) AGAINST (’bird catch’ IN BOOLEAN MODE) > 0
HAVING column LIKE ‘%bird catch%’
ORDER BY rank DESC
I know the HAVING clause is slow, but it is only allpied to the rows that match the search criteira.
Have’n tested on big tables, but I have a feeling it works just fine.
| Posted by Nathan Huebner on October 29 2005 2:19pm | [Delete] [Edit] |
I find that IN BOOLEAN MODE is the only thing that actually returns results back. I don’t understand why either. Also, I found that utf8_bin causes a lot of issues with FULLTEXT searching, (not reporting a bug), most likely because it’s some sort of CASE SENSITIVE, or something kind of search, however I changed from utf8_bin to utf8_general_ci, and my BOOLEAN MODE FULLTEXT searching works great.
I noticed another thing that FULLTEXT searching doesn’t do, it doesn’t give an accurate Relevancy.
For example:
I have 646,000 Books, of which I want to search the title for the word Hatchet. In the results, if the book title “Hatchet” is found, it rates 1.5, when it should rate it 100, since it is a 100% match for the word Hatchet. However, this “Hatchet” that was found, is rated 1.5, as well as 30 other book titles, that have the word “Hatchet” in their title, but not necessarily the exact book title “Hatchet”, if you know what I mean.
In this case, I am being forced to make an Array ReArrange. Which will take the MOST RELEVANT, on top of the relevant found by FULLTEXT, and rearrange to meet my needs.
I am doing this:
Take original array, rebuild array, find EXACT matches, put them first. Then find matches starting with the keyword “Hatchet”, and putting them next, and then everything else after that. I wish MySQL would do that for me, but I guess the relevance engine has a function like this, I am just using it incorrectly.
Here is my syntax, if anyone has any ideas how to speed it up too, that would be great:
SELECT ProductID, Title, Description, Price, RetailPrice, MATCH(Title)
AGAINST (’+(>$keyword)’ IN BOOLEAN MODE)
AS m FROM server.book WHERE MATCH(Title)
AGAINST (’+(>$keyword)’ IN BOOLEAN MODE) ORDER BY m DESC, Title ASC LIMIT 50;
Before writing me, test it yourself.
Thanks!
Nathan
| Posted by Nathan Huebner on October 29 2005 2:20pm | [Delete] [Edit] |
Also, if my email isn’t displayed, it’s admin@sellchain.com
I am working on the solution, but if anyone finds it, im sure others could use it.
Nathan
| Posted by Robert Collins on November 8 2005 8:45pm | [Delete] [Edit] |
This works for me so I get a score and the benefits of a boolean search. However, it’s doing two different fulltext searches so it may slow things down a lot:
SELECT *, MATCH(post_content, post_title) AGAINST(’string’) AS `score` FROM posts MATCH(post_content, post_title) AGAINST(’string’ IN BOOLEAN MODE) ORDER BY score DESC LIMIT 10
The boolean mode after the from statement automatically pulls out the 0’s and then the Match statement in the SELECT clause allows me to get the relevance score so I can sort by it.
| Posted by Martin Halford on December 2 2005 12:09am | [Delete] [Edit] |
Following on from Robert’s comment, I’ve been playing around using his technique trying to get ‘exact’ and ‘any word’ searches to work. I’ve been having trouble with plurals, e.g. searching for ‘anchor bolts’ when the fulltext index includes ‘anchors’ and ‘bolt’. This can be solved with the boolean part of the Match statement by trimming and searching for +anchor* +bolt*, but the non-boolean part of the Match statement for relevance is still a problem returning zeros in some instances. My ‘fix’ for this is to search for ‘anchor anchors bolt bolts’ in the non-boolean part (fairly easy to program in php), which seems to solve the problem. Any easier solutions such as like ‘%anchor%’?
| Posted by Michael Newton on March 9 2006 1:54am | [Delete] [Edit] |
It seems that the * modifier can only be applied to the end of a word. Placed at the beginning of a word, it will remove that word from the search altogether.
| Posted by Mary Miller-Clark on April 12 2006 1:36pm | [Delete] [Edit] |
I recently had this experience when using the “match against” in boolean :
“There is something strange about the FULLTEXT SEARCH, MATCH AGAINST IN BOOLEAN MODE that is not intuitive when searching for a PHRASE, and that is the situation where the phrase contains a special character like a backslash(/). The documentation states that anything within the double quotes of the phrase, including punctuation and white space, is used in the search. Apparently this is not totally true.”
Here is the response I received from Paul DuBois after reporting this as a bug:
“A phrase that is enclosed within double quote (`”‘) characters matches only rows that contain the phrase literally, as it was typed. The full-text engine splits the phrase into words, performs a search in the FULLTEXT index for the words. Prior to MySQL 5.0.3, the engine then performed a substring search for the phrase in the records that were found, so the match must include non-word characters in the phrase. As of MySQL 5.0.3, non-word characters need not be matched exactly: Phrase searching requires only that matches contain exactly the same words as the phrase and in the same order. For example, “test phrase” matches “test,phrase” in MySQL 5.0.3, but not before.”
Thank you, Paul, now I understand. It would have saved me a lot of time if I had realized this before. That is why I have posted this here.
| Posted by Justin Laing on October 4 2006 4:50pm | [Delete] [Edit] |
I needed to be able to take a user search that might have words in it that are less then the min word length and return only results that contained all words.
What I did was break the query up into two sets of words, one set contained all the words that were >= ft_min_word_len, the other set contained all the shorter words. I did a fulltext search on the words that met the fulltext search length requirement and augmented it with an AND clause that used LIKE ‘%<shortword1>%’. MySQL uses the fulltext index to narrow down the results and then applies the LIKE conditions, so it stays fast.
This way you don’t need to change your ft_min_word_len to a smaller number, which will make your indexes bigger.
Here’s the regex I used to break things up:
\b(\w{4,})\b
where ft_min_word_len = 4, that will match all words of 4 or more letters.
Example
Search for the string “axle hub nut” will result in:
WHERE MATCH (col) AGAINST (”+axle*” IN BOOLEAN MODE) AND col LIKE ‘%hub%’ AND col LIKE ‘%nut%’
| Posted by Joost on March 30 2007 10:37am | [Delete] [Edit] |
Fulltext boolean mode search returning (relevance):
SELECT MATCH (x) AGAINST (’word1 word2′ IN BOOLEAN MODE) AS relevance
The returned relevance is 0,1 or 2. 0 = no match, 1 = one of the words is matched (word1 or word2), 2 = both word are matched.
When using eg. ‘+word1 word2 word3′ it returns a floating point number (double) which is the relevance (all returned rows contain word1, some are more relevant (they return word2 and word3, others less..they contain word1 and (word2 or word3).. others even less..containing only word1 (relevance = 1).
| Posted by Ben Allfree on October 1 2007 2:58pm | [Delete] [Edit] |
Seemingly inconsistent behavior that others might encounter.
MATCH(..) AGAINST(’-a -b’)
is not the same as
NOT MATCH(..) AGAINST(’+a +b’)
| Posted by Peter Palm on November 13 2007 1:12pm | [Delete] [Edit] |
A query like ‘-macintosh’ will not return any data because too many results would match. From the documentation:
‘A word that matches half of the rows in a table is less likely to locate relevant documents. In fact, it most likely finds plenty of irrelevant documents. We all know this happens far too often when we are trying to find something on the Internet with a search engine. It is with this reasoning that rows containing the word are assigned a low semantic value for the particular data set in which they occur. A given word may reach the 50% threshold in one data set but not another.’
And
MATCH(..) AGAINST(’-a -b’)
is not the same as
NOT MATCH(..) AGAINST(’+a +b’)
is true, take a look at http://en.wikipedia.org/wiki/De_Morgan’s_laws
Data Definition Language
Data Definition Language
From Wikipedia, the free encyclopedia
A Data Definition Language (DDL) is a computer language for defining data structures.
[edit] XML Schema
XML Schema is an example of a pure DDL[citation needed] (although only relevant in the context of XML).
[edit] SQL
A subset of SQL’s instructions form another DDL. These SQL statements define the structure of a database, including rows, columns, tables, indexes, and database specifics such as file locations. DDL SQL statements are more part of the DBMS and have large differences between the SQL variations. DDL SQL commands include the following:
[edit] CREATE statements
Create - To make a new database, table, index, or stored query. A CREATE statement in SQL creates an object inside of a relational database management system (RDBMS). The types of objects that can be created depends on which RDBMS is being used, but most support the creation TABLEs, INDEXes, USERs, and DATABASEs. Some systems (such as PostgreSQL) allow CREATE, and other DDL commands, inside of a transaction and thus they may be rolled back.
[edit] CREATE TABLE
Perhaps the most common CREATE command is the CREATE TABLE command. The typical usage is:
CREATE [TEMP[ORARY]] TABLE [table name] ( [column definitions] ) [table parameters].
Column Definitions: A comma-separated list consisting of any of the following
- Column definition: [column name] [data type] {NULL | NOT NULL} {column options}
- Primary key definition: PRIMARY KEY ( [comma separated column list] )
- CONSTRAINTS: {CONSTRAINT} [constraint definition]
- RDBMS specific functionality
For example, the command to create a table named employees with a few sample columns would be:
CREATE TABLE employees (
id INTEGER PRIMARY KEY,
first_name CHAR(50) null,
last_name CHAR(75) not null,
date_of_birth DATE null
);
[edit] DROP statements
Drop - To destroy an existing database, table, index, or view.
A DROP statement in SQL removes an object from a relational database management system (RDBMS). The types of objects that can be dropped depends on which RDBMS is being used, but most support the dropping of tables, users, and databases. Some systems (such as PostgreSQL) allow DROP and other DDL commands to occur inside of a transaction and thus be rolled back.
The typical usage is simply DROP objecttype objectname. For example, the command to drop a table named employees would be:
DROP TABLE employees;
The DROP statement is distinct from the DELETE statement. For example, a DELETE statement might delete some (or all) data from a table, whereas a DROP statement might remove the entire table from the database.
[edit] ALTER statements
Alter - To modify an existing database object.
An ALTER statement in SQL changes the properties of an object inside of a relational database management system (RDBMS). The types of objects that can be altered depends on which RDBMS is being used.
The typical usage is ALTER objecttype objectname parameters. For example, the command to add a column named bubbles to an existing table named sink would be:
ALTER TABLE sink ADD bubbles INTEGER;
실행계획에서 COST, CARD, BYTE의 의미
cost는 어떤 특별한 측정 단위가 아니고 비용기반 옵티마이저가 하나의 SQL에 대해 여러 개의 실행계획을 작성하여 그 중 최적의 실행계획을 추출하기 위해 생성된 실행계획간에 비교하기 위한 편의상의 단위입니다.
cost가 낮은 경우 좋은 성능을 예상할 수 있습니다. (항상 그러하지는 않습니다)
card는 각 실행계획 단계에서 access된 row 수를 의미합니다.
byte는 각 실행계획 단계에서 access된 byte 수를 의미합니다.
mysql memory 사용량 계산공식
메모리 설정이 가능한지 모르겠네요.
1. 현재 메모리 사용량 = Shared Memory + ( Each Threads Memory * Threads_connected )
2. 과거 최대 메모리 사용량 = Shared Memory + ( Each Threads Memory * Max_used_connections )
3. 최대 사용가능 메모리량 = Shared Memory + ( Each Threads Memory * max_connections )
Shared Memory =
(
key_buffer_size +
query_cache_size +
binlog_cache_size +
innodb_buffer_pool_size +
innodb_additional_mem_pool_size +
innodb_log_buffer_size
)
Each Threads Memory =
(
thread_stack +
net_buffer_length +
max_allowed_packet +
tmp_table_size +
read_buffer_size +
read_rnd_buffer_size +
sort_buffer_size
)
Threads_connected 와 Max_used_connections 는 show status; 하시면 확인하실수 있고
max_connections 외 그외 옵션은 show variables; 를 통해 확인할수 있습니다.
물론 이외에도 다른부분에서(myisamchk, dump)들이 있으나 자주 사용하는 부분이
아니라서 제외 시켜두었습니다. 그리고 Each Threads Memory 들 중에
tmp_table_size 는 사용되지 않는 경우도 있을 것이며 max_allowed_packet 수치는
그보다 작을수도 있을겁니다. 이부분은 저도 확실치 않아서 …
여기서 주의깊게 봐야할것은 “최대 사용가능 메모리량” 이며 서버의 최대 접속수(max_connections)에
왔을때의 메모리 사용량을 계산해보시고 “최대 사용가능 메모리량”이 현재 시스템 메모리보다 크다면
max_connection에 도달하기전에 메모리 오류가 나겠죠.
아파치나 그외 프로그램이 사용하는 memory 에 따라서 시스템마다 달라지겠지만
“최대 사용가능 메모리량”이 시스템의 7~80% 정도면 괜찮지 않을까 생각합니다.
혹시 몰라서 … 이건 메모리 오류등을 막기위한 메모리계산하여 max_connect 을 줄일지
아니면 메모리를 늘릴지를 대략 파악하자는 것이지 최적화 설정을 하는게 아닙니다.
- Anti-Forensic
- Biology
- Drug Culture
- Electronic Music
- Medical
- Medicine
- SEO+WEBPR
- VJ
- Web Design
- 개념
- 게임
- 기초의학
- 데이타베이스
- 독백 (monologue)
- 뮤지션 (musician)
- 분자생리학
- 상식
- 생리학개론
- 서버관련정보
- 성형및시술
- 세포생리학
- 아바타 (avatar)
- 영어공부
- 인터넷활용
- 자작 VJ-Video
- 조직학
- 종양학
- 즐겨찾는곳 (link)
- 증상학
- 질병분류
- 컴퓨터활용
- 컴퓨팅관련노하우
- 코스메틱
- 튜어리즘(Tourism)
- 팁+노하우 (howto)
- 파티 (party)
- 패션
- 프로그래밍
- 피부과학
- 한자공부
- 해킹및보안
User Comments
I think you should say that if the query returns no data, the explain won’t work, and will say ‘Impossible WHERE noticed after reading const tables’.
EXPLAIN appears to be empirical.
EXPLAIN does not look at
an index and a select and determine the potential use of
the index. Instead EXPLAIN appears to look at the actual
data and determine the actual use of the index. In a development
database this becomes problematic requiring the generations
of representative data. Occasionally it is difficult to predict
how the data will effect the application of an index and it
is not clear that your index does not support your select or
your test data does not exercise the index.
More details of what to expect from EXPLAIN should be included
in the document.
In a 5,500 rows tableset - the following query took 202.30 sec in MySQL Control Center prior to a “analyze table partners; analyze table phones;” query!!
select s.id as id, s.label_text, p.phone_number as phonenumber, f.phone_number as faxnumber from partners as s
left join phones as p on s.aid=p.aid and p.phone_type=0 and p.preferred=1
left join phones as f on s.aid=f.aid and f.phone_type=1 and f.preferred=1
where s.partner_type<3 and s.voided_by=0
So - my ‘tip’ is to really RTFM very carefully where it says: run analyze table frequently
(needless to say that after the analyze query - mySQL provided me with the initial 1,000 rows in just under 500 msecs!!)
When creating query’s on large ammounts of data it’s smart to take a look at the optimization part of the documentation.
It will help you understand the way MySQL executes query’s.
Another good thing to read is this http://dev.mysql.com/doc/mysql/en/Data_size.html
The section contains information regarding keys and such which will also help you to improve SELECT perfomance (in regard to the explain method).
I have to add though that explain might give you a lot of usefull info, (IMHO) it lacks output relevant to single table queries. IE when making a FULLTEXT search on one table useing a multi column FULLTEXT index I get a keylen of 0. I’m not sure if this is the same as NULL (none used). A more precise explanation of what actually is going on would be nice.
To follow up on Andres’s post, it should be noted that MySQL 4.0 and 4.1 differ in their use of a ResultSet with EXPLAIN.
In 4.0, you can receive a result with just a mysterious “Comment” column (none of the rest of the columns as described in explain.html) showing why it is angry at you.
In 4.1, you will receive the result set as described and the “Extra” column will contain the error message.
As an example, try the following query on a 4.0 server then on 4.1:
explain select * from mysql.db where 1=0\G
The key_len value is, I belive, given in bytes. So for a multiple-part key you have to know the length in bytes of the columns that make up the key.
For example, consider a key composed of (tinyint_col, integer_col, char5_col). The lengths of the individual columns are 1, 4 and 5 bytes respectively. So if MySQL only uses 1 part of the key you will see a value of 1 for key_len. If the first two parts of the key are used key_len will be 5, or 10 if all the key’s parts are used.
(IMO the actual manual entry for key_len needs more explanation and emphasis, because for a multiple-column key how much of the key is used can have a huge effect on query speed. It would have saved me a lot of time if the manual had explained *how* to work out how much of a multiple-column key is used, instead of me having to work it out by trial and error!)
Add your own comment.