私は https://serverfault.com/questions/353888/mysql-full-text-search-cause-high-usage-cp に質問を開いています。
ニュースサイトを構築しました。毎日、Web APIから数万のデータを入力します。
精度の高い検索サービスを提供するために、テーブルではMyISAMを使用して、フルテキストインデックス(タイトル、コンテンツ、日付)を構築しています。私たちのサイトは、2GB RAM、30GBスペースのGodaddy VDSでテスト中です(VDSではスワップの構築が許可されていないため、スワップはありません)。 CPUはIntel(R) Xeon(R) CPU L5609 @ 1.87GHz
です
./mysqltuner.pl
を実行した後
結果が得られます。
-------- General Statistics --------------------------------------------------
[--] Skipped version check for MySQLTuner script
[OK] Currently running supported MySQL version 5.5.20
[OK] Operating on 32-bit architecture with less than 2GB RAM
-------- Storage Engine Statistics -------------------------------------------
[--] Status: -Archive -BDB -Federated +InnoDB -ISAM -NDBCluster
[--] Data in MyISAM tables: 396M (Tables: 39)
[--] Data in InnoDB tables: 208K (Tables: 8)
[!!] Total fragmented tables: 9
-------- Security Recommendations -------------------------------------------
[!!] User '@ip-XX-XX-XX-XX.ip.secureserver.net'
[!!] User '@localhost'
-------- Performance Metrics -------------------------------------------------
[--] Up for: 17h 27m 58s (1M q [20.253 qps], 31K conn, TX: 513M, RX: 303M)
[--] Reads / Writes: 61% / 39%
[--] Total buffers: 168.0M global + 2.7M per thread (151 max threads)
[OK] Maximum possible memory usage: 573.8M (28% of installed RAM)
[OK] Slow queries: 0% (56/1M)
[!!] Highest connection usage: 100% (152/151)
[OK] Key buffer size / total MyISAM indexes: 8.0M/162.5M
[OK] Key buffer hit rate: 100.0% (2B cached / 882K reads)
[!!] Query cache is disabled
[OK] Sorts requiring temporary tables: 0% (0 temp sorts / 17K sorts)
[!!] Temporary tables created on disk: 49% (32K on disk / 64K total)
[!!] Thread cache is disabled
[!!] Table cache hit rate: 0% (400 open / 298K opened)
[OK] Open file limit used: 41% (421/1K)
[!!] Table locks acquired immediately: 77%
[OK] InnoDB data size / buffer pool: 208.0K/128.0M
-------- Recommendations -----------------------------------------------------
General recommendations:
Run OPTIMIZE TABLE to defragment tables for better performance
MySQL started within last 24 hours - recommendations may be inaccurate
Enable the slow query log to troubleshoot bad queries
Reduce or eliminate persistent connections to reduce connection usage
When making adjustments, make tmp_table_size/max_heap_table_size equal
Reduce your SELECT DISTINCT queries without LIMIT clauses
Set thread_cache_size to 4 as a starting value
Increase table_cache gradually to avoid file descriptor limits
Optimize queries and/or use InnoDB to reduce lock wait
Variables to adjust:
max_connections (> 151)
wait_timeout (< 28800)
interactive_timeout (< 28800)
query_cache_size (>= 8M)
tmp_table_size (> 16M)
max_heap_table_size (> 16M)
thread_cache_size (start at 4)
table_cache (> 400)
そしてこちらがmy.cnf
です
[mysqld]
port = 3306
socket = /tmp/mysql.sock
skip-external-locking
key_buffer_size = 256M
max_allowed_packet = 16M
max_connections = 1024
wait_timeout = 5
table_open_cache = 512
sort_buffer_size = 2M
read_buffer_size = 2M
read_rnd_buffer_size = 2M
myisam_sort_buffer_size = 128M
thread_cache_size = 8
query_cache_size= 256M
# Try number of CPU's*2 for thread_concurrency
thread_concurrency = 8
ft_min_Word_len = 2
read_rnd_buffer_size=2M
tmp_table_size=128M
最適化の方法my.cnf
が./mysqltuner.pl
の結果に依存するかどうかはわかりません。
興味深いサプライズがあります。
実行できるフルテキストインデックス作成の唯一の最適化は、my.cnfレベルではありません。それはすべて2つのことについてです:
FULLTEXTインデックスからフィルターで除外する必要がある場合とない場合がある543ストップワード があります。ストップワードのリストはコンパイル時に作成されました。次のように、そのリストを独自のリストで上書きできます。
では、ストップワードリストを作成しましょう。私は通常、英語の記事を唯一のストップワードとして設定しました。
echo "a" > /var/lib/mysql/stopwords.txt
echo "an" >> /var/lib/mysql/stopwords.txt
echo "the" >> /var/lib/mysql/stopwords.txt
次に、/ etc/my.cnfにオプションを追加し、1文字、2文字、および3文字の単語を許可します。
[mysqld]
ft_min_Word_len=1
ft_stopword_file=/var/lib/mysql/stopwords.txt
最後に、mysqlを再起動します
service mysql restart
すでにFULLTEXTインデックスが設定されているテーブルがある場合は、それらのFULLTEXTインデックスを削除して、再度作成する必要があります。
フルテーブルインデックスを使用したMySQLクエリに関するほとんど知られていない事実は次のとおりです。MySQLクエリオプティマイザーがFULLTEXTインデックスの使用を完全に停止し、フルテーブルスキャンを実行する場合があります。
次に例を示します。
use test
drop table if exists ft_test;
create table ft_test
(
id int not null auto_increment,
txt text,
primary key (id),
FULLTEXT (txt)
) ENGINE=MyISAM;
insert into ft_test (txt) values
('mount camaroon'),('mount camaron'),('mount camnaroon'),
('mount cameroon'),('mount cemeroon'),('mount camnaroon'),
('mount camraon'),('mount camaraon'),('mount camaran'),
('mount camnaraon'),('mount cameroan'),('mount cemeroan'),
('mount camnaraon'),('munt camraon'),('munt camaraon'),
('munt camaran'),('munt camnaraon'),('munt cameroan'),
('munt cemeroan'),('munt camnaraon'),('mount camraan');
select * from ft_test WHERE MATCH(txt) AGAINST ("+mount +cameroon" IN BOOLEAN MODE);
ロードされたサンプルデータは次のとおりです。
mysql> use test
Database changed
mysql> drop table if exists ft_test;
Query OK, 0 rows affected (0.00 sec)
mysql> create table ft_test
-> (
-> id int not null auto_increment,
-> txt text,
-> primary key (id),
-> FULLTEXT (txt)
-> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.03 sec)
mysql> insert into ft_test (txt) values
-> ('mount camaroon'),('mount camaron'),('mount camnaroon'),
-> ('mount cameroon'),('mount cemeroon'),('mount camnaroon'),
-> ('mount camraon'),('mount camaraon'),('mount camaran'),
-> ('mount camnaraon'),('mount cameroan'),('mount cemeroan'),
-> ('mount camnaraon'),('munt camraon'),('munt camaraon'),
-> ('munt camaran'),('munt camnaraon'),('munt cameroan'),
-> ('munt cemeroan'),('munt camnaraon'),('mount camraan');
Query OK, 21 rows affected (0.00 sec)
Records: 21 Duplicates: 0 Warnings: 0
mysql>
これはサンプルクエリとそのEXPLAINプランです
mysql> select * from ft_test WHERE MATCH(txt) AGAINST ("cameroon" IN BOOLEAN MODE);
+----+----------------+
| id | txt |
+----+----------------+
| 4 | mount cameroon |
+----+----------------+
1 row in set (0.00 sec)
mysql> explain select * from ft_test WHERE MATCH(txt) AGAINST ("cameroon" IN BOOLEAN MODE)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: ft_test
type: fulltext
possible_keys: txt
key: txt
key_len: 0
ref:
rows: 1
Extra: Using where
1 row in set (0.00 sec)
mysql>
OKフルテキストインデックスが使用されています。
さて、クエリを少し変更しましょう
mysql> select * from ft_test WHERE MATCH(txt) AGAINST ("cameroon" IN BOOLEAN MODE) = 1;
+----+----------------+
| id | txt |
+----+----------------+
| 4 | mount cameroon |
+----+----------------+
1 row in set (0.00 sec)
mysql> explain select * from ft_test WHERE MATCH(txt) AGAINST ("cameroon" IN BOOLEAN MODE) = 1\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: ft_test
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 21
Extra: Using where
1 row in set (0.00 sec)
mysql>
OMG FULLTEXTインデックスはどうなりましたか。 MySQLクエリオプティマイザは基本的にそれを回避しました。 ft_testテーブルを使用してJOINを実行していた場合、フルテキスト検索でWHERE句が発行され、同じことが行われると、クエリの残りの部分でいったい何が発生するかがわかります。
解決策は、クエリをリファクタリングしてFULLTEXT検索を分離し、キーのみを収集することです。次に、それらのキーを元のテーブルに左結合します。
例
SELECT B.*
FROM (SELECT id from ft_test
WHERE MATCH(txt) AGAINST ("+cameroon" IN BOOLEAN MODE)) A
LEFT JOIN ft_test B USING (id);
このクエリの場合、結果とそのEXPLAINは次のとおりです
mysql> SELECT B.*
-> FROM (SELECT id from ft_test
-> WHERE MATCH(txt) AGAINST ("+cameroon" IN BOOLEAN MODE)) A
-> LEFT JOIN ft_test B USING (id);
+----+----------------+
| id | txt |
+----+----------------+
| 4 | mount cameroon |
+----+----------------+
1 row in set (0.00 sec)
mysql> explain SELECT B.*
-> FROM (SELECT id from ft_test
-> WHERE MATCH(txt) AGAINST ("+cameroon" IN BOOLEAN MODE)) A
-> LEFT JOIN ft_test B USING (id)\G
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: <derived2>
type: system
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1
Extra:
*************************** 2. row ***************************
id: 1
select_type: PRIMARY
table: B
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: const
rows: 1
Extra:
*************************** 3. row ***************************
id: 2
select_type: DERIVED
table: ft_test
type: fulltext
possible_keys: txt
key: txt
key_len: 0
ref:
rows: 1
Extra: Using where
3 rows in set (0.00 sec)
mysql>
EXPLAINプランのDERIVED2部分では、FULLTEXTインデックスが実際に使用されていることに注意してください。
データベースに必要なストップワードの数を決定し、そのストップワードリストを作成して構成し、すべてのFULLTEXTインデックスを作成/再作成する習慣を身に付ける必要があります。また、MySQL Query Optimizerが不正なEXPLAINプランを生成したり、EXPLAINプランに参加している残りのクエリのインデックスを無効にしたりしないように、FULLTEXT検索クエリをリファクタリングする習慣を身に付ける必要があります。