14

1つのmysqlクエリを最適化するためにあなたの助けが必要です。簡単な表を例にとってみましょう。

CREATE TABLE `Modules` (
 `ID` int(11) NOT NULL AUTO_INCREMENT,
 `moduleName` varchar(100) NOT NULL,
 `menuName` varchar(255) NOT NULL,
PRIMARY KEY (`ID`),
KEY `moduleName` (`moduleName`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

いくつかのデータで埋めましょう:

INSERT INTO  `Modules` (`moduleName` ,`menuName`)
VALUES 
    ('abc1',  'name1'), 
    ('abc',  'name2'), 
    ('ddf',  'name3'), 
    ('ccc',  'name4'), 
    ('fer',  'name5');

そして、いくつかのサンプル文字列。それをしましょうabc_def;

従来、検索文字列を含むすべての行を検索しようとしています。

それどころか、私の仕事は、moduleName入力文字列に含まれるすべての行を見つけることです。今のところ、目的の結果を得るために次のクエリがあります。

SELECT `moduleName` ,`menuName` 
FROM `Modules` 
WHERE 'abc_def' LIKE(CONCAT(`moduleName`,'%'))

これは戻ります

moduleName   | menuName 
---------------------------
abc          | name2

問題は、このクエリがインデックスを使用していないことです。

強制的に使用させる方法はありますか?

4

12 に答える 12

11

インデックスとは何か、そしてそれがクエリの高速化にどのように役立つかを誤解しているようです。

あなたのmoduleNameインデックスは何かを見てみましょう。これは基本的に、moduleNameからIDへのマッピングのソートされたリストです。そして、あなたは何を選択していますか?

SELECT moduleName, menuName 
FROM Modules
WHERE 'abc_def' LIKE CONCAT(moduleName,'%');

つまり、moduleNameフィールドの何らかの方法でマップされた値と何らかの関係がある、各行に2つのフィールドが必要です。インデックスはどのように役立ちますか?完全に一致するものはなく、moduleNamesがソートされているという事実を利用する方法はありません。

インデックスから利用する必要があるのは、条件が完全に一致するかどうかをチェックすることです。

SELECT moduleName, menuName 
FROM Modules
WHERE moduleName = LEFT('abc_def', LENGTH(moduleName));

これで完全一致が得られましたが、条件の正しい部分はmoduleNameにも依存するため、この条件は行ごとにチェックされます。彼の場合、MySQLは一致する行の数を予測できませんが、一致する各行のmenuNamesをフェッチするためにランドンディスクアクセスが必要になると予測できるため、MySQLはインデックスを使用しません。

したがって、基本的に2つのアプローチがあります。

  1. 条件によって一致する行の数が大幅に減少することがわかっている場合は、インデックスを強制することができます
  2. もう1つのオプションは、インデックスをカバーする複合インデックスに拡張することです(moduleName, menuName)。そうすると、クエリのすべての結果がインデックスから直接(つまり、メモリから)フェッチされます。

アプローチ#2(SQLfiddleを参照)は、単純なクエリでインデックスヒットを取得し、より大きなテーブルではるかに優れたパフォーマンスを提供するはずです。小さなテーブルでは、私(つまり、lserni-コメントを参照)は、努力する価値があるとは思いません。

于 2013-04-02T22:17:26.140 に答える
7

フィールドで正規表現を効果的に実行しているため、キーは機能しません。ただし、この例では、一致する各moduleNameは'abc_def'以下である必要があるため、少し効率的にすることができます。そのため、次を追加できます。

and moduleName <= 'abc_def'

私が考えることができる他の唯一の選択肢は次のとおりです。

where modleName in ('a','ab','abc','abc_','abc_d','abc_de','abc_def')

きれいではありません。

于 2013-03-26T12:59:40.433 に答える
4

質問にインデックスヒントを追加してみてください。

SELECT `moduleName` ,`menuName` 
FROM `Modules` USE INDEX (col1_index,col2_index) 
WHERE 'abc_def' LIKE(CONCAT(`moduleName`,'%'))
于 2013-03-26T12:58:06.717 に答える
4

dtabaseエンジンは「InnoDB」であるため、 デフォルトでInnoDBのすべてのユーザーデータは、Bツリーインデックスを含むページに保存されます。

B-tree are good for following lookups:
● Exact full value (= xxx)
● Range of values (BETWEEN xx AND yy)
● Column prefix (LIKE 'xx%')
● Leftmost prefix

したがって、クエリでは、インデックスなどを使用して最適化するのではなく、クエリを高速化することを考えることができます。

カバーリングインデックスを作成することで、クエリを高速化できます。

カバーリングインデックスとはall fields selected in a query are covered by an index、、その場合はInnoDB(MyISAMではない)will never read the data in the table, but only use the data in the indexの場合を指しsignificantly speeding up the selectます。InnoDBでは、主キーはすべてのセカンダリインデックスに含まれているため、ある意味ですべてのセカンダリインデックスは複合インデックスであることに注意してください。これは、InnoDBで次のクエリを実行した場合を意味します。

SELECT `moduleName` ,`menuName` 
FROM `Modules1` 
WHERE 'abc_def' LIKE(CONCAT(`moduleName`,'%'))

MySQL will always use a covering index and will not access the actual table

To believe, go to **Explain**

What does Explain statement mean?

table:出力が影響を受けるテーブルを示します。

type:使用されている結合のタイプを示します。最良から最悪まで、タイプは次のとおりです。system、const、eq_ref、ref、range、index、all

possible_keys:このテーブルの行を見つけるためにMySQLが選択できるインデックスを示します

key:MySQLが実際に使用することを決定したキー(インデックス)を示します。MySQLがpossible_keysインデックスの1つを使用して行を検索することを決定した場合、そのインデックスがキー値としてリストされます。

key_len:使用するキーの長さです。短いほど良いです。

ref:使用される列(または定数)

rows:MySQLがクエリを実行するために調べる必要があると考える行数。

extra Extra info:ここで見るのが悪いのは「一時的なものを使う」と「ファイルソートを使う」です

1,990行ありました。

私の実験:

where句にはIsernのソリューションをお勧めします

    case 1) no indexes
explain select `moduleName` ,`menuName`  FROM `Modules1` WHERE moduleName = SUBSTRING('abc_def', 1, LENGTH(moduleName));
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | Modules | ALL  | NULL          | NULL | NULL    | NULL | 2156 | Using where |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

カバーインデックスを作成する方法

case 2) ALTER TABLE `test`.`Modules1` ADD index `mod_name` (`moduleName`)

explain select `moduleName` ,`menuName`  FROM `Modules1` WHERE moduleName = SUBSTRING('abc_def', 1, LENGTH(moduleName));
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | Modules | ALL  | NULL          | NULL | NULL    | NULL | 2156 | Using where |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+

ここでは、使用されているインデックスを示しています。列を参照してください:キー、エクストラ

case 3) ALTER TABLE  `test`.`Modules1` DROP INDEX  `mod_name` ,
ADD INDEX  `mod_name` (  `moduleName` ,  `menuName` )

  explain select `moduleName` ,`menuName`  FROM `Modules1` WHERE moduleName = SUBSTRING('abc_def', 1, LENGTH(moduleName));
+----+-------------+----------+-------+---------------+----------+---------+------+------+--------------------------+
| id | select_type | table    | type  | possible_keys | key      | key_len | ref  | rows | Extra                    |
+----+-------------+----------+-------+---------------+----------+---------+------+------+--------------------------+
|  1 | SIMPLE      | Modules | index | NULL          | mod_name | 1069    | NULL | 2066 | Using where; Using index |
+----+-------------+----------+-------+---------------+----------+---------+------+------+--------------------------+
1 row in set (0.00 sec)


case 4) ALTER TABLE  `test`.`Modules1` DROP INDEX  `mod_name` ,
ADD INDEX  `mod_name` (  `ID` ,  `moduleName` ,  `menuName` )

  explain select `moduleName` ,`menuName`  FROM `Modules1` WHERE moduleName = SUBSTRING('abc_def', 1, LENGTH(moduleName));
+----+-------------+----------+-------+---------------+----------+---------+------+------+--------------------------+
| id | select_type | table    | type  | possible_keys | key      | key_len | ref  | rows | Extra                    |
+----+-------------+----------+-------+---------------+----------+---------+------+------+--------------------------+
|  1 | SIMPLE      | Modules | index | NULL          | mod_name | 1073    | NULL | 2061 | Using where; Using index |
+----+-------------+----------+-------+---------------+----------+---------+------+------+--------------------------+
1 row in set (0.00 sec)

編集:

use where moduleName regexp "^(a|ab|abc|abc_|abc_d|abc_de|abc_def)$";
in place  of substring()
于 2013-04-04T10:22:24.577 に答える
3

@SEARCHING_TEXT AS VARCHAR(500)を宣言します

SET @SEARCHING_TEXT ='ab'

SELECT'moduleName'、'menuName' FROM [MODULES] WHERE FREETEXT(MODULENAME、@SEARCHING_TEXT);

于 2013-03-26T12:58:39.753 に答える
3

これが本当に良いクエリかどうかはわかりませんが、インデックスを利用しています。

SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 7) = `moduleName`
UNION ALL
SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 6) = `moduleName`
UNION ALL
SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 5) = `moduleName`
UNION ALL
SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 4) = `moduleName`
UNION ALL
SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 3) = `moduleName`
UNION ALL
SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 2) = `moduleName`
UNION ALL
SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 1) = `moduleName`

一般的な解決策

そして、これは動的クエリを使用した一般的なソリューションです。

SET @search='abc_def';

SELECT
  CONCAT(
    'SELECT `moduleName` ,`menuName` FROM `Modules` WHERE ',
    GROUP_CONCAT(
      CONCAT(
        'moduleName=\'',
        LEFT(@search, ln),
        '\'') SEPARATOR ' OR ')
    )
FROM
  (SELECT DISTINCT LENGTH(moduleName) ln
   FROM Modules
   WHERE LENGTH(moduleName)<=LENGTH(@search)) s
INTO @sql;

これにより、条件のあるSQLクエリを使用して文字列がWHERE moduleName='abc' OR moduleName='abc_' OR ...作成され、インデックスがあるため、文字列をすばやく作成できるはずです(そうでない場合は、1から最大許容数までの一時的なインデックス付きテーブルを使用して大幅に改善できます。文字列の長さ、たとえばフィドルの例)。次に、クエリを実行するだけです。

PREPARE stmt FROM @sql;
EXECUTE stmt;

こちらのフィドルをご覧ください。

于 2013-03-29T15:26:30.007 に答える
3

私の答えはもっと複雑かもしれません

alter table Modules add column name_index int
alter table Modules add index name_integer_index(name_index);

モジュールテーブルに挿入するときは、moduleNameのint値を計算します。select ascii('a')

クエリを実行するときは、実行する必要があります

SELECT `moduleName`, `menuName`
FROM   `Modules`
WHERE  name_index >
  (select ascii('a')) and name_index < (select ascii('abc_def'))

name_integr_indexを使用します

于 2013-04-01T03:11:59.780 に答える
3

fthiellaによる提案に似ていますが、より柔軟です(より長いストリングに簡単に対処できるため):-

SELECT DISTINCT `moduleName` ,`menuName`
FROM `Modules`
CROSS JOIN (SELECT a.i + b.i * 10 + c.i * 100 + 1 AS anInt FROM integers a, integers b, integers c) Sub1
WHERE LEFT('abc_def', Sub1.anInt) = `moduleName`

これは(入力されたとおり)最大1000文字の文字列を処理しますが、fthiellasソリューションよりも低速です。100文字までの文字列を簡単に切り詰めることができます。その時点では、fthiellasソリューションよりもわずかに速いようです。

長さをチェックすると、少しスピードアップします:-

SELECT SQL_NO_CACHE  DISTINCT `moduleName` ,`menuName`
FROM `Modules`
INNER JOIN (SELECT a.i + b.i * 10 + c.i * 100 + 1 AS anInt FROM integers a, integers b, integers c ) Sub1
ON Sub1.anInt <= LENGTH('abc_def') AND Sub1.anInt <= LENGTH(`moduleName`)
WHERE LEFT('abc_def', Sub1.anInt) = `moduleName`

または、サブセレクトから可能なサブストリングを戻すためのわずかな修正を加えて:-

SELECT SQL_NO_CACHE  DISTINCT `moduleName` ,`menuName`
FROM `Modules`
CROSS JOIN (SELECT DISTINCT LEFT('abc_def', a.i + b.i * 10 + c.i * 100 + 1) AS aStart FROM integers a, integers b, integers c WHERE( a.i + b.i * 10 + c.i * 100 + 1) <= LENGTH('abc_def')) Sub1
WHERE aStart = `moduleName`

これらのソリューションは、値が0から9の単一の列と行を持つ整数のテーブルに依存していることに注意してください。

于 2013-04-02T13:50:01.243 に答える
3

同様のクエリはインデックスを使用していません...しかし、代わりに、このような文字列を検索するための全文インデックスを定義することもできます。しかし、innodbエンジンはそれをサポートしておらず、myisamだけがそれをサポートしています。

于 2013-04-03T23:28:01.247 に答える
3

moduleNameにインデックスキーを追加します。詳細については、 http: //dev.mysql.com/doc/refman/5.0/en/mysql-indexes.htmlB ツリーインデックスの特性を確認してください。

なぜLIKEを使用するのかわからないので、それを避けるのが常に最善です。私の提案は、すべての行にJSONで保存してから、AJAX検索を実行することです。

于 2013-04-04T02:08:05.027 に答える
3

(回答の前の部分が削除されました-同じですが、それについてはより良いnewtoverの回答を参照してください)

newtoverのアプローチ#2(SQLfiddleを参照)は、単純なクエリでインデックスヒットを取得し、より長いテーブルでより優れたパフォーマンスを提供するはずです。

SELECT `moduleName`, `menuName` 
FROM `Modules` 
WHERE moduleName = LEFT('abc_def', LENGTH(moduleName));

だけではなく、多くの列からのデータが必要な場合menuName、つまり、が大きいだけでなく長い場合は、、、およびその長さのみを含むルックアップテーブルにModules移動する方がよい場合があります(1つの関数呼び出しを保存するため) 。moduleNameIDmoduleName

実際に必要な余分なスペースは少なく、moduleNameカーディナリティが低い場合、つまり、moduleName多くのsに沿って数秒繰り返されるmenuName場合、実際にはかなりのスペースを節約することになります。

新しいスキーマは次のようになります。

moduleName_id    integer, keys to Lookup.id
...all the fields in Modules except moduleName...


Lookup table
   id            primary key
   moduleName    varchar
   moduleLength  integer

とクエリ:

SELECT `Lookup`.`moduleName`,`menuName` 
FROM `Modules` INNER JOIN `Lookup`
    ON (`Modules`.`moduleName_id` = Lookup.id)
WHERE `Lookup`.`moduleName` = LEFT('abc_def',
         `Lookup`.`moduleLength`);

このSQLfiddleはスキーマから開始し、上記を実現するようにスキーマを変更します。速度とストレージスペースの改善は、テーブルに配置するデータに大きく依存します。私は意図的に最良の状態に置き(モジュール内の多くの短いフィールド、menuNameそれぞれ平均100秒moduleName)、ストレージスペースの約30%を節約することができました。検索パフォーマンスは約3倍の速さで、おそらくI / Oキャッシングによってバイアスがかかっていたので、誰かがより徹底的なテストを実行しない限り、「かなりのスペースと時間の節約が可能」のままにしておきます。

一方、小さくて単純なテーブルで、メニューとモジュールの数が同じ(つまり、1:1)の場合、速度が大幅に向上しないため、ストレージにわずかなペナルティが発生します。ただし、そのような状況では、関係するスペースと時間は非常に小さいため、複雑さが増しても、上記のより「正規化された」形式がまだ道のりである可能性があります。

于 2013-04-04T11:44:44.683 に答える
0

insteadSUBSTRING('abc_def'、1、LENGTH(moduleName))として2つの関数の1つの関数自体で達成できます

where locate(moduleName, 'abc_def');
于 2013-04-04T15:27:59.233 に答える