web-dev-qa-db-ja.com

なぜMySQLはこの注文に対して強制的にもインデックスを無視するのですか?

私はEXPLAINを実行します:

_mysql> explain select last_name from employees order by last_name;
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL | 10031 | Using filesort |
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
1 row in set (0.00 sec)  
_

私のテーブルのインデックス:

_mysql> show index from employees;  
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+  
| Table     | Non_unique | Key_name      | Seq_in_index | Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |  
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+  
| employees |          0 | PRIMARY       |            1 | subsidiary_id | A         |           6 |     NULL | NULL   |      | BTREE      |         |               |  
| employees |          0 | PRIMARY       |            2 | employee_id   | A         |       10031 |     NULL | NULL   |      | BTREE      |         |               |  
| employees |          1 | idx_last_name |            1 | last_name     | A         |       10031 |      700 | NULL   |      | BTREE      |         |               |  
| employees |          1 | date_of_birth |            1 | date_of_birth | A         |       10031 |     NULL | NULL   | YES  | BTREE      |         |               |  
| employees |          1 | date_of_birth |            2 | subsidiary_id | A         |       10031 |     NULL | NULL   |      | BTREE      |         |               |  
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+  
5 rows in set (0.02 sec)  
_

Last_nameにインデックスがありますが、オプティマイザはそれを使用しません。
私もです:

_mysql> explain select last_name from employees force index(idx_last_name) order by last_name;  
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows  | Extra          |  
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL | 10031 | Using filesort |  
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
1 row in set (0.00 sec)  
_

しかしまだインデックスはnot使用されています!ここで何が悪いのですか?
それは、インデックスが_NON_UNIQUE_であるという事実に関係していますか?ところで、last_nameはVARCHAR(1000)です

@ RolandoMySQLDBAによって要求された更新

_mysql> SELECT COUNT(DISTINCT last_name) DistinctCount FROM employees;  
+---------------+  
| DistinctCount |  
+---------------+  
|         10000 |  
+---------------+  
1 row in set (0.05 sec)  


mysql> SELECT COUNT(1) FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A;  
+----------+  
| COUNT(1) |  
+----------+  
|        0 |  
+----------+  
1 row in set (0.15 sec)  
_
14
Cratylus

実際、ここでの問題は、これがプレフィックスインデックスのように見えることです。質問にテーブル定義は表示されませんが、_sub_part_ = 700?列全体にインデックスを付けていないので、インデックスはソートに使用できず、カバーするインデックスとしても役立ちません。これは、WHEREに「一致する可能性がある」行を見つけるためにのみ使用でき、サーバーレイヤー(ストレージエンジンの上)は、一致した行をさらにフィルター処理する必要があります。姓に本当に1000文字が必要ですか?


更新 説明するには:500行を超えるlitleを含むテーブルテストテーブルがあり、それぞれに列domain_name VARCHAR(254) NOT NULLにWebサイトのドメイン名があり、インデックスがありません。

_mysql> alter table keydemo add key(domain_name);
Query OK, 0 rows affected (0.17 sec)
Records: 0  Duplicates: 0  Warnings: 0
_

完全な列にインデックスを付けて、クエリはインデックスを使用します。

_mysql> explain select domain_name from keydemo order by domain_name;
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
| id | select_type | table   | type  | possible_keys | key         | key_len | ref  | rows | Extra       |
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
|  1 | SIMPLE      | keydemo | index | NULL          | domain_name | 764     | NULL |  541 | Using index |
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
1 row in set (0.01 sec)
_

したがって、ここでそのインデックスを削除し、domain_nameの最初の200文字だけをインデックス化します。

_mysql> alter table keydemo drop key domain_name;
Query OK, 0 rows affected (0.11 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table keydemo add key(domain_name(200));
Query OK, 0 rows affected (0.08 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select domain_name from keydemo order by domain_name;
+----+-------------+---------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+---------+------+---------------+------+---------+------+------+----------------+
|  1 | SIMPLE      | keydemo | ALL  | NULL          | NULL | NULL    | NULL |  541 | Using filesort |
+----+-------------+---------+------+---------------+------+---------+------+------+----------------+
1 row in set (0.00 sec)

mysql>
_

出来上がり。

また、200文字のインデックスは、列の最も長い値よりも長いことに注意してください...

_mysql> select max(length(domain_name)) from keydemo;
+--------------------------+
| max(length(domain_name)) |
+--------------------------+
|                       43 |
+--------------------------+
1 row in set (0.04 sec)
_

...しかし、違いはありません。プレフィックス長で宣言されたインデックスは、ルックアップにのみ使用でき、並べ替えには使用できません。また、完全な列値が含まれていないため、カバリングインデックスとしては使用できません。

また、上記のクエリはInnoDBテーブルで実行されましたが、MyISAMテーブルで実行すると、実質的に同じ結果が得られます。この場合のonlyの違いは、rowsのInnoDBカウントがわずかにオフ(541)であるのに対して、MyISAMは正確な行数(563)を示しています。 2つのストレージエンジンがインデックスダイブを非常に異なる方法で処理するため、通常の動作。

私はまだlast_name列が必要以上に大きいと主張しますが、InnoDBを使用して実行している場合、列全体にインデックスを付けることは可能です MySQL 5.5または5.6:

デフォルトでは、単一列インデックスのインデックスキーは最大767バイトです。同じ長さ制限が、すべてのインデックスキープレフィックスに適用されます。セクション13.1.13「_CREATE INDEX_構文」を参照してください。たとえば、_UTF-8_文字セットと各文字の最大3バイトを想定して、TEXTまたはVARCHAR列の列プレフィックスインデックスが255文字を超えると、この制限に達する可能性があります。 _innodb_large_prefix_ 構成オプションが有効になっている場合、InnoDBおよびDYNAMIC行フォーマットを使用するCOMPRESSEDテーブルの場合、この長さ制限は3072バイトに引き上げられます。

http://dev.mysql.com/doc/refman/5.5/en/innodb-restrictions.html

19

コメントではフォーマットがサポートされないので、私は回答しました。RolandoMySQLDBAがgen_clust_indexとinnodbについて話しました。そして、これはinnodbベースのテーブルでは非常に重要です。これは、Cコードを分析できる必要があるため、DBAの通常の知識よりもさらに進んでいます。

Innodbを使用している場合は、常にPRIMARY KEYまたはUNIQUE KEYを作成する必要があります。 innodbを使用しない場合は、生成された独自のROW_IDを使用するため、害を及ぼす可能性があります。

証明はCコードに基づいているので簡単に説明してみます。

/**********************************************************************//**
Returns a new row id.
@return the new id */
UNIV_INLINE
row_id_t
dict_sys_get_new_row_id(void)
/*=========================*/
{
    row_id_t    id;

    mutex_enter(&(dict_sys->mutex));

    id = dict_sys->row_id;

    if (0 == (id % DICT_HDR_ROW_ID_WRITE_MARGIN)) {
          dict_hdr_flush_row_id();
    }

    dict_sys->row_id++;
    mutex_exit(&(dict_sys->mutex));
    return(id);
}

最初の問題

mutex_enter(&(dict_sys-> mutex));

この行により、1つのスレッドのみがdict_sys-> mutexに同時にアクセスできることが保証されます。すでに値がミューテックスになっている場合はどうなりますか?はい、スレッドは待機する必要があるため、スレッドロックのような素敵なランダム機能のようなものを取得します。または、独自のプライマリキーまたはユニークキーなしでさらにテーブルがある場合innodb 'table Locking'を使用して、Nice機能を使用することになります。MyISAMがInnoDBに置き換えられたのは、レコード/行ベースのロックと呼ばれるNice機能がオフになっているためです。

2番目の問題

(0 ==(id%DICT_HDR_ROW_ID_WRITE_MARGIN))

モジュロ(%)の計算は、毎回再計算する必要があるため、バッチ挿入の場合は遅くなります。また、DICT_HDR_ROW_ID_WRITE_MARGIN(値256)は2の累乗であるため、これを大幅に高速化できます。

(0 ==(ID&(DICT_HDR_ROW_ID_WRITE_MARGIN-1)))

Cコンパイラが最適化するように構成されていて、それが優れたオプティマイザである場合、Cオプティマイザは「重い」コードを軽量バージョンに修正します

ストーリーのモットーは常に独自の主キーを作成するか、最初からテーブルを作成するときに一意のインデックスがあることを確認してください

2
Raymond Nijland