Contents

MySQL基础命令

1.常用查询

查看大小写是否敏感

show variables like '%lower_case_table_names%';

查看缓存相关的设置

show variables like "%query_cache%";

查看第一次执行的SQL

show profile cpu,block io for query 1 ;

查看第二次执行的SQL

show profile cpu,block io for query ;

查看支持的存储引擎

show engines;

查看当前MySQL默认的存储引擎

show variables like "%storage_engine%";

Join

2.常见的Join查询图

https://www.runoob.com/wp-content/uploads/2019/01/sql-join.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

内连接:  A  inner join  B  on    
内连接的结果集: 交集
外连接:   A    left outer join  B  on           A     right outer join   B  on 
         主表(驱动表)     从表(匹配表)
外连接确定主从表:  左外连左主右从, 右外连右主左从!
外连接的结果集:  主表取所有,从表取匹配. 主表与从表未匹配的数据通过null来补全.
 

3.2 Join示例
1)	建表语句
CREATE TABLE `t_dept` (
 `id` INT(11) NOT NULL AUTO_INCREMENT,
 `deptName` VARCHAR(30) DEFAULT NULL,
 `address` VARCHAR(40) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `t_emp` (
 `id` INT(11) NOT NULL AUTO_INCREMENT,
 `name` VARCHAR(20) DEFAULT NULL,
 `age` INT(3) DEFAULT NULL,
 `deptId` INT(11) DEFAULT NULL,
  empno int  not null,
  PRIMARY KEY (`id`),
  KEY `idx_dept_id` (`deptId`)
  #CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO t_dept(deptName,address) VALUES('华山','华山');
INSERT INTO t_dept(deptName,address) VALUES('丐帮','洛阳');
INSERT INTO t_dept(deptName,address) VALUES('峨眉','峨眉山');
INSERT INTO t_dept(deptName,address) VALUES('武当','武当山');
INSERT INTO t_dept(deptName,address) VALUES('明教','光明顶');
INSERT INTO t_dept(deptName,address) VALUES('少林','少林寺');
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('风清扬',90,1,100001);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('岳不群',50,1,100002);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('令狐冲',24,1,100003);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('洪七公',70,2,100004);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('乔峰',35,2,100005);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('灭绝师太',70,3,100006);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('周芷若',20,3,100007);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张三丰',100,4,100008);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张无忌',25,5,100009);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('韦小宝',18,null,100010);

2)	所有有门派人员的信息(要求显示门派名称)
SELECT e.`name`,d.`deptName` FROM t_emp e INNER JOIN t_dept d ON e.`deptId`=d.`id`;
3)	列出所有人员及其门派信息
SELECT e.`name`,d.`deptName` FROM t_emp e LEFT JOIN t_dept d ON e.`deptId`=d.`id`;
4)	列出所有门派
SELECT * FROM t_dept;
5)	所有无门派人士
SELECT * FROM t_emp WHERE deptId IS NULL;
6)	所有无人门派
SELECT d.* FROM  t_dept d LEFT JOIN t_emp e ON d.`id`=e.`deptId` WHERE e.`deptId` IS NULL;
7)	所有人员和门派的对应关系
SELECT * FROM t_emp e LEFT JOIN t_dept d ON e.`deptId`=d.`id`
UNION
SELECT * FROM t_emp e RIGHT JOIN t_dept d ON e.`deptId`=d.`id`;
8)	所有没有入门派的人员和没人入的门派
SELECT * FROM t_emp e  LEFT JOIN t_dept d ON e.`deptId`=d.`id` WHERE e.deptId IS NULL
UNION
SELECT * FROM  t_dept d LEFT JOIN t_emp e ON d.`id`=e.`deptId` WHERE e.`deptId` IS NULL;
9)	添加CEO字段
ALTER TABLE `t_dept` 
add  CEO  INT(11)  ;
update t_dept set CEO=2 where id=1;
update t_dept set CEO=4 where id=2;
update t_dept set CEO=6 where id=3;
update t_dept set CEO=8 where id=4;
update t_dept set CEO=9 where id=5;
10)	求各个门派对应的掌门人名称
SELECT d.deptName,e.name FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id
11)	求所有当上掌门人的平均年龄
SELECT AVG(e.age) FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id
12)	求所有人物对应的掌门名称
SELECT ed.name '人物',c.name '掌门' 
FROM (SELECT e.name,d.ceo from t_emp e LEFT JOIN t_dept d on e.deptid=d.id) ed
LEFT JOIN t_emp c on ed.ceo= c.id;

SELECT e.name '人物',tmp.name '掌门'
FROM t_emp e LEFT JOIN (SELECT d.id did,e.name FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id)tmp 
ON e.deptId=tmp.did;

SELECT e1.name '人物',e2.name '掌门' 
FROM t_emp e1 
LEFT JOIN t_dept d on e1.deptid = d.id
LEFT JOIN t_emp e2 on d.ceo = e2.id ;

SELECT e2.name '人物',
(SELECT e1.name FROM t_emp e1 where e1.id= d.ceo) '掌门'
from t_emp e2 LEFT JOIN t_dept d on e2.deptid=d.id;

3.索引

索引分类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
单值索引 INDEX
概念:即一个索引只包含单个列,一个表可以有多个单列索引
1)	随表一起创建:
CREATE TABLE customer (
  id INT(10) AUTO_INCREMENT ,
  customer_no VARCHAR(200),
  customer_name VARCHAR(200),
  PRIMARY KEY(id),
  KEY (customer_name)
);
2)	单独建单值索引:
CREATE  INDEX idx_customer_name ON customer(customer_name);
3)	查看某个表的索引
show index from 表名   
show keys from  表名
4.3.2 唯一索引
概念:索引列的值必须唯一,但允许有空值
1)	随表一起创建:
CREATE TABLE customer (
  id INT(10)  AUTO_INCREMENT ,
  customer_no
  VARCHAR(200),
  customer_name VARCHAR(200),
  PRIMARY KEY(id),
  KEY (customer_name),
  UNIQUE (customer_no)
);
2)	单独建唯一索引:
CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no);

4.3.3 主键索引
概念:设定为主键后数据库会自动建立索引,innodb为聚簇索引
1)	随表一起建索引
CREATE TABLE customer (
 id INT(10) AUTO_INCREMENT ,
 customer_no
 VARCHAR(200),
 customer_name VARCHAR(200),
 PRIMARY KEY(id) 
);
2)	单独建主键索引:
ALTER TABLE customer add PRIMARY KEY customer(customer_no);
3)	删除建主键索引:
ALTER TABLE customer drop PRIMARY KEY ;
4)	需要注意的问题:
设置为自增的主键上的索引不能删除.
4.3.4 复合索引
概念:即一个索引包含多个列
1)	随表一起建索引:
CREATE TABLE customer (
  id INT(10)  AUTO_INCREMENT ,
  customer_no VARCHAR(200),
  customer_name VARCHAR(200),
  PRIMARY KEY(id),
  KEY (customer_name),
  UNIQUE (customer_name),
  KEY (customer_no,customer_name)
);
2)	单独建索引:
CREATE  INDEX idx_no_name ON customer(customer_no,customer_name);

索引基本语法

创建 CREATE [UNIQUE ] INDEX [indexName] ON table_name(column))
删除 DROP INDEX [indexName] ON mytable;
查看 SHOW INDEX FROM table_name\G
使用Alter命令 ALTER TABLE tbl_name ADD PRIMARY KEY (column_list) : 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出现多次。
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT ,用于全文索引。

适合创建索引的情况

  1. 主键自动建立唯一索引

  2. 频繁作为查询条件的字段应该创建索引

  3. 查询中与其它表关联的字段,外键关系建立索引

  4. 单键/组合索引的选择问题, 组合索引性价比更高

  5. 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度

  6. 查询中统计或者分组字段

总结: 查询中过滤、统计、分组、排序、关联所能用到的字段应该建立索引,

​ 建索引优先考虑复合索引,其次考虑单值索引.

不适合创建索引的情况

  1. 表记录太少

  2. 经常增删改的表或者字段

  3. Where条件里用不到的字段不创建索引

  4. 过滤性不好的不适合建索引

3.Explain性能分析

使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。

Explain+SQL语句

Explain字段解释

id: select 查询的序列号`,表示查询中执行select子句或操作表的顺序。

select_type: 主要用于区别普通查询、联合查询、子查询等的复杂查询。

table: 这个数据是基于哪张表的。

partitions: 查询数据匹配的分区

type: 是查询的访问类型,是较为重要的一个指标

possible_keys: 显示可能应用在这张表中的索引,一个或多个。

key: 实际使用的索引。如果为NULL,则没有使用索引。

key_len: 表示索引中使用的字节数

ref: 显示索引的哪一列被使用了

rows: 显示MySQL认为它执行查询时必须检查的行数,不精确。

filtered: 返回结果的行占需要读到的行(rows列的值)的百分比

Extra: 其他的额外重要的信息。

索引引用情况

Where****语句 索引是否被使用
where a = 3 Y,使用到a
where a = 3 and b = 5 Y,使用到a,b
where a = 3 and b = 5 and c = 4 Y,使用到a,b,c
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 N
where a = 3 and c = 5 使用到a, 但是c不可以,b中间断了
where a = 3 and b > 4 and c = 5 使用到a和b, c不能用在范围之后,b断了
where a is null and b is not null is null 支持索引 但是is not null 不支持,所以 a 可以使用索引,但是 b不可以使用
where a <> 3 不能使用索引
where abs(a) =3 不能使用 索引
where a = 3 and b like ‘kk%’ and c = 4 Y,使用到a,b,c
where a = 3 and b like ‘%kk’ and c = 4 Y,只用到a
where a = 3 and b like ‘%kk%’ and c = 4 Y,只用到a
where a = 3 and b like ‘k%kk%’ and c = 4 Y,使用到a,b,c

口诀

全值匹配我最爱,最左前缀要遵守;

带头大哥不能死,中间兄弟不能断;

索引列上少计算,范围之后全失效;

LIKE百分写最右,覆盖索引不写*;

不等空值还有OR,索引影响要注意;

4.查询优化

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
查询优化
8.1 关联查询优化
1)	建表语句
CREATE TABLE IF NOT EXISTS `class` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
 PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
 PRIMARY KEY (`bookid`)
);
 
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
 
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

8.1.1 left join优化
1)	查看Explian
mysql> explain select * from class left join book on class.card = book.card ;
 
2)	book表上建立索引
mysql> ALTER TABLE `book` ADD INDEX  idx_card(`card`);
 
class表上建立索引
mysql> drop index idx_card on book;
mysql> alter table class add index idx_card(cad);
 结论:在优化关联查询时,只有在匹配表上建立索引才有效。
      left join时,左侧的为驱动表,右侧为匹配表!
8.1.2 inner join优化
1)	查看Explian
mysql> explain select * from book inner join class on class.card = book.card;
 
2)	book表上建索引
mysql>ALTER TABLE `book` ADD INDEX  idx_card(`card`);
mysql> explain select * from book inner join class on class.card = book.card;
 
   调换两个表的顺序
mysql> explain select * from class inner join book on class.card = book.card;
   
3)	book表中,删除9条记录
mysql> delete from book where bookid <10 ;
mysql> select count(*) from book ;
mysql> select count(*) from class ;
 

mysql> CALL proc_drop_index("mydb","book");
mysql> explain select * from class inner join book on class.card = book.card;
 
结论:inner join 时,mysql会自己帮你把小结果集的表选为驱动表。
4)	straight_join: 效果和inner join一样,但是会强制将左侧作为驱动表!
mysql> explain select * from class straight_join book  on class.card= book.card;
 
8.1.3 四个关联查询案例分析
1)	案例一
EXPLAIN SELECT ed.name '人物',c.name '掌门' FROM 
(SELECT e.name,d.ceo from emp e LEFT JOIN dept d on e.deptid=d.id) ed
 LEFT JOIN emp c on ed.ceo= c.id;
 
MySQL5.5结果:
 
2)	案例二
EXPLAIN SELECT e.name '人物',tmp.name '掌门'
FROM emp e LEFT JOIN (SELECT d.id did,e.name FROM dept d LEFT JOIN emp e ON d.ceo=e.id)tmp 
ON e.deptId=tmp.did;
 
MySQL5.5结果: 
 
3)	案例三
EXPLAIN SELECT e1.name '人物',e2.name '掌门' 
 FROM emp e1 
 LEFT JOIN dept d on e1.deptid = d.id
 LEFT JOIN emp e2 on d.ceo = e2.id ;
 
MySQL5.5结果:  
 
4)	案例四
Explain SELECT e2.name '人物',
(SELECT e1.name FROM emp e1 where e1.id= d.ceo) '掌门'
 from emp e2 LEFT JOIN dept d on e2.deptid=d.id;
 
MySQL5.5结果: 
  
8.1.4 建议
1)	保证被驱动表的join字段已经被索引
2)	Left Join 或者 right join时,选择小表作为驱动表,大表作为被驱动表
3)	inner join 时,mysql会自己把小表选为驱动表
4)	子查询尽量不要放在被驱动表,有可能使用不到索引
5)	能直接多表关联就尽量直接关联,不用子查询

8.2 子查询优化
1)	取所有不为掌门人的员工,按年龄分组!
mysql> CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME);
mysql> explain select age as '年龄', count(*) as '人数' from emp where id  not in 
(select ceo from dept where ceo is not null) group by age;
 
2)	解决dept表的全表扫描,建立ceo字段的索引
 
3)	进一步优化,替换not in
 mysql>explain select age as '年龄',count(*) as '人数' from emp e left join dept d on e.id=d.ceo
 where d.id is null group by age;
 结论: 在范围判断时,尽量不要使用not innot exists,使用 left join on xxx is null代替。
8.3 排序分组优化
where 条件和 on的判断这些过滤条件,作为优先优化的部分,是要被先考虑的!其次,如果有分组和排序,那么也要考虑group by order by
8.3.1 无过滤,不索引
1)	创建索引,查看Explian
create index idx_age_deptid_name on emp (age,deptid,name);
explain select * from emp where age=40 order by deptid;
explain  select * from emp order by age,deptid;
explain  select * from emp order by age,deptid limit 10;
 结论:using filesort说明进行了手工排序。原因在于没有where作为过滤条件。wherelimt都相当于一种过滤条件,所以才能使用上索引。
8.3.2 顺序错,必排序
2)	查看Explian
explain  select * from emp where age=45 order by deptid,name;
explain select * from emp where age=45 order by deptid,empno;
explain  select * from emp where age=45 order by  name,deptid;
explain select * from emp where deptid=45 order by age;
 
结论:正常使用索引
 结论:empno字段并没有建立索引,因此也无法用到索引,此字段需要排序!
 结论:where 两侧列的顺序可以变换,但是order by列的顺序不能随便变换!
   结论:deptid作为过滤条件的字段,无法使用索引,因此排序没法用上索引
8.3.3. 方向反,必排序
1)	查看Explian
explain select * from emp where age=45 order by  deptid desc, name desc ;
 结论:如果可以用上索引的字段都使用正序或者逆序,实际上是没有任何影响的,无非将结果集调换顺序。
explain select * from emp where age=45 order by  deptid asc, name desc ;
 结论:如果排序的字段,顺序有差异,就需要将差异的部分,进行一次倒置顺序,因此还是需要手动排序的!
8.3.4. 索引的选择
1)	清除emp上面的所有索引,只保留主键索引!
CALL proc_drop_index("mydb","emp");
2)	查询年龄为30岁的,且员工编号小于101000的用户,按用户名称排序
explain SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;
 
3)	优化:创建一个此三个字段的复合索引
create index idx_age_empno_name on emp(age,empno,name);
 再次查询,发现using filesort依然存在。empno是范围查询,因此导致了索引失效,所以name字段无法使用索引排序。所以,三个字段的符合索引,没有意义,因为empnoname字段只能选择其一!
4)	解决:要么选择empno,要么选择name
CALL proc_drop_index("mydb","emp");
create index idx_age_name on emp(age,name);
create index idx_age_empno on emp(age,empno);
explain SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;

 原因:所有的排序都是在条件过滤之后才执行的,所以如果条件过滤了大部分数据的话,几百几千条数据进行排序其实并不是很消耗性能,即使索引优化了排序但实际提升性能很有限。  相对的 empno<101000 这个条件如果没有用到索引的话,要对几万条的数据进行扫描,这是非常消耗性能的,使用empno字段的范围查询,过滤性更好(empno100000开始)!结论: 当范围条件和group by 或者 order by  的字段出现二选一时 ,优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之,亦然。
8.3.5 group by
group by 使用索引的原则几乎跟order by一致 ,唯一区别是groupby 即使没有过滤条件用到索引,也可以直接使用索引。
 
 
8.3.6 使用覆盖索引
简单说就是,select  from 之间查询的列 <=使用的索引列+主键
SQL只需要通过索引就可以返回查询所需要的数据,而不必通过二级索引查到主键之后再去查询数据。