目录
数据库
1.1SQL
1.1.1 介绍一下数据库分页
1.1.2 介绍一下SQL中的聚合函数
1.1.3 表跟表是怎么关联的?
1.1.4 说一说你对外连接的了解
1.1.6 SQL中怎么将行转成列?
1.1.7 谈谈你对SQL注入的理解
1.1.8 将一张表的部分数据更新到另一张表,该如何操作呢?
1.1.9 WHERE和HAVING有什么区别?
1.2 索引
1.2.1 说一说你对MySQL索引的理解
1.2.2 索引有哪几种?
1.2.3 如何创建及保存MySQL的索引?
1.2.4 MySQL怎么判断要不要加索引?
1.2.5 只要创建了索引,就一定会走索引吗?
1.2.6 如何判断数据库的索引有没有生效?
1.2.7 如何评估一个索引创建的是否合理?
1.2.8 索引是越多越好吗?
1.2.9 数据库索引失效了怎么办?
1.2.10 所有的字段都适合创建索引吗?
1.2.11 说一说索引的实现原理
1.2.12 介绍一下数据库索引的重构过程
1.2.13 MySQL的索引为什么用B+树?
1.2.15 MySQL的Hash索引和B树索引有什么区别?
1.2.16 聚簇索引和非聚簇索引有什么区别?
1.2.17 什么是联合索引?
1.2.18 select in语句中如何使用索引?
1.2.19 模糊查询语句中如何使用索引?
数据库
1.1SQL
1.1.1 介绍一下数据库分页
MySQL
的分页语法:
在
MySQL
中,
SELECT
语句默认返回所有匹配的行,它们可能是指定表中的每个行。为了返回第一行或前几行,可使用LIMIT
子句,以实现分页查询。
LIMIT
子句的语法如下:
--
在所有的查询结果中,返回前
5
行记录。
SELECT
prod_name
FROM
products
LIMIT
5
;
--
在所有的查询结果中,从第
5
行开始,返回
5
行记录。
SELECT
prod_name
FROM
products
LIMIT
5
,
5
;
总之,带一个值的
LIMIT
总是从第一行开始,给出的数为返回的行数。带两个值的
LIMIT
可以指定从行号为第一个值的位置开始。优化LIMIT分页:在偏移量非常大的时候,例如 LIMIT 10000,20
这样的查询,这时
MySQL
需要查询
10020
条记录然后只返回最后20
条,前面的
10000
条记录都将被抛弃,这样的代价是非常高的。如果所有的页面被访问的频率都相同,那么这样的查询平均需要访问半个表的数据。要优化这种查询,要么是在页面中限制分页的数量,要么是优化大偏移量的性能。优化此类分页查询的一个最简单的办法就是尽可能地使用索引覆盖扫描,而不是查询所有的列,然后根据需要做一次关联操作再返回所需的列。对于偏移量很大的时候,这样做的效率会提升非常大。考虑下面的查询:
SELECT
film_id,description
FROM
sakila
.film
ORDER BY
title
LIMIT
50
,
5
;
如果这个表非常大,那么这个查询最好改写成下面的样子:
SELECT
film
.film_id
,film
.description
FROM
sakila
.film
INNER JOIN
(
SELECT
film_id
FROM
sakila
.film
ORDER BY
title
LIMIT
50
,
5
)
AS
lim
USING
(
film_id
)
;
这里的“延迟关联”将大大提升查询效率,它让MySQL扫描尽可能少的页面,获取需要访问的记录后再根据关联列回原表查询需要的所有列。这个技术也可以用于优化关联查询中的LIMIT子句。有时候也可以将LIMIT查询转换为已知位置的查询,让MySQL通过范围扫描获得对应的结果。例如,如果在一个位置列上有索引,并且预先计算出了边界值,上面的查询就可以改写为:
SELECT
film_id,description
FROM
skila
.film
WHERE
position
BETWEEN
50
AND
54
ORDER BY
position;
对数据进行排名的问题也与此类似,但往往还会同时和
GROUP BY
混合使用,在这种情况下通常都需要预先计算并存储排名信息。
LIMIT
和
OFFSET
的问题,其实是
OFFSET
的问题,它会导致
MySQL
扫描大量不需要的行然后再抛弃掉。如果可以使用书签记录上次取数的位置,那么下次就可以直接从该书签记录的位置开始扫描,这样就可以避免使用OFFSET。例如,若需要按照租赁记录做翻页,那么可以根据最新一条租赁记录向后追溯,这种做法可行是因为租赁记录的主键是单调增长的。首先使用下面的查询获得第一组结果:
SELECT
*
FROM
sakila
.rental
ORDER BY
rental_id
DESC LIMIT
20
;
假设上面的查询返回的是主键16049到16030的租赁记录,那么下一页查询就可以从16030这个点开始:
假设上面的查询返回的是主键16049到16030的租赁记录,那么下一页查询就可以从16030这个点开始:
SELECT
*
FROM
sakila
.rental
WHERE
rental_id
<
16030
ORDER BY
rental_id
DESC LIMIT
20
;
该技术的好处是无论翻页到多么后面,其性能都会很好。
1.1.2 介绍一下SQL中的聚合函数
参考答案
常用的聚合函数有
COUNT()
、
AVG()
、
SUM()
、
MAX()
、
MIN()
,下面以
MySQL
为例,说明这些函数的作
用。
COUNT()
函数:
COUNT()
函数统计数据表中包含的记录行的总数
,或者根据查询结果返回列中包含的数据行数,它有两
种用法:
COUNT(*)
计算表中总的行数,不管某列是否有数值或者为空值。
COUNT(
字段名
)
计算指定列下总的行数,计算时将忽略空值的行。
COUNT()
函数可以与
GROUP BY
一起使用来计算每个分组的总和。
AVG()
函数
()
:
AVG()
函数通过计算返回的行数和每一行数据的和,求得指定列数据的平均值。
AVG()
函数可以与
GROUP BY
一起使用,来计算每个分组的平均值。
SUM()
函数:
SUM()
是一个求总和的函数,返回指定列值的总和。
SUM()
可以与
GROUP BY
一起使用,来计算每个分组的总和。
MAX()
函数:
MAX()
返回指定列中的最大值。
MAX()
也可以和
GROUP BY
关键字一起使用,求每个分组中的最大值。
MAX()
函数不仅适用于查找数值类型,也可应用于字符类型。
MIN()
函数:
MIN()
返回查询列中的最小值。
MIN()
也可以和
GROUP BY
关键字一起使用,求出每个分组中的最小值。
MIN()
函数与
MAX()
函数类似,不仅适用于查找数值类型,也可应用于字符类型。
1.1.3 表跟表是怎么关联的?
参考答案
表与表之间常用的
关联方式
有两种:内连接、外连接,下面以
MySQL
为例来说明这两种连接方式。
内连接:
内连接通过
INNER JOIN
来实现,它将返回两张表中满足连接条件的数据,不满足条件的数据不会查询出来。
外连接:
外连接通过
OUTER JOIN
来实现,它会返回两张表中满足连接条件的数据,同时返回不满足连接条件的数据。外连接有两种形式:左外连接(LEFT OUTER JOIN
)、右外连接(
RIGHT OUTER JOIN
)。
左外连接:可以简称为左连接(
LEFT JOIN
),它会返回左表中的所有记录和右表中满足连接条件
的记录。右外连接:可以简称为右连接(RIGHT JOIN
),它会返回右表中的所有记录和左表中满足连接条件的记录。
除此之外,还有一种常见的连接方式:等值连接。这种连接是通过
WHERE
子句中的条件,将两张表连接在一起,它的实际效果等同于内连接。出于语义清晰的考虑,一般更建议使用内连接,而不是等值连接。
以上是从语法上来说明表与表之间关联的实现方式,而从表的关系上来说,比较常见的关联关系有:一对多关联、多对多关联、自关联。
一对多关联:这种关联形式最为常见,一般是两张表具有主从关系,并且以主表的主键关联从表的
外键来实现这种关联关系。另外,以从表的角度来看,它们是具有多对一关系的,所以不再赘述多
对一关联了。
多对多关联:这种关联关系比较复杂,如果两张表具有多对多的关系,那么它们之间需要有一张中
间表来作为衔接,以实现这种关联关系。这个中间表要设计两列,分别存储那两张表的主键。因
此,这两张表中的任何一方,都与中间表形成了一对多关系,从而在这个中间表上建立起了多对多
关系。
自关联:自关联就是一张表自己与自己相关联,为了避免表名的冲突,需要在关联时通过别名将它
们当做两张表来看待。一般在表中数据具有层级(树状)时,可以采用自关联一次性查询出多层级
的数据。
1.1.4 说一说你对外连接的了解
参考答案
外连接通过
OUTER JOIN
来实现,它会返回两张表中满足连接条件的数据,同时返回不满足连接条件的数据。常见的外连接有两种形式:左外连接(LEFT OUTER JOIN
)、右外连接(
RIGHT OUTER JOIN
)。
左外连接:可以简称为左连接(
LEFT JOIN
),它会返回左表中的所有记录和右表中满足连接条件
的记录。
右外连接:可以简称为右连接(
RIGHT JOIN
),它会返回右表中的所有记录和左表中满足连接条件的记录。
实际上,外连接还有一种形式:完全外连接(
FULL OUTER JOIN
),但
MySQL
不支持这种形式。
1.1.5 说一说数据库的左连接和右连接
参考答案
外连接通过
OUTER JOIN
来实现,它会返回两张表中满足连接条件的数据,同时返回不满足连接条件的数据。常见的外连接有两种形式:左外连接(LEFT OUTER JOIN
)、右外连接(
RIGHT OUTER JOIN
)。
左外连接:可以简称为左连接(
LEFT JOIN
),它会返回左表中的所有记录和右表中满足连接条件的记录。
右外连接:可以简称为右连接(
RIGHT JOIN
),它会返回右表中的所有记录和左表中满足连接条件的记录。
1.1.6 SQL中怎么将行转成列?
参考答案
我们以
MySQL
数据库为例,来说明行转列的实现方式。
首先,假设我们有一张分数表(
tb_score
),表中的数据如下图:
然后,我们再来看一下转换之后需要得到的结果,如下图:
可以看出,这里行转列是将原来的
subject
字段的多行内容选出来,作为结果集中的不同列,并根据userid进行分组显示对应的
score
。通常,我们有两种方式来实现这种转换。
1.
使用
CASE...WHEN...THEN
语句实现行转列,参考如下代码:
SELECT
userid,
SUM
(
CASE
`subject`
WHEN
'
语文
'
THEN
score
ELSE
0
END
)
as
'
语文
'
,
SUM
(
CASE
`subject`
WHEN
'
数学
'
THEN
score
ELSE
0
END
)
as
'
数学
'
,
SUM
(
CASE
`subject`
WHEN
'
英语
'
THEN
score
ELSE
0
END
)
as
'
英语
'
,
SUM
(
CASE
`subject`
WHEN
'
政治
'
THEN
score
ELSE
0
END
)
as
'
政治
'
FROM
tb_score
GROUP BY
userid
注意,
SUM()
是为了能够使用
GROUP BY
根据
userid
进行分组,因为每一个
userid
对应的subject="语文
"
的记录只有一条,所以
SUM()
的值就等于对应那一条记录的
score
的值。假如
userid
='001' and subject='
语文
'
的记录有两条,则此时
SUM()
的值将会是这两条记录的和,同理,使用
Max()
的值将会是这两条记录里面值最大的一个。但是正常情况下,一个
user
对应一个
subject
只有
一个分数,因此可以使用
SUM()
、
MAX()
、
MIN()
、
AVG()
等聚合函数都可以达到行转列的效果。
2. 使用 IF() 函数实现行转列,参考如下代码:
SELECT
userid,
SUM
(
IF
(
`subject`
=
'
语文
'
,score,
0
))
as
'
语文
'
,
SUM
(
IF
(
`subject`
=
'
数学
'
,score,
0
))
as
'
数学
'
,
SUM
(
IF
(
`subject`
=
'
英语
'
,score,
0
))
as
'
英语
'
,
SUM
(
IF
(
`subject`
=
'
政治
'
,score,
0
))
as
'
政治
'
FROM
tb_score
GROUP BY
userid
注意, IF(subject='语文',score,0) 作为条件,即对所有subject='语文'的记录的score字段进
行
SUM()
、
MAX()
、
MIN()
、
AVG()
操作,如果
score
没有值则默认为
0
。
1.1.7 谈谈你对SQL注入的理解
参考答案
SQL
注入的原
理是将
SQL
代码伪装到输入参数中,传递到服务器解析并执行的一种攻击手法
。也就是说,在一些对SERVER
端发起的请求参数中植入一些
SQL
代码,
SERVER
端在执行
SQL
操作时,会拼接对应参数,同时也将一些SQL
注入攻击的
“SQL”
拼接起来,导致会执行一些预期之外的操作。
举个例子: 比如我们的登录功能,其登录界面包括用户名和密码输入框以及提交按钮,登录时需要输入用户名和密码,然后提交。此时调用接口/user/login/
加上参数
username
、
password
,首先连接数据库,然后后台对请求参数中携带的用户名、密码进行参数校验,即SQL
的查询过程。假设正确的用户名和密码为
ls
和123456,输入正确的用户名和密码、提交,相当于调用了以下的
SQL
语句。
SELECT
*
FROM user WHERE
username
=
'ls'
AND password
=
'123456'
SQL
中会将
#
及
--
以后的字符串当做注释处理,如果我们使用
' or 1=1 #
作为用户名参数,那么服务端构建的SQL
语句就如下:
select
*
from user where
username
=
''
or
1
=
1
#' and password='123456'
而
#
会忽略后面的语句,而
1=1
属于常等型条件,因此这个
SQL
将查询出所有的登录用户。其实上面的SQL注入只是在参数层面做了些手脚,如果是引入了一些功能性的
SQL
那就更危险了,比如上面的登录功能,如果用户名使用这个 ' or 1=1
;
delete * from users
;
#
,那么在
";"
之后相当于是另外一条新的SQL
,这个
SQL
是删除全表,是非常危险的操作,因此
SQL
注入这种还是需要特别注意的。如何解决SQL
注入。
1.
严格的参数校验
参数校验就没得说了,在一些不该有特殊字符的参数中提前进行特殊字符校验即可。
2.
SQL
预编译
在知道了
SQL
注入的原理之后,我们同样也了解到
MySQL
有预编译的功能,指的是在服务器启动 时,MySQL Client
把
SQL
语句的模板(变量采用占位符进行占位)发送给
MySQL
服务器,
MySQL
服务器对
SQL
语句的模板进行编译,编译之后根据语句的优化分析对相应的索引进行优化,在最终
绑定参数时把相应的参数传送给
MySQL
服务器,直接进行执行,节省了
SQL
查询时间,以及MySQL服务器的资源,达到一次编译、多次执行的目的,除此之外,还可以防止
SQL
注入。具体是怎样防止SQL
注入的呢?实际上当将绑定的参数传到
MySQL
服务器,
MySQL
服务器对参数进行编译,即填充到相应的占位符的过程中,做了转义操作。我们常用的JDBC
就有预编译功能,不仅提升性能,而且防止SQL
注入。
1.1.8 将一张表的部分数据更新到另一张表,该如何操作呢?
参考答案
可以采用关联更新的方式,将一张表的部分数据,更新到另一张表内。参考如下代码:
update
b
set
b
.col
=
a
.col
from
a,b
where
a
.id
=
b
.id
;
update
b
set
col
=
a
.col
from
b
inner join
a
on
a
.id
=
b
.id
;
update
b
set
b
.col
=
a
.col
from
b
left Join
a
on
b
.id
=
a
.id
;
1.1.9 WHERE和HAVING有什么区别?
参考答案
WHERE
是一个约束声明,使用
WHERE
约束来自数据库的数据,
WHERE
是在结果返回之前起作用的,WHERE中不能使用聚合函数。HAVING是一个过滤声明,是在查询返回结果集以后对查询结果进行的过滤操作,在
HAVING
中可以使用聚合函数。另一方面,HAVING
子句中不能使用除了分组字段和聚合函数之外的其他字段。从性能的角度来说,HAVING
子句中如果使用了分组字段作为过滤条件,应该替换成
WHERE
子句。因为WHERE可以在执行分组操作和计算聚合函数之前过滤掉不需要的数据,性能会更好。
1.2 索引
1.2.1 说一说你对MySQL索引的理解
参考答案
索引是一个单独的、存储在磁盘上的数据库结构,
包含着对数据表里所有记录的引用指针
。使用索引可以
快速找出在某个或多个列中有一特定值的行
,所有
MySQL
列类型都可以被索引,对相关列使用索引是提高查询操作速度的最佳途径。索引是在存储引擎中实现的,因此,每种存储引擎的索引都不一定完全相同,并且每种存储引擎也不一定支持所有索引类型。MySQL
中索引的存储类型有两种,即
BTREE
和
HASH
,具体和表的存储引擎相关。MyISAM
和
InnoDB
存储引擎只支持
BTREE
索引;
MEMORY/HEAP
存储引擎可以支持
HASH
和
BTREE索引。
索引的优点主要有以下几条:
1.
通过
创建唯一索引,可以保证数据库表中每一行数据的唯一性。
2.
可以
大大加快数据的查询速度
,这也是创建索引的主要原因。
3.
在实现数据的参考完整性方面,可以加速表和表之间的连接。
4.
在使用分组和排序子句进行数据查询时,也可以显著减少查询中分组和排序的时间。
增加索引也有许多不利的方面,主要表现在如下几个方面:
1.
创建索引和维护索引要耗费时间
,并且随着数据量的增加所耗费的时间也会增加。
2.
索引需要占磁盘空间
,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果有大量的索引,索引文件可能比数据文件更快达到最大文件尺寸。
3.
当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护
,这样就降低了数据的维护速度。
1.2.2 索引有哪几种?
参考答案
MySQL
的索引可以分为以下几类:
1.
普通索引和唯一索引
普通索引是
MySQL
中的基本索引类型,允许在定义索引的列中插入重复值和空值。唯一索引要求索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。主键索引是一种特殊的唯一索引,不允许有空值。
2.
单列索引和组合索引
单列索引即一个索引只包含单个列,一个表可以有多个单列索引。组合索引是指在表的多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用。使用组合索引时遵循最左前缀集合。
3.
全文索引
全文索引类型为
FULLTEXT
,在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复
值和空值。全文索引可以在
CHAR
、
VARCHAR
或者
TEXT
类型的列上创建。
MySQL
中只有
MyISAM存储引擎支持全文索引。
4.
空间索引
空间索引是对空间数据类型的字段建立的索引,
MySQL
中的空间数据类型有
4
种,分别是
GEOMETRY
、
POINT
、
LINESTRING
和
POLYGON
。
MySQL
使用
SPATIAL
关键字进行扩展,使得能够用创建正规索引类似的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL
,空间索引只能在存储引擎为MyISAM
的表中创建。
1.2.3 如何创建及保存MySQL的索引?
参考答案
MySQL
支持多种方法在单个或多个列上创建索引:
在创建表的时候创建索引:使用CREATE TABLE
创建表时,除了可以定义列的数据类型,还可以定义主键约束、外键约束或者唯一性约束,而不论创建哪种约束,在定义约束的同时相当于在指定列上创建了一个索引。创建表时创建索引的基本语法如下:
CREATE TABLE table_name
[
col_name data_type
]
[
UNIQUE
|
FULLTEXT
|
SPATIAL
] [
INDEX
|
KEY
] [
index_name
] (
col_name
[
length
])
[
ASC
|
DESC
]
其中,UNIQUE、FULLTEXT和SPATIAL为可选参数,分别表示唯一索引、全文索引和空间索引;INDEX与KEY为同义词,两者作用相同,用来指定创建索引。例如,可以按照如下方式,在id字段上使用UNIQUE关键字创建唯一索引:
CREATE TABLE
t1
(
id
INT
NOT
NULL
,
name
CHAR
(
30
)
NOT
NULL
,
UNIQUE INDEX
UniqIdx
(
id
)
)
;
在已存在的表上创建索引
在已经存在的表中创建索引,可以使用
ALTER TABLE
语句或者
CREATEINDEX
语句。
ALTER TABLE
创建索引的基本语法如下:
ALTER TABLE table_name ADD
[
UNIQUE
|
FULLTEXT
|
SPATIAL
] [
INDEX
|
KEY
] [
index_name
] (
col_name
[
length
]
,...
)
[
ASC
|
DESC
]
例如,可以按照如下方式,在bookId字段上建立名称为UniqidIdx的唯一索引:
ALTER TABLE book ADD UNIQUE INDEX UniqidIdx (bookId);
CREATE INDEX创建索引的基本语法如下:
CREATE
[
UNIQUE
|
FULLTEXT
|
SPATIAL
]
INDEX
index_name
ON table_name
(
col_name
[
length
]
,...
) [
ASC
|
DESC
]
例如,可以按照如下方式,在bookId字段上建立名称为UniqidIdx的唯一索引:
CREATE UNIQUE INDEX
UniqidIdx
ON
book
(
bookId
)
;
1.2.4 MySQL怎么判断要不要加索引?
参考答案
建议按照如下的原则来创建索引:
1.
当唯一性是某种数据本身的特征时,指定唯一索引。使用唯一索引需能确保定义的列的数据完整
性,以提高查询速度。
2.
在频繁进行排序或分组(即进行
group by
或
order by
操作)的列上建立索引,如果待排序的列有多个,可以在这些列上建立组合索引。
1.2.5 只要创建了索引,就一定会走索引吗?
参考答案
不一定。
比如,在使用组合索引的时候,如果没有遵从
“
最左前缀
”
的原则进行搜索,则索引是不起作用的。
举例,假设在
id
、
name
、
age
字段上已经成功建立了一个名为
MultiIdx
的组合索引。索引行中按
id
、 name、
age
的顺序存放,索引可以搜索
id
、(
id,name
)、(
id, name, age
)字段组合。如果列不构成索引最左面的前缀,那么MySQL
不能使用局部索引,如(
age
)或者(
name,age
)组合则不能使用该索引查询。
1.2.6 如何判断数据库的索引有没有生效?
参考答案
可以使用
EXPLAIN
语句查看索引是否正在使用。
举例,假设已经创建了
book
表,并已经在其
year_publication
字段上建立了普通索引。执行如下语句:
EXPLAIN SELECT
*
FROM
book
WHERE
year_publication
=
1990
;
EXPLAIN语句将为我们输出详细的SQL执行信息,其中:
possible_keys
行给出了
MySQL
在搜索数据记录时可选用的各个索引。
key
行是
MySQL
实际选用的索引。
如果
possible_keys
行和
key
行都包含
year_publication
字段,则说明在查询时使用了该索引。
1.2.7 如何评估一个索引创建的是否合理?
建议按照如下的原则来设计索引:
1.
避免对经常更新的表进行过多的索引,并且索引中的列要尽可能少。应该经常用于查询的字段创建索引,但要避免添加不必要的字段。
2.
数据量小
的表最好不要使用索引,由于数据较少,查询花费的时间可能比遍历索引的时间还要短,索引可能不会产生优化效果。
3.
在条件表达式中经常用到的不同值较多的列上建立索引,在不同值很少的列上不要建立索引。比如在学生表的“
性别
”
字段上只有
“
男
”
与
“
女
”
两个不同值,因此就无须建立索引,如果建立索引不但不会提高查询效率,反而会严重降低数据更新速度。
4.
当唯一性是某种数据本身的特征时,指定唯一索引
。使用唯一索引需能确保定义的列的数据完整
性,以提高查询速度。
5.
在频繁进行排序或分组(即进行
group by
或
order by
操作)的列上建立索引
,如果待排序的列有多个,可以在这些列上建立组合索引。
1.2.8 索引是越多越好吗?
参考答案
索引并非越多越好,一个表中如有大量的索引,
不仅占用磁盘空间
,还会影响
INSERT
、
DELETE
、UPDATE等语句的性能,因为在表中的
数据更改时,索引也会进行调整和更新。
1.2.9 数据库索引失效了怎么办?
参考答案
可以采用以下几种方式,来避免索引失效:
1.
使用组合索引时,需要遵循
“
最左前缀
”
原则;
2.
不在索引列上做任何操作,例如计算、函数、类型转换,会导致索引失效而转向全表扫描;
3.
尽量使用覆盖索引(之访问索引列的查询),减少
select *
覆盖索引能减少回表次数;
4. MySQL
在使用不等于(
!=
或者
<>
)的时候无法使用索引会导致全表扫描;
5. LIKE
以通配符开头(
%abc
)
MySQL
索引会失效变成全表扫描的操作;
6.
字符串不加单引号会导致索引失效(可能发生了索引列的隐式转换);
7.
少用
or
,用它来连接时会索引失效。
1.2.10 所有的字段都适合创建索引吗?
参考答案
不是。
下列几种情况,是不适合创建索引的:
1.
频繁更新的字段
不适合建立索引;
2.
where
条件中用不到的字段
不适合建立索引;
3.
数据比较少
的表不需要建索引;
4.
数据重复且分布比较均匀
的的字段不适合建索引,例如性别、真假值;
5.
参与列计算的列
不适合建索引。
1.2.11 说一说索引的实现原理
参考答案
在
MySQL
中,索引是在存储引擎层实现的,不同存储引擎对索引的实现方式是不同的,下面我们探讨一下MyISAM
和
InnoDB
两个存储引擎的索引实现方式。
MyISAM
索引实现:
MyISAM
引擎使用
B+Tree
作为索引结构
,
叶节点的
data
域存放的是数据记录的地址
,
MyISAM
索引的原理图如下。这里假设表一共有三列,假设我们以Col1
为主键,则上图是一个
MyISAM
表的主索引 (Primary key
)示意。可以看出
MyISAM
的索引文件仅仅保存数据记录的地址。在
MyISAM
中,主索引 和辅助索引(Secondary key
)在结构上没有任何区别,只是主索引要求
key
是唯一的,而辅助索引的key可以重复。
如果我们在
Col2
上建立一个辅助索引,则此索引的结构如下图所示。同样也是一颗
B+Tree
,
data
域保存数据记录的地址。因此,MyISAM
中索引检索的算法为首先按照
B+Tree
搜索算法搜索索引,如果指定的Key存在,则取出其
data
域的值,然后以
data
域的值为地址,读取相应数据记录。
InnoDB
索引实现:
虽然
InnoDB
也使用
B+Tree
作为索引结构,但具体实现方式却与
MyISAM
截然不同。
第一个重大区别是
InnoDB
的
数据文件本身就是索引文件
。从上文知道,
MyISAM
索引文件和数据文件是
分离的
,
索引文件仅保存数据记录的地址
。而在
InnoDB
中,表数据文件本身就是按
B+Tree
组织的一个 索引结构,这棵树的叶节点
data
域保存了完整的数据记录
。这个索引的
key
是数据表的主键
,因此 、InnoDB表数据文件本身就是主索引。下图是InnoDB
主索引(同时也是数据文件)的示意图,可以看到叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为InnoDB
的数据文件本身要按主键聚集,所以
InnoDB
要求表必须有主键 (MyISAM
可以没有),如果没有显式指定,则
MySQL
系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL
自动为
InnoDB
表生成一个隐含字段作为主键,这个字段长度为6
个字节,类型为长整形。
第二个与
MyISAM
索引的不同是
InnoDB
的辅助索引
data
域存储相应记录主键的值而不是地址。换句话说,InnoDB
的所有辅助索引都引用主键作为
data
域。下图为定义在
Col3
上的一个辅助索引。这里以英文字符的ASCII
码作为比较准则。聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。
了解不同存储引擎的索引实现方式对于正确使用和优化索引都非常有帮助,例如知道了
InnoDB
的索引实现后,就很容易明白为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的 主索引会令辅助索引变得过大。再例如,用非单调的字段作为主键在InnoDB中不是个好主意,因为 InnoDB数据文件本身是一颗
B+Tree
,非单调的主键会造成在插入新记录时数据文件为了维持
B+Tree
的 特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。
1.2.12 介绍一下数据库索引的重构过程
参考答案
什么时候需要重建索引呢?
1.
表上频繁发生
update,delete
操作;
2.
表上发生了
alter table ..move
操作(
move
操作导致了
rowid
变化)。
怎么判断索引是否应该重建?
1.
一般看索引是否倾斜的严重,是否浪费了空间,对索引进行结构分析:
analyze index
index_name validate structure;
2. 在相同的session中查询index_stats表:
select height,DEL_LF_ROWS/LF_ROWS from index_stats;
当查询的
height>=4
(索引的深度,即从根到叶节点的高度)或
DEL_LF_ROWS/LF_ROWS>0.2
的情况下,就应该考虑重建该索引。
如何重建索引?
drop
原索引,然后再创建索引:
drop index
index_name;
create index
index_name
on table_name
(
index_column
)
;
这种方式相当耗时,一般不建议使用。
直接重建索引:
alter index
indexname
rebuild
;
alter index
indexname
rebuild online
;
此方法较快,建议使用。
rebuild
是快速重建索引的一种有效的办法,因为它是一种使用现有索引项来重建新索引的方法。如果重 建索引时有其他用户在对这个表操作,尽量使用带online
参数来最大限度的减少索引重建时将会出现的 任何加锁问题。由于新旧索引在建立时同时存在,因此,使用这种重建方法需要有额外的磁盘空间可供 临时使用,当索引建完后把老索引删除,如果没有成功,也不会影响原来的索引。利用这种办法可以用来将一个索引移到新的表空间。
rebuild
重建索引的过程:
1. Rebuild
以
index fast full scan
或
table full scan
方式(采用那种方式取决于
cost
)读取原索引中的
数据来构建一个新的索引,重建过程中有排序操作,
rebuild online
执行表扫描获取数据,重建过
程中有排序的操作;
2. Rebuild
会阻塞
DML
操作,
rebuild online
不会阻塞
DML
操作;
3. rebuild online
时系统会产生一个
SYS_JOURNAL_xxx
的
IOT
类型的系统临时日志表,所有
rebuild
online
时索引的变化都记录在这个表中,当新的索引创建完成后,把这个表的记录维护到新的索引
中去,然后
drop
掉旧的索引,
rebuild online
就完成了。重建索引过程中的注意事项:
1.
执行
rebuild
操作时,需要检查表空间是否足够;
2.
虽然说
rebuild online
操作允许
DML
操作,但还是建议在业务不繁忙时间段进行;
3. Rebuild
操作会产生大量
Redo Log
;
1.2.13 MySQL的索引为什么用B+树?
参考答案
B+
树由
B
树和索引顺序访问方法演化而来,它是为磁盘或其他直接存取辅助设备设计的一种平衡查找树,在B+
树中,所有记录节点都是按键值的大小顺序存放在同一层的叶子节点,各叶子节点通过指针进 行链接。如下图:
4.2.14
联合索引的存储结构是什么,它的有效方式是什么?
参考答案
从本质上来说,联合索引还是一棵
B+
树,不同的是联合索引的键值数量不是
1
,而是大于等于
2
,参考下 图。另外,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,所以使用联合索引时遵循最左前缀集合。
1.2.15 MySQL的Hash索引和B树索引有什么区别?
参考答案
hash
索引底层就是
hash
表,进行查找时,调用一次
hash
函数就可以获取到相应的键值,之后进行回表 查询获得实际数据。B+
树底层实现是多路平衡查找树,对于每一次的查询都是从根节点出发,查找到叶 子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。它们有以下的不同:
hash
索引进行等值查询更快
(
一般情况下
)
,但是却无法进行范围查询。因为在
hash
索引中经过
hash
函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询。而
B+
树的的所
有节点皆遵循
(
左节点小于父节点,右节点大于父节点,多叉树也类似
)
,天然支持范围。
hash
索引不支持使用索引进行排序,原理同上。
hash
索引不支持模糊查询以及多列索引的最左前缀匹配,原理也是因为
hash
函数的不可预测。
hash
索引任何时候都避免不了回表查询数据,而
B+
树在符合某些条件
(
聚簇索引,覆盖索引等
)
的时
候可以只通过索引完成查询。 hash索引虽然在等值查询上较快,但是不稳定,性能不可预测,当某个键值存在大量重复的时候, 发生hash
碰撞,此时效率可能极差。而
B+
树的查询效率比较稳定,对于所有的查询都是从根节点 到叶子节点,且树的高度较低。 因此,在大多数情况下,直接选择B+
树索引可以获得稳定且较好的查询速度。而不需要使用
hash
索引。
1.2.16 聚簇索引和非聚簇索引有什么区别?
参考答案
在
InnoDB
存储引擎中,可以将
B
+
树索引分为聚簇索引和辅助索引
(非聚簇索引)。无论是何种索引, 每个页的大小都为16KB
,且不能更改。 聚簇索引是根据主键创建的一棵
B+
树,聚簇索引的叶子节点存放了表中的所有记录。辅助索引是根据索
引键创建的一棵
B+
树,
与聚簇索引不同的是,其叶子节点仅存放索引键值,以及该索引键值指向的主 键。也就是说,如果通过辅助索引来查找数据,那么当找到辅助索引的叶子节点后,很有可能还需要根 据主键值查找聚簇索引来得到数据,这种查找方式又被称为书签查找。因为辅助索引不包含行记录的所有数据,这就意味着每页可以存放更多的键值,因此其高度一般都要小于聚簇索引。
1.2.17 什么是联合索引?
参考答案
联合索引是指对表上的多个列进行索引
,联合索引的创建方法与单个索引创建的方法一样,不同之处仅在于有多个索引列。从本质上来说,联合索引还是一棵B+
树,不同的是联合索引的键值数量不是
1
,而是大于等于2
,参考下图。另外,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,所以使用联合索引时遵循最左前缀集合。
1.2.18 select in语句中如何使用索引?
参考答案
索引是否起作用,主要取决于字段类型:
如果字段类型为字符串,需要给
in
查询中的数值与字符串值都需要添加引号,索引才能起作用。
如果字段类型为
int
,则
in
查询中的值不需要添加引号,索引也会起作用。
IN
的字段,在联合索引中,按以上方法,也会起作用。
1.2.19 模糊查询语句中如何使用索引?
参考答案
在
MySQL
中模糊查询
mobile like ‘%8765’
,这种情况是不能使用
mobile
上的索引的,那么如果需
要根据手机号码后四位进行模糊查询,可以用一下方法进行改造。 我们可以加入冗余(MySQL5.7
之后加入了虚拟列,使用虚拟列更合适,思路相同),比如 mobile_reverse,内部存储为
mobile
的倒叙文本,如
mobile
为
17312345678
,那么
mobile_reverse 存储 87654321371
,为
mobile_reverse
列建立索引,查询中使用语句
mobile_reverse like reverse(’%5678’) 即可。reverse 是
MySQL
中的反转函数,这条语句相当于
mobile_reverse like ‘8765%’
,这种语句是可以使用索引的。