创建索引
官方文档:
- https://dev.mysql.com/doc/refman/5.7/en/alter-table.html
 - http://dev.mysql.com/doc/refman/5.7/en/create-index.html
```sql
— ALTER TABLE
mysql> create table test_index_1(a int, b int , c int); Query OK, 0 rows affected (0.20 sec) 
mysql> show create table test_index_1\G
* 1. row *
       Table: test_index_1
Create Table: CREATE TABLE test_index_1 (
  a int(11) DEFAULT NULL,
  b int(11) DEFAULT NULL,
  c int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
mysql> insert into test_index_1 values -> (1,10,100),(2,20,200), -> (3,30,300),(4,40,400); Query OK, 4 rows affected (0.03 sec) Records: 4 Duplicates: 0 Warnings: 0
mysql> select * from test_index_1; +———+———+———+ | a | b | c | +———+———+———+ | 1 | 10 | 100 | | 2 | 20 | 200 | | 3 | 30 | 300 | | 4 | 40 | 400 | +———+———+———+ 4 rows in set (0.00 sec)
mysql> explain select from test_index_1 where a=3\G — 看执行计划,使用的是扫描整张表的方式 ** 1. row * id: 1 select_type: SIMPLE table: test_index_1 partitions: NULL type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 4 filtered: 25.00 Extra: Using where 1 row in set, 1 warning (0.00 sec)
— 给字段a 增加索引 mysql> alter table test_index_1 add index idx_a (a); — 给字段a添加索引。索引名为idx_a Query OK, 0 rows affected (0.15 sec) Records: 0 Duplicates: 0 Warnings: 0
mysql> explain select from test_index_1 where a=3\G — 看执行计划,使用的key为idx_a,走了索引 ** 1. row * id: 1 select_type: SIMPLE table: test_index_1 partitions: NULL type: ref possible_keys: idx_a key: idx_a key_len: 5 ref: const rows: 1 filtered: 100.00 Extra: NULL 1 row in set, 1 warning (0.00 sec)
— 使用create index mysql> explain select from test_index_1 where b=30\G — 同样b字段也没有索引 ** 1. row * id: 1 select_type: SIMPLE table: test_index_1 partitions: NULL type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 4 filtered: 25.00 Extra: Using where 1 row in set, 1 warning (0.00 sec)
— 给b字段增加索引 mysql> create index idx_b on test_index_1 (b); Query OK, 0 rows affected (0.14 sec) Records: 0 Duplicates: 0 Warnings: 0
mysql> explain select from test_index_1 where b=30\G — 查看执行计划,使用的key为idx_b,走了索引 ** 1. row * id: 1 select_type: SIMPLE table: test_index_1 partitions: NULL type: ref possible_keys: idx_b key: idx_b key_len: 5 ref: const rows: 1 filtered: 100.00 Extra: NULL 1 row in set, 1 warning (0.00 sec)
<a name="46MHh"></a>### 查看索引```sql---- 方式一--mysql> desc orders;+-----------------+-------------+------+-----+---------+-------+| Field | Type | Null | Key | Default | Extra |+-----------------+-------------+------+-----+---------+-------+| o_orderkey | int(11) | NO | PRI | NULL | | -- 索引| o_custkey | int(11) | YES | MUL | NULL | | -- 索引| o_orderstatus | char(1) | YES | | NULL | || o_totalprice | double | YES | | NULL | || o_orderDATE | date | YES | MUL | NULL | | -- 索引| o_orderpriority | char(15) | YES | | NULL | || o_clerk | char(15) | YES | | NULL | || o_shippriority | int(11) | YES | | NULL | || o_comment | varchar(79) | YES | | NULL | |+-----------------+-------------+------+-----+---------+-------+9 rows in set (0.00 sec)---- 方式二--mysql> show create table orders\G*************************** 1. row ***************************Table: ordersCreate Table: CREATE TABLE `orders` (`o_orderkey` int(11) NOT NULL,`o_custkey` int(11) DEFAULT NULL,`o_orderstatus` char(1) DEFAULT NULL,`o_totalprice` double DEFAULT NULL,`o_orderDATE` date DEFAULT NULL,`o_orderpriority` char(15) DEFAULT NULL,`o_clerk` char(15) DEFAULT NULL,`o_shippriority` int(11) DEFAULT NULL,`o_comment` varchar(79) DEFAULT NULL,PRIMARY KEY (`o_orderkey`), -- 索引KEY `i_o_orderdate` (`o_orderDATE`), -- 索引KEY `i_o_custkey` (`o_custkey`), -- 索引CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`o_custkey`) REFERENCES `customer` (`c_custkey`)) ENGINE=InnoDB DEFAULT CHARSET=latin11 row in set (0.00 sec)---- 方式三--mysql> show index from orders\G*************************** 1. row ***************************Table: ordersNon_unique: 0 -- 表示唯一的Key_name: PRIMARY -- key的name是primarySeq_in_index: 1Column_name: o_orderkeyCollation: ACardinality: 1306748 -- 基数,这个列上不同值的记录数Sub_part: NULLPacked: NULLNull:Index_type: BTREE -- 索引类型是BTreeComment:Index_comment:*************************** 2. row ***************************Table: ordersNon_unique: 1 -- Non_unique为True,表示不唯一Key_name: i_o_orderdateSeq_in_index: 1Column_name: o_orderDATECollation: ACardinality: 2405Sub_part: NULLPacked: NULLNull: YESIndex_type: BTREEComment:Index_comment:*************************** 3. row ***************************Table: ordersNon_unique: 1Key_name: i_o_custkeySeq_in_index: 1Column_name: o_custkeyCollation: ACardinality: 95217Sub_part: NULLPacked: NULLNull: YESIndex_type: BTREEComment:Index_comment:3 rows in set (0.00 sec)mysql> select count(*) from orders;+----------+| count(*) |+----------+| 1500000 | -- orders中有150W条记录,和Cardinality 是不一致的+----------+1 row in set (0.25 sec)
Cardinality(基数)
Cardinality 表示该索引列上有多少不同的记录,这个是一个预估的值,是采样得到的(由 InnoDB 触发,采样20个页,进行预估),该值越大越好,即当 Cardinality / RowNumber 越接近1越好,表示该列是高选择性的。
- 高选择性:身份证 、手机号码、姓名、订单号等。
 - 低选择性:性别、年龄等。
 
即该列是否适合创建索引,就看该字段是否具有高选择性。
mysql> show create table lineitem\G*************************** 1. row ***************************Table: lineitemCreate Table: CREATE TABLE `lineitem` (`l_orderkey` int(11) NOT NULL,`l_partkey` int(11) DEFAULT NULL,`l_suppkey` int(11) DEFAULT NULL,`l_linenumber` int(11) NOT NULL,`l_quantity` double DEFAULT NULL,`l_extendedprice` double DEFAULT NULL,`l_discount` double DEFAULT NULL,`l_tax` double DEFAULT NULL,`l_returnflag` char(1) DEFAULT NULL,`l_linestatus` char(1) DEFAULT NULL,`l_shipDATE` date DEFAULT NULL,`l_commitDATE` date DEFAULT NULL,`l_receiptDATE` date DEFAULT NULL,`l_shipinstruct` char(25) DEFAULT NULL,`l_shipmode` char(10) DEFAULT NULL,`l_comment` varchar(44) DEFAULT NULL,PRIMARY KEY (`l_orderkey`,`l_linenumber`), -- 两个列作为primaryKEY `i_l_shipdate` (`l_shipDATE`),KEY `i_l_suppkey_partkey` (`l_partkey`,`l_suppkey`),KEY `i_l_partkey` (`l_partkey`),KEY `i_l_suppkey` (`l_suppkey`),KEY `i_l_receiptdate` (`l_receiptDATE`),KEY `i_l_orderkey` (`l_orderkey`),KEY `i_l_orderkey_quantity` (`l_orderkey`,`l_quantity`),KEY `i_l_commitdate` (`l_commitDATE`),CONSTRAINT `lineitem_ibfk_1` FOREIGN KEY (`l_orderkey`) REFERENCES `orders` (`o_orderkey`),CONSTRAINT `lineitem_ibfk_2` FOREIGN KEY (`l_partkey`, `l_suppkey`) REFERENCES `partsupp` (`ps_partkey`, `ps_suppkey`)) ENGINE=InnoDB DEFAULT CHARSET=latin11 row in set (0.00 sec)mysql> show index from lineitem\G -- 省略其他输出,只看PRIMARY*************************** 1. row ***************************Table: lineitemNon_unique: 0Key_name: PRIMARYSeq_in_index: 1 -- 索引中的顺序,该列的顺序为1Column_name: l_orderkeyCollation: ACardinality: 1416486 -- 约140WSub_part: NULLPacked: NULLNull:Index_type: BTREEComment:Index_comment:*************************** 2. row ***************************Table: lineitemNon_unique: 0Key_name: PRIMARYSeq_in_index: 2 -- 索引中的顺序,该列的顺序为2Column_name: l_linenumberCollation: ACardinality: 5882116 -- 约580WSub_part: NULLPacked: NULLNull:Index_type: BTREEComment:Index_comment:
对应当前例子:
- 第一个索引(Seq_in_index = 1)的 Cardinality 的值表示当前列(l_orderkey)的不重复的值。
 - 第二个索引(Seq_in_index = 2)的 Cardinality 的值表示前两列(l_orderkey)和(l_linenumber)不重复的值。
```sql
— SQL-1
mysql> select * from lineitem limit 10;
+——————+—————-+—————-+———————+——————+————————-+——————+———-+———————+———————+——————+———————+———————-+—————————-+——————+————————————————————+ | l_orderkey | l_partkey | l_suppkey | l_linenumber | l_quantity | l_extendedprice | l_discount | l_tax | l_returnflag | l_linestatus | l_shipDATE | l_commitDATE | l_receiptDATE | l_shipinstruct | l_shipmode | l_comment | +——————+—————-+—————-+———————+——————+————————-+——————+———-+———————+———————+——————+———————+———————-+—————————-+——————+————————————————————+ | 1 | 155190 | 7706 | 1 | 17 | 21168.23 | 0.04 | 0.02 | N | O | 1996-03-13 | 1996-02-12 | 1996-03-22 | DELIVER IN PERSON | TRUCK | blithely regular ideas caj | | 1 | 67310 | 7311 | 2 | 36 | 45983.16 | 0.09 | 0.06 | N | O | 1996-04-12 | 1996-02-28 | 1996-04-20 | TAKE BACK RETURN | MAIL | slyly bold pinto beans detect s | | 1 | 63700 | 3701 | 3 | 8 | 13309.6 | 0.1 | 0.02 | N | O | 1996-01-29 | 1996-03-05 | 1996-01-31 | TAKE BACK RETURN | REG AIR | deposits wake furiously dogged, | | 1 | 2132 | 4633 | 4 | 28 | 28955.64 | 0.09 | 0.06 | N | O | 1996-04-21 | 1996-03-30 | 1996-05-16 | NONE | AIR | even ideas haggle. even, bold reque | | 1 | 24027 | 1534 | 5 | 24 | 22824.48 | 0.1 | 0.04 | N | O | 1996-03-30 | 1996-03-14 | 1996-04-01 | NONE | FOB | carefully final gr | | 1 | 15635 | 638 | 6 | 32 | 49620.16 | 0.07 | 0.02 | N | O | 1996-01-30 | 1996-02-07 | 1996-02-03 | DELIVER IN PERSON | MAIL | furiously regular accounts haggle bl | | 2 | 106170 | 1191 | 1 | 38 | 44694.46 | 0 | 0.05 | N | O | 1997-01-28 | 1997-01-14 | 1997-02-02 | TAKE BACK RETURN | RAIL | carefully ironic platelets against t | | 3 | 4297 | 1798 | 1 | 45 | 54058.05 | 0.06 | 0 | R | F | 1994-02-02 | 1994-01-04 | 1994-02-23 | NONE | AIR | blithely s | | 3 | 19036 | 6540 | 2 | 49 | 46796.47 | 0.1 | 0 | R | F | 1993-11-09 | 1993-12-20 | 1993-11-24 | TAKE BACK RETURN | RAIL | final, regular pinto | | 3 | 128449 | 3474 | 3 | 27 | 39890.88 | 0.06 | 0.07 | A | F | 1994-01-16 | 1993-11-22 | 1994-01-23 | DELIVER IN PERSON | SHIP | carefully silent pinto beans boost fur | +——————+—————-+—————-+———————+——————+————————-+——————+———-+———————+———————+——————+———————+———————-+—————————-+——————+————————————————————+ 10 rows in set (0.00 sec) 
—
— SQL-2
mysql> select l_orderkey, l_linenumber from lineitem limit 10; +——————+———————+ | l_orderkey | l_linenumber | +——————+———————+ | 721220 | 2 | | 842980 | 4 | | 904677 | 1 | | 990147 | 1 | | 1054181 | 1 | | 1111877 | 3 | | 1332613 | 1 | | 1552449 | 2 | | 2167527 | 3 | | 2184032 | 5 | +——————+———————+ 10 rows in set (0.00 sec)
—- SQL-1 和 SQL-2 其实都是在没有排序的情况下,取出前10条数据。但是结果不一样
—
— SQL-3
mysql> select l_orderkey, l_linenumber from lineitem order by l_orderkey limit 10; — 和上面的sql相比,多了一个order by的操作 +——————+———————+ | l_orderkey | l_linenumber | +——————+———————+ | 1 | 1 | ——- | 1 | 2 | — 看orderkey为1,对应的linenumber有6条 | 1 | 3 | — 这就是orderkey的Cardinality仅为140W | 1 | 4 | — 而(orderkey + linenumber)就有580W | 1 | 5 | | 1 | 6 | ——- | 2 | 1 | | 3 | 1 | | 3 | 2 | | 3 | 3 | +——————+———————+ 10 rows in set (0.01 sec)
—- SQL-3 和 SQL-2 不同的原因是 他们走了不同的索引 mysql> explain select l_orderkey, l_linenumber from lineitem limit 10\G * 1. row * id: 1 select_type: SIMPLE table: lineitem partitions: NULL type: index possible_keys: NULL key: i_l_shipdate — 使用了shipdate进行了索引 key_len: 4 ref: NULL rows: 5882306 filtered: 100.00 Extra: Using index 1 row in set, 1 warning (0.00 sec)
mysql> explain select l_orderkey, l_linenumber from lineitem order by l_orderkey limit 10\G * 1. row * id: 1 select_type: SIMPLE table: lineitem partitions: NULL type: index possible_keys: NULL key: i_l_orderkey — 使用了orderkey进行了查询 key_len: 4 ref: NULL rows: 10 filtered: 100.00 Extra: Using index 1 row in set, 1 warning (0.00 sec)
mysql> explain select from lineitem limit 10\G ** 1. row * id: 1 select_type: SIMPLE table: lineitem partitions: NULL type: ALL — SQL-1进行了全表扫描 possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 5882306 filtered: 100.00 Extra: NULL 1 row in set, 1 warning (0.00 sec)
— 所以,不使用order by取出的结果,可以理解为不是根据主键排序的结果。
在 MySQL5.5 之前,执行 show create table 操作会触发采样,而 5.5 之后将该参数(`innodb_stats_on_metadata = off`) off 后,需要主动执行 analyze table 才会去采样,采样不会锁表或者锁记录。<a name="K9PoR"></a>### 复合索引```sqlmysql> show create table lineitem\G*************************** 1. row ***************************Table: lineitemCreate Table: CREATE TABLE `lineitem` (`l_orderkey` int(11) NOT NULL,`l_partkey` int(11) DEFAULT NULL,`l_suppkey` int(11) DEFAULT NULL,`l_linenumber` int(11) NOT NULL,`l_quantity` double DEFAULT NULL,`l_extendedprice` double DEFAULT NULL,`l_discount` double DEFAULT NULL,`l_tax` double DEFAULT NULL,`l_returnflag` char(1) DEFAULT NULL,`l_linestatus` char(1) DEFAULT NULL,`l_shipDATE` date DEFAULT NULL,`l_commitDATE` date DEFAULT NULL,`l_receiptDATE` date DEFAULT NULL,`l_shipinstruct` char(25) DEFAULT NULL,`l_shipmode` char(10) DEFAULT NULL,`l_comment` varchar(44) DEFAULT NULL,PRIMARY KEY (`l_orderkey`,`l_linenumber`), -- 两个列作为primary,这个就是复合索引KEY `i_l_shipdate` (`l_shipDATE`),KEY `i_l_suppkey_partkey` (`l_partkey`,`l_suppkey`),KEY `i_l_partkey` (`l_partkey`),KEY `i_l_suppkey` (`l_suppkey`),KEY `i_l_receiptdate` (`l_receiptDATE`),KEY `i_l_orderkey` (`l_orderkey`),KEY `i_l_orderkey_quantity` (`l_orderkey`,`l_quantity`),KEY `i_l_commitdate` (`l_commitDATE`),CONSTRAINT `lineitem_ibfk_1` FOREIGN KEY (`l_orderkey`) REFERENCES `orders` (`o_orderkey`),CONSTRAINT `lineitem_ibfk_2` FOREIGN KEY (`l_partkey`, `l_suppkey`) REFERENCES `partsupp` (`ps_partkey`, `ps_suppkey`)) ENGINE=InnoDB DEFAULT CHARSET=latin11 row in set (0.00 sec)---- 复合索引举例--mysql> create table test_index_2(a int, b int , c int);Query OK, 0 rows affected (0.15 sec)mysql> alter table test_index_2 add index idx_mul_ab (a, b);Query OK, 0 rows affected (0.11 sec)Records: 0 Duplicates: 0 Warnings: 0mysql> insert into test_index_2 values-> (1,1,10),-> (1,2,9),-> (2,1,8),-> (2,4,15),-> (3,1,6),-> (3,2,17);Query OK, 6 rows affected (0.04 sec)Records: 6 Duplicates: 0 Warnings: 0mysql> select * from test_index_2 where a = 1;+------+------+------+| a | b | c |+------+------+------+| 1 | 1 | 10 || 1 | 2 | 9 |+------+------+------+2 rows in set (0.00 sec)mysql> explain select * from test_index_2 where a = 1\G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: test_index_2partitions: NULLtype: refpossible_keys: idx_mul_abkey: idx_mul_ab -- 走了索引key_len: 5ref: constrows: 2filtered: 100.00Extra: NULL1 row in set, 1 warning (0.00 sec)mysql> select * from test_index_2 where a = 1 and b = 2;+------+------+------+| a | b | c |+------+------+------+| 1 | 2 | 9 |+------+------+------+1 row in set (0.00 sec)mysql> explain select * from test_index_2 where a = 1 and b = 2\G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: test_index_2partitions: NULLtype: ref -- 此时也是走了索引possible_keys: idx_mul_abkey: idx_mul_abkey_len: 10ref: const,constrows: 1filtered: 100.00Extra: NULL1 row in set, 1 warning (0.00 sec)mysql> explain select * from test_index_2 where b = 2\G -- 只查询b*************************** 1. row ***************************id: 1select_type: SIMPLEtable: test_index_2partitions: NULLtype: ALL -- 没有使用索引possible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 6filtered: 16.67Extra: Using where1 row in set, 1 warning (0.00 sec)mysql> explain select * from test_index_2 where a=1 or b = 2\G -- 使用or,要求结果集是并集*************************** 1. row ***************************id: 1select_type: SIMPLEtable: test_index_2partitions: NULLtype: ALL -- 没有使用索引,因为b没有索引,所以b是走全表扫描的,既然走扫描,a的值也可以一起过滤-- 就没有必要在去查一次 a 的索引了possible_keys: idx_mul_abkey: NULLkey_len: NULLref: NULLrows: 6filtered: 30.56Extra: Using where1 row in set, 1 warning (0.00 sec)---- 特别的例子------ 还是只使用b列去做范围查询,发现是走索引了---- 注意查询的是 count(*)mysql> explain select count(*) from test_index_2 where b >1 and b < 3\G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: test_index_2partitions: NULLtype: index -- 走了索引possible_keys: NULLkey: idx_mul_abkey_len: 10ref: NULLrows: 6filtered: 16.67Extra: Using where; Using index -- 覆盖索引1 row in set, 1 warning (0.00 sec)-- 因为要求的是count(*), 要求所有的记录的和,-- 那索引a是包含了全部的记录的,即扫描(a,b)的索引也是可以得到count(*)的mysql> explain select * from test_index_2 where b >1 and b < 3\G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: test_index_2partitions: NULLtype: ALL -- 查询 * 就没法使用(a,b)索引了,需要全表扫描b的值。possible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 6filtered: 16.67Extra: Using where1 row in set, 1 warning (0.00 sec)mysql> explain select * from test_index_2 where a = 1 and c = 10\G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: test_index_2partitions: NULLtype: ref -- 也是走索引的,先用走a索引得到结果集,再用c=10去过滤possible_keys: idx_mul_abkey: idx_mul_abkey_len: 5ref: constrows: 2filtered: 16.67Extra: Using where1 row in set, 1 warning (0.00 sec)mysql> explain select * from test_index_2 where b = 2 and c = 10\G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: test_index_2partitions: NULLtype: ALL -- 而b和c是不行的,(b,c)不是有序的possible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 6filtered: 16.67Extra: Using where1 row in set, 1 warning (0.00 sec)
FORCE INDEX & IGNORE INDEX
MySQL 可以使用 FORCE INDEX 和 IGNORE INDEX 命令来强制或者禁止使用某个索引。
-- 没使用 force index 和 ignore index 命令,默认使用 primary 索引mysql> explain select * from dept_emp where emp_no='10003';+----+-------------+----------+------------+------+----------------+---------+---------+-------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+----------------+---------+---------+-------+------+----------+-------+| 1 | SIMPLE | dept_emp | NULL | ref | PRIMARY,emp_no | PRIMARY | 4 | const | 1 | 100.00 | NULL |+----+-------------+----------+------------+------+----------------+---------+---------+-------+------+----------+-------+1 row in set, 1 warning (0.00 sec)-- 强制使用 emp_no 索引mysql> explain select * from dept_emp force index(emp_no) where emp_no='10003';+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------+| 1 | SIMPLE | dept_emp | NULL | ref | emp_no | emp_no | 4 | const | 1 | 100.00 | NULL |+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------+1 row in set, 1 warning (0.00 sec)-- 禁止使用 primary 索引mysql> explain select * from dept_emp ignore index(primary) where emp_no='10003';+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------+| 1 | SIMPLE | dept_emp | NULL | ref | emp_no | emp_no | 4 | const | 1 | 100.00 | NULL |+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------+1 row in set, 1 warning (0.00 sec)
官方文档:https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
MySQL 的官方文档指出这两个命令以后会被弃用。
作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/yozvqv 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
