一、最左首碼法則 如果索引了多列(聯合索引),要遵守最左首碼法則。最左首碼法則指的是查詢從索引的最左列開始,並且不跳過索引中的列,如果跳躍某一列,索引將部分失效(後面的欄位索引失效) 示例1:account_transaction表中創建一個聯合索引,使用method欄位+trader_staff_ ...
一、最左首碼法則
如果索引了多列(聯合索引),要遵守最左首碼法則。最左首碼法則指的是查詢從索引的最左列開始,並且不跳過索引中的列,如果跳躍某一列,索引將部分失效(後面的欄位索引失效)
示例1:account_transaction表中創建一個聯合索引,使用method欄位+trader_staff_id欄位+operator_staff_id欄位三個欄位當做聯合索引
mysql> create index mto on account_transaction(method, trader_staff_id, operator_staff_id); Query OK, 0 rows affected (5.29 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> show index from account_transaction; +---------------------+------------+-------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | +---------------------+------------+-------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+ | account_transaction | 0 | PRIMARY | 1 | id | A | 2067077 | NULL | NULL | | BTREE | | | YES | NULL | | account_transaction | 1 | trade_index | 1 | trade_no | A | 2249115 | NULL | NULL | | BTREE | | | YES | NULL | | account_transaction | 1 | mto | 1 | method | A | 3 | NULL | NULL | | BTREE | | | YES | NULL | | account_transaction | 1 | mto | 2 | trader_staff_id | A | 31046 | NULL | NULL | | BTREE | | | YES | NULL | | account_transaction | 1 | mto | 3 | operator_staff_id | A | 15847 | NULL | NULL | | BTREE | | | YES | NULL | +---------------------+------------+-------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+ 5 rows in set (0.00 sec)
說明1:mto是一個聯合索引,裡面包含了三個欄位method,trader_staff_id,operator_staff_id三個欄位。
說明2:method是第1索引欄位,即也是最左索引,trader_staff_id 是第2索引, operator_staff_id 是第3索引,這個順序很重要!
案例1:同時按順序使用三個欄位查詢一條數據
mysql> select * from account_transaction where method="CASH" and trader_staff_id=275 and operator_staff_id=12; +--------+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+ | id | trade_no | type | method | time | payment | out_trade_no | amount | balance | trader_staff_id | operator_staff_id | device_id | remark | +--------+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+ | 24 | 156384428075000275 | TOP_UP | CASH | 2019-07-23 01:11:20.221977 | LOCAL_ACCOUNT | | 10000 | 10000 | 275 | 12 | 6 | | | 747793 | 157370375171000275 | TOP_UP | CASH | 2019-11-14 03:55:51.480417 | LOCAL_ACCOUNT | | 10000 | 11000 | 275 | 12 | 6 | | +--------+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+ 2 rows in set (0.00 sec) mysql> explain select * from account_transaction where method="CASH" and trader_staff_id=275 and operator_staff_id=12; +----+-------------+---------------------+------------+------+---------------+------+---------+-------------------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------------------+------------+------+---------------+------+---------+-------------------+------+----------+-------+ | 1 | SIMPLE | account_transaction | NULL | ref | mto | mto | 70 | const,const,const | 2 | 100.00 | NULL | +----+-------------+---------------------+------------+------+---------------+------+---------+-------------------+------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
說明1:使用method,trader_staff_id,operator_staff_id三個欄位作為查詢條件,查詢時間0.00秒以內
說明2:使用explain關鍵字查詢執行計劃,該查詢使用的key是mto 即剛創建的聯合索引,key_len是70長度。記住這個長度,我們在後面還會用到。
案例2:使用 method 和 trader_staff_id 兩個欄位作為查詢條件
mysql> select * from account_transaction where method="CASH" and trader_staff_id=275;
+--------+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
| id | trade_no | type | method | time | payment | out_trade_no | amount | balance | trader_staff_id | operator_staff_id | device_id | remark |
+--------+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
| 24 | 156384428075000275 | TOP_UP | CASH | 2019-07-23 01:11:20.221977 | LOCAL_ACCOUNT | | 10000 | 10000 | 275 | 12 | 6 | |
| 747793 | 157370375171000275 | TOP_UP | CASH | 2019-11-14 03:55:51.480417 | LOCAL_ACCOUNT | | 10000 | 11000 | 275 | 12 | 6 | |
+--------+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
2 rows in set (0.00 sec)
mysql> explain select * from account_transaction where method="CASH" and trader_staff_id=275;
+----+-------------+---------------------+------------+------+---------------+------+---------+-------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------------+------------+------+---------------+------+---------+-------------+------+----------+-------+
| 1 | SIMPLE | account_transaction | NULL | ref | mto | mto | 66 | const,const | 2 | 100.00 | NULL |
+----+-------------+---------------------+------------+------+---------------+------+---------+-------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
說明1:通過explain執行計劃,可以查看使用的key仍然是mto,但是key_len只有66,比上一條的key_len少了4位。說明operator_staff_id的索引失效,並且operator_staff_id的長度為4
案例3:使用method+operator_staff_id查詢
mysql> explain select * from account_transaction where method="CASH" and operator_staff_id=12;
+----+-------------+---------------------+------------+------+---------------+------+---------+-------+-------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------------+------------+------+---------------+------+---------+-------+-------+----------+-----------------------+
| 1 | SIMPLE | account_transaction | NULL | ref | mto | mto | 62 | const | 39916 | 10.00 | Using index condition |
+----+-------------+---------------------+------------+------+---------------+------+---------+-------+-------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
說明1:使用了method+operator_staff_id作為查詢條件,跳過了trader_staff_id欄位,但是最左首碼method有使用,所以依然觸發了mto索引。
說明2:key_len=62說明索引欄位又變短了,那是因為從跳過的trader_staff_id欄位,所以trader_staff_id及之後的索引欄位就失效,案例2中的key_len是66,而現在又變成了62,說明trader_staff_id的索引長度也為4
案例4:使用trader_staff_id + operator_staff_id查詢
mysql> select * from account_transaction where trader_staff_id=275 and operator_staff_id=12; +---------+--------------------+---------------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------------------------+ | id | trade_no | type | method | time | payment | out_trade_no | amount | balance | trader_staff_id | operator_staff_id | device_id | remark | +---------+--------------------+---------------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------------------------+ | 24 | 156384428075000275 | TOP_UP | CASH | 2019-07-23 01:11:20.221977 | LOCAL_ACCOUNT | | 10000 | 10000 | 275 | 12 | 6 | | | 747793 | 157370375171000275 | TOP_UP | CASH | 2019-11-14 03:55:51.480417 | LOCAL_ACCOUNT | | 10000 | 11000 | 275 | 12 | 6 | | | 1993075 | 160454902688000275 | REFUND | WEB | 2020-11-05 04:03:46.980204 | LOCAL_ACCOUNT | | -3200 | 0 | 275 | 12 | 43 | | | 3764809 | 162122330931000275 | TOP_UP | CHEQUE | 2021-05-17 03:48:29.748154 | LOCAL_ACCOUNT | | 10000 | 10000 | 275 | 12 | 6 | | | 4791205 | 162856536047000275 | CONSUME_LUNCH | WEB | 2021-08-04 04:46:17.000000 | LOCAL_ACCOUNT | | 200 | 9400 | 275 | 12 | 35 | 管理後臺補充消費 | | 4791211 | 162856542884000275 | CONSUME_LUNCH | WEB | 2021-08-05 04:46:17.000000 | LOCAL_ACCOUNT | | 200 | 9200 | 275 | 12 | 35 | 管理後臺補充消費 | | 4791217 | 162856543723000275 | CONSUME_LUNCH | WEB | 2021-08-06 04:46:17.000000 | LOCAL_ACCOUNT | | 200 | 9000 | 275 | 12 | 35 | 管理後臺補充消費 | +---------+--------------------+---------------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------------------------+ 11 rows in set (4.58 sec) mysql> explain select * from account_transaction where trader_staff_id=275 and operator_staff_id=12; +----+-------------+---------------------+------------+------+---------------+------+---------+------+---------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------------------+------------+------+---------------+------+---------+------+---------+----------+-------------+ | 1 | SIMPLE | account_transaction | NULL | ALL | NULL | NULL | NULL | NULL | 2249115 | 1.00 | Using where | +----+-------------+---------------------+------------+------+---------------+------+---------+------+---------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
說明1:查詢時間為4.58秒,比之前用時多了很多
說明2:通過explain執行計劃,可以發現該查詢語句沒有使用索引,是因為不符合最左首碼原則,即索引的最左邊的method也就是第一索引列,這一列必須要使用,是觸發組合索引的首碼。
案例5:包含最左首碼,但是最左首碼不在最前面
mysql> select * from account_transaction where trader_staff_id=275 and operator_staff_id=12 and method="CASH";
+--------+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
| id | trade_no | type | method | time | payment | out_trade_no | amount | balance | trader_staff_id | operator_staff_id | device_id | remark |
+--------+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
| 24 | 156384428075000275 | TOP_UP | CASH | 2019-07-23 01:11:20.221977 | LOCAL_ACCOUNT | | 10000 | 10000 | 275 | 12 | 6 | |
| 747793 | 157370375171000275 | TOP_UP | CASH | 2019-11-14 03:55:51.480417 | LOCAL_ACCOUNT | | 10000 | 11000 | 275 | 12 | 6 | |
+--------+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
2 rows in set (0.00 sec)
mysql> explain select * from account_transaction where trader_staff_id=275 and operator_staff_id=12 and method="CASH";
+----+-------------+---------------------+------------+------+---------------+------+---------+-------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------------+------------+------+---------------+------+---------+-------------------+------+----------+-------+
| 1 | SIMPLE | account_transaction | NULL | ref | mto | mto | 70 | const,const,const | 2 | 100.00 | NULL |
+----+-------------+---------------------+------------+------+---------------+------+---------+-------------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
說明1:通過explain發現依然觸發了mto索引,雖然最左首碼沒有在最左邊,但是只要出現了就可以,複合最左首碼法則。
二、範圍查詢
聯合查詢索引中,出現範圍查詢(>,<),則在範圍查詢欄位在索引中靠後的索引欄位都會失效
案例1:查詢method="CASH" and trader_staff_id<257 and operator_staff_id=12;
mysql> show index from account_transaction;
+---------------------+------------+-------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+---------------------+------------+-------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| account_transaction | 0 | PRIMARY | 1 | id | A | 2067077 | NULL | NULL | | BTREE | | | YES | NULL |
| account_transaction | 1 | trade_index | 1 | trade_no | A | 2249115 | NULL | NULL | | BTREE | | | YES | NULL |
| account_transaction | 1 | mto | 1 | method | A | 3 | NULL | NULL | | BTREE | | | YES | NULL |
| account_transaction | 1 | mto | 2 | trader_staff_id | A | 31046 | NULL | NULL | | BTREE | | | YES | NULL |
| account_transaction | 1 | mto | 3 | operator_staff_id | A | 15847 | NULL | NULL | | BTREE | | | YES | NULL |
+---------------------+------------+-------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
說明1:mto索引欄位中method的索引順序是1,trader_staff_id的索引順序是2,operator_staff_idde的索引欄位是3
mysql> explain select * from account_transaction where trader_staff_id>275 and operator_staff_id=12 and method="CASH"; +----+-------------+---------------------+------------+-------+---------------+------+---------+------+-------+----------+----------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------------------+------------+-------+---------------+------+---------+------+-------+----------+----------------------------------+ | 1 | SIMPLE | account_transaction | NULL | range | mto | mto | 66 | NULL | 37708 | 10.00 | Using index condition; Using MRR | +----+-------------+---------------------+------------+-------+---------------+------+---------+------+-------+----------+----------------------------------+ 1 row in set, 1 warning (0.01 sec)
說明2,在搜索條件中的trader_staff_id是一個範圍查詢使用的">",因為trader_staff_id在創建索引的時候在第2順序,所以該查詢語句中,處於第三個欄位的operator_staff_id欄位就失效了,所以key_len是66
mysql> explain select * from account_transaction where trader_staff_id>=275 and operator_staff_id=12 and method="CASH";
+----+-------------+---------------------+------------+-------+---------------+------+---------+------+-------+----------+----------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------------+------------+-------+---------------+------+---------+------+-------+----------+----------------------------------+
| 1 | SIMPLE | account_transaction | NULL | range | mto | mto | 70 | NULL | 37718 | 10.00 | Using index condition; Using MRR |
+----+-------------+---------------------+------------+-------+---------------+------+---------+------+-------+----------+----------------------------------+
1 row in set, 1 warning (0.00 sec)
說明3:如果在不影響業務的時候,最好使用">="或者"<=",這樣就可以保證索引的正常使用
三、索引列運算
案例1:不要再索引列上進行運算操作,索引將失效
mysql> select * from account_transaction where trade_no = "156384395941000265";
+----+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
| id | trade_no | type | method | time | payment | out_trade_no | amount | balance | trader_staff_id | operator_staff_id | device_id | remark |
+----+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
| 16 | 156384395941000265 | TOP_UP | CASH | 2019-07-23 01:05:59.102933 | LOCAL_ACCOUNT | | 10000 | 10000 | 265 | 12 | 6 | |
+----+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
1 row in set (0.00 sec)
mysql> explain select * from account_transaction where trade_no = "156384395941000265";
+----+-------------+---------------------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | account_transaction | NULL | ref | trade_index | trade_index | 62 | const | 1 | 100.00 | NULL |
+----+-------------+---------------------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from account_transaction where substring(trade_no, 16,3) = "265";
+----+-------------+---------------------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | account_transaction | NULL | ALL | NULL | NULL | NULL | NULL | 2249115 | 100.00 | Using where |
+----+-------------+---------------------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
說明1:通過 trade_no 直接查詢的時候,會觸發trade_index索引
說明2:先對 trade_no 欄位做字元串截取,在查詢的時候,則沒有觸發trader_index索引
四、字元串查詢不加引號,索引失效
案例1:
mysql> select * from account_transaction where trade_no = "156384395941000265";
+----+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
| id | trade_no | type | method | time | payment | out_trade_no | amount | balance | trader_staff_id | operator_staff_id | device_id | remark |
+----+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
| 16 | 156384395941000265 | TOP_UP | CASH | 2019-07-23 01:05:59.102933 | LOCAL_ACCOUNT | | 10000 | 10000 | 265 | 12 | 6 | |
+----+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
1 row in set (0.00 sec)
mysql> explain select * from account_transaction where trade_no = "156384395941000265";
+----+-------------+---------------------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | account_transaction | NULL | ref | trade_index | trade_index | 62 | const | 1 | 100.00 | NULL |
+----+-------------+---------------------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> select * from account_transaction where trade_no = 156384395941000265;
+----+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
| id | trade_no | type | method | time | payment | out_trade_no | amount | balance | trader_staff_id | operator_staff_id | device_id | remark |
+----+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
| 16 | 156384395941000265 | TOP_UP | CASH | 2019-07-23 01:05:59.102933 | LOCAL_ACCOUNT | | 10000 | 10000 | 265 | 12 | 6 | |
+----+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
1 row in set (3.52 sec)
mysql> explain select * from account_transaction where trade_no = 156384395941000265;
+----+-------------+---------------------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | account_transaction | NULL | ALL | trade_index | NULL | NULL | NULL | 2249115 | 10.00 | Using where |
+----+-------------+---------------------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 3 warnings (0.01 sec)
說明1:第一個查詢使用了0.00秒以內,並且觸發了trade_index索引。
說明2:第二個查詢使用了3.52秒,沒有觸發索引,因為trade_no是字元串類型的,但是並沒有加“”。
五、模糊查詢
如果僅僅是尾部模糊匹配,索引不會失效,如果是頭部模糊匹配,索引會失效
mysql> select * from account_transaction where trade_no like "15638439594%";
+----+--------------------+--------+--------+----------------------------+---------------+--------------+--------+---------+-----------------+-------------------+-----------+--------+
|