【C++语言部分】面试高频问题汇总及详细解答
【操作系统(Linux)】面试高频问题汇总及详细解答
【计算机网络】面试高频问题汇总及详细解答
本文目录
- 1. SQL
- 1.1 介绍一下数据库分页
- 1.2 介绍一下SQL中的聚合函数
- 1.3 表跟表是怎么关联的
- 1.4 说一说你对外连接的了解
- 1.5 说说SQL中怎么将行转成列
- 1.6 说说你对SQL注入的理解
- 1.7 将一张表的部分数据更新到另一张表,该如何操作
- 1.8 说说 WHERE 和 HAVING 有什么区别
- 2. 索引
- 2.1 说说你对MySQL索引的理解
- 2.2 说说索引有哪几种
- 2.3 简述如何创建及保存MySQL的索引
- 2.4 说说MySQL怎么判断要不要加索引
- 2.5 只要创建了索引,就一定会走索引吗
- 2.6 如何判断数据库的索引有没有生效
- 2.7 如何评估一个索引创建的是否合理
- 2.8 说说索引是越多越好吗
- 2.9 说说数据库索引失效了怎么办
- 2.10 所有的字段都适合创建索引吗
- 2.11 说说索引的实现原理
- 2.12 简述数据库索引的重构过程
- 2.13 简述MySQL的索引为什么用B+树
- 2.14 联合索引的存储结构是什么,它的有效方式是什么
- 2.15 说说MySQL的Hash索引和B树索引有什么区别
- 2.16 说说聚簇索引和非聚簇索引有什么区别
- 2.17 说说 select in 语句中如何使用索引
- 2.18 说说模糊查询语句中如何使用索引
- 3. 事务
- 3.1 说说你对数据库事务的了解
- 3.2 事务有哪几种类型,它们之间有什么区别
- 3.3 说说MySQL的ACID特性分别是怎么实现的
- 3.4 说说MySQL的事务隔离级别
- 3.5 说说MySQL的事务隔离级别是怎么实现的
- 3.6 说说事务可以嵌套吗
- 3.7 说说如何实现可重复读
- 3.8 说说如何解决幻读问题
- 3.9 说说MySQL事务如何回滚
- 4. 锁
- 4.1 说说数据库的锁
- 4.2 说说间隙锁
- 4.3 说说InnoDB中行级锁是怎么实现的
- 4.4 说说数据库在什么情况下会发生死锁
- 4.5 说说数据库死锁的解决办法
- 5. 优化
- 5.1 说说你对数据库优化的理解
- 5.2 如何优化MySQL的查询
- 5.3 怎样插入数据才能更高效
- 5.4 说说表中包含几千万条数据该怎么办
- 5.5 说说MySQL的慢查询优化
- 5.6 说说你对explain的了解
- 5.7 说说explain关注什么
- 6. 其他
- 6.1 简述数据库设计的三大范式
- 6.2 说说你对MySQL引擎的了解
- 6.3 说说你对redo log、undo log、binlog的了解
- 6.4 说说你对MVCC的了解
- 6.5 说说MySQL主从同步是如何实现的
1. SQL
1.1 介绍一下数据库分页
在数据库中,分页是指在查询结果集中按照一定的规则对数据集进行分割,每次获取部分数据集,而不是一次性获取所有数据。常见的用途是在Web应用程序中分页展示数据,以提高用户体验和性能。
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这个点开始:
SELECT * FROM sakila.rental
WHERE rental_id < 16030 ORDER BY rental_id DESC LIMIT 20;
该技术的好处是无论翻页到多么后面,其性能都会很好。
1.2 介绍一下SQL中的聚合函数
在 SQL 中,聚合函数是一种专门用于处理列数据的函数,它们的作用是对数据进行分组并统计相关信息。常见的聚合函数包括以下几种:
- COUNT:用于统计记录的数量,可以对整张表或者某些列进行计数。例如,可以使用 COUNT(*) 计算表中的总记录数,或者使用 COUNT(column) 统计某列的非空记录数。
- SUM:用于计算数值列的总和,主要用于数值型数据的统计,例如,可以使用 SUM(column) 计算某一列数据的总和。
- AVG:用于计算数值列的平均值。例如,可以使用 AVG(column) 计算某一列数据的平均值。
- MAX:用于获取某列的最大值。例如,可以使用 MAX(column) 计算某一列数据的最大值。
- MIN:用于获取某列的最小值。例如,可以使用 MIN(column) 计算某一列数据的最小值。
聚合函数通常配合 GROUP BY 子句一起使用,将表中数据按照某些列进行分组,并针对每个分组计算聚合函数的值。
例如,假设有一个商品销售数据的表 sales,其中包括商品名称、销售日期、销售数量等字段。要求统计每个商品每天的销售总量,可以使用如下 SQL 语句:
SELECT 商品名称, 销售日期, SUM(销售数量) AS 销售总量
FROM sales
GROUP BY 商品名称, 销售日期;
该 SQL 语句使用了 GROUP BY 子句将数据按照商品名称和销售日期进行分组,并使用 SUM 聚合函数计算每个分组的销售总量。最后的结果将包括 商品名称、销售日期 和 销售总量 三个列,其中 销售总量 为每个商品每天的总销售量。
1.3 表跟表是怎么关联的
表与表之间常用的关联方式有两种:内连接、外连接,下面以MySQL为例来说明这两种连接方式。
内连接:内连接通过INNER JOIN来实现,它将返回两张表中满足连接条件的数据,不满足条件的数据不会查询出来。
外连接:外连接通过OUTER JOIN来实现,它会返回两张表中满足连接条件的数据,同时返回不满足连接条件的数据。外连接有两种形式:左外连接(LEFT OUTER JOIN)、右外连接(RIGHT OUTER JOIN)。
- 左外连接:可以简称为左连接(LEFT JOIN),它会返回左表中的所有记录和右表中满足连接条件的记录。
- 右外连接:可以简称为右连接(RIGHT JOIN),它会返回右表中的所有记录和左表中满足连接条件的记录。
除此之外,还有一种常见的连接方式:等值连接。这种连接是通过WHERE子句中的条件,将两张表连接在一起,它的实际效果等同于内连接。出于语义清晰的考虑,一般更建议使用内连接,而不是等值连接。
以上是从语法上来说明表与表之间关联的实现方式,而从表的关系上来说,比较常见的关联关系有:一对多关联、多对多关联、自关联。
- 一对多关联:这种关联形式最为常见,一般是两张表具有主从关系,并且以主表的主键关联从表的外键来实现这种关联关系。另外,以从表的角度来看,它们是具有多对一关系的,所以不再赘述多对一关联了。
- 多对多关联:这种关联关系比较复杂,如果两张表具有多对多的关系,那么它们之间需要有一张中间表来作为衔接,以实现这种关联关系。这个中间表要设计两列,分别存储那两张表的主键。因此,这两张表中的任何一方,都与中间表形成了一对多关系,从而在这个中间表上建立起了多对多关系。
- 自关联:自关联就是一张表自己与自己相关联,为了避免表名的冲突,需要在关联时通过别名将它们当做两张表来看待。一般在表中数据具有层级(树状)时,可以采用自关联一次性查询出多层级的数据。
1.4 说一说你对外连接的了解
外连接通过OUTER JOIN来实现,它会返回两张表中满足连接条件的数据,同时返回不满足连接条件的数据。常见的外连接有两种形式:左外连接(LEFT OUTER JOIN)、右外连接(RIGHT OUTER JOIN)。
- 左外连接:可以简称为左连接(LEFT JOIN),它会返回左表中的所有记录和右表中满足连接条件的记录。
- 右外连接:可以简称为右连接(RIGHT JOIN),它会返回右表中的所有记录和左表中满足连接条件的记录。
实际上,外连接还有一种形式:完全外连接(FULL OUTER JOIN),但MySQL不支持这种形式。
1.5 说说SQL中怎么将行转成列
我们以MySQL数据库为例,来说明行转列的实现方式。
首先,假设我们有一张分数表(tb_score),表中的数据如下图:
然后,我们再来看一下转换之后需要得到的结果,如下图:
可以看出,这里行转列是将原来的subject字段的多行内容选出来,作为结果集中的不同列,并根据userid进行分组显示对应的score。通常,我们有两种方式来实现这种转换。
- 使用 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()等聚合函数都可以达到行转列的效果。
- 使用 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.6 说说你对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注入:
- 严格的参数校验
在一些不该有特殊字符的参数中提前进行特殊字符校验即可。 - SQL预编译
在知道了SQL注入的原理之后,我们同样也了解到MySQL有预编译的功能,指的是在服务器启动时,MySQL Client把SQL语句的模板(变量采用占位符进行占位)发送给MySQL服务器,MySQL服务器对SQL语句的模板进行编译,编译之后根据语句的优化分析对相应的索引进行优化,在最终绑定参数时把相应的参数传送给MySQL服务器,直接进行执行,节省了SQL查询时间,以及MySQL服务器的资源,达到一次编译、多次执行的目的,除此之外,还可以防止SQL注入。
具体是怎样防止SQL注入的呢?实际上当将绑定的参数传到MySQL服务器,MySQL服务器对参数进行编译,即填充到相应的占位符的过程中,做了转义操作。我们常用的JDBC就有预编译功能,不仅提升性能,而且防止SQL注入。
1.7 将一张表的部分数据更新到另一张表,该如何操作
如果需要将一张表的部分数据更新到另一张表中,可以使用 UPDATE 和 JOIN 语句来实现。
假设有两个表,分别为 table1 和 table2。现在需要将 table1 中 ID 列为 1 的记录更新到 table2 中。可以使用如下 SQL 语句:
UPDATE table1
SET field1 = table2.field1, field2 = table2.field2
FROM table1
INNER JOIN table2 ON table1.ID = table2.ID
WHERE table1.ID = 1;
该 SQL 语句使用 INNER JOIN 筛选出需要更新的部分数据,然后将其更新到目标表 table2 中。具体步骤如下:
- 在 UPDATE 子句中指定要更新的表为 table1。
- 在 SET 子句中指定需要更新的字段,并将它们的值设置为目标表 table2 中相应字段的值。
- 使用 INNER JOIN 子句将 table1 和 table2 进行关联,条件为它们的 ID 列相等。
- 使用 WHERE 子句指定要更新的记录,此处为 table1 中 ID 为 1 的记录。
该 SQL 语句可以将 table1 中 ID 为 1 的记录的 field1 和 field2 字段更新到 table2 中对应的记录中。更新操作还可以根据具体需求进行调整和优化,例如可以将 WHERE 子句中的条件更改为多个 ID 或者使用其他类型的 JOIN 筛选数据。
1.8 说说 WHERE 和 HAVING 有什么区别
- WHERE 语句用于筛选行,仅保留满足指定条件的行,通过条件对行进行过滤。一般在 SELECT、UPDATE、DELETE 语句中使用,WHERE 后面的条件可以是包含不同逻辑操作符的表达式或者指定多个条件的布尔运算。在筛选过程中,WHERE 语句可以使用表中的任何列或表达式,因此,它通常是 SQL 查询最初执行的语句。
例如,下面是一个使用 WHERE 语句的查询示例:
SELECT *
FROM orders
WHERE order_total > 1000;
以上 SQL 查询将从 orders 表中选取 order_total 大于 1000 的记录。
- HAVING 语句用于对分组进行筛选,仅保留满足指定条件的分组,通过条件对分组进行过滤。一般在 GROUP BY 语句后使用,可以带有聚合函数,和 WHERE 类似,HAVING 后面的条件也可以是包含不同逻辑操作符的表达式或者指定多个条件的布尔运算。在分组筛选过程中,HAVING 语句可以使用 GROUP BY 语句中指定的列和聚合函数。
例如,下面是一个使用 HAVING 语句的查询示例:
SELECT region, COUNT(*)
FROM customers
GROUP BY region
HAVING COUNT(*) > 10;
以上 SQL 查询将从 customers 表中选取 region 列,按照 region 列进行分组并计算每组中的行数。最终,仅保留行数大于 10 的分组,输出每个分组的 region 和行数(COUNT(*))。
因此,WHERE 和 HAVING 语句的区别在于它们作用的范围不同。WHERE 仅能作用于筛选行,HAVING 仅能作用于分组筛选,它们分别在不同的执行阶段起作用,所以应该根据具体需求合理运用。
2. 索引
2.1 说说你对MySQL索引的理解
索引是一个单独的、存储在磁盘上的数据库结构,包含着对数据表里所有记录的引用指针。使用索引可以快速找出在某个或多个列中有一特定值的行,所有MySQL列类型都可以被索引,对相关列使用索引是提高查询操作速度的最佳途径。
索引是在存储引擎中实现的,因此,每种存储引擎的索引都不一定完全相同,并且每种存储引擎也不一定支持所有索引类型。MySQL中索引的存储类型有两种,即BTREE和HASH,具体和表的存储引擎相关。MyISAM和InnoDB存储引擎只支持BTREE索引;MEMORY/HEAP存储引擎可以支持HASH和BTREE索引。
索引的优点主要有以下几条:
- 通过创建唯一索引,可以保证数据库表中每一行数据的唯一性。
- 可以大大加快数据的查询速度,这也是创建索引的主要原因。
- 在实现数据的参考完整性方面,可以加速表和表之间的连接。
- 在使用分组和排序子句进行数据查询时,也可以显著减少查询中分组和排序的时间。
增加索引也有许多不利的方面,主要表现在如下几个方面:
- 创建索引和维护索引要耗费时间,并且随着数据量的增加所耗费的时间也会增加。
- 索引需要占磁盘空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果有大量的索引,索引文件可能比数据文件更快达到最大文件尺寸。
- 当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度。
2.2 说说索引有哪几种
MySQL的索引可以分为以下几类:
- 普通索引和唯一索引
普通索引是MySQL中的基本索引类型,允许在定义索引的列中插入重复值和空值。
唯一索引要求索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
主键索引是一种特殊的唯一索引,不允许有空值。 - 单列索引和组合索引
单列索引即一个索引只包含单个列,一个表可以有多个单列索引。
组合索引是指在表的多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用。使用组合索引时遵循最左前缀集合。 - 全文索引
全文索引类型为FULLTEXT,在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复值和空值。全文索引可以在CHAR、VARCHAR或者TEXT类型的列上创建。 - 空间索引
空间索引是对空间数据类型的字段建立的索引,MySQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING和POLYGON。MySQL使用SPATIAL关键字进行扩展,使得能够用创建正规索引类似的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MyISAM的表中创建。
2.3 简述如何创建及保存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);
2.4 说说MySQL怎么判断要不要加索引
在 MySQL 中,判断是否需要添加索引可以从以下几个方面进行考虑:
- 频繁的查询操作:如果某个列或者多个列上进行了频繁的查询操作,那么可以考虑在查询列上创建索引来提高查询效率。
- 大量的数据操作:如果表中包含大量数据,而且需要在其中的某列或多列上进行查询、排序或分组操作,这种情况下可以考虑在查询列上创建索引来加快操作速度。
- 经常被连表查询:如果某个表经常被用于与其他表进行连接查询(JOIN),那么可以考虑在连接查询上所使用的列上创建索引来提高联合查询的效率。
- 索引唯一性:如果需要保证某些列的唯一性,那么可以在这些列上创建唯一索引或主键索引。
需要注意的是,虽然索引可以提高查询、排序和分组等操作的效率,但是过多的索引有可能会对写操作(INSERT、UPDATE、DELETE)性能造成影响,因此在选择是否需要添加索引时,需要充分考虑读写操作的平衡,权衡性能提升和空间占用等因素。
2.5 只要创建了索引,就一定会走索引吗
不一定。比如,在使用组合索引的时候,如果没有遵从“最左前缀”的原则进行搜索,则索引是不起作用的。
举例,假设在id、name、age字段上已经成功建立了一个名为MultiIdx的组合索引。索引行中按id、name、age的顺序存放,索引可以搜索id、(id,name)、(id, name, age)字段组合。如果列不构成索引最左面的前缀,那么MySQL不能使用局部索引,如(age)或者(name,age)组合则不能使用该索引查询。
2.6 如何判断数据库的索引有没有生效
可以通过以下方法判断数据库的索引是否生效:
- EXPLAIN 分析查询语句
可以通过 EXPLAIN 命令来分析查询语句的执行计划,从而判断索引是否被正确使用。例如,以下是查询 customers 表中 customer_name 列的语句:
EXPLAIN SELECT * FROM customers WHERE customer_name = 'Alice';
执行上述命令后,可以得到一张表格,其中的 key 列显示了是否使用了索引。如果 key 列的值为 NULL,说明查询语句没有使用索引;如果 key 列的值为索引的名称,说明查询语句使用了该索引。
- SHOW INDEX 查看索引信息
可以通过 SHOW INDEX 命令来查看表中所有索引的信息。例如,以下是查看 customers 表中索引信息的命令:
SHOW INDEX FROM customers;
执行上述命令后,可以查看 customers 表中所有索引的名称、列、索引类型等信息。如果某个索引在使用中,其 Cardinality 值(表示该列的唯一值数量)将会比较大;如果 Cardinality 值为 1,则可能表示该索引没有被正确利用。
需要注意的是,通过以上方法可以初步判断索引是否生效,但是具体原因还需要进一步分析、排查。在实际使用中,可以通过 SQL 语句优化、索引设计和数据库性能工具等多种方式来检测和优化索引,提高查询效率。
2.7 如何评估一个索引创建的是否合理
建议按照如下的原则来设计索引:
- 避免对经常更新的表进行过多的索引,并且索引中的列要尽可能少。应该经常用于查询的字段创建索引,但要避免添加不必要的字段。
- 数据量小的表最好不要使用索引,由于数据较少,查询花费的时间可能比遍历索引的时间还要短,索引可能不会产生优化效果。
- 在条件表达式中经常用到的不同值较多的列上建立索引,在不同值很少的列上不要建立索引。比如在学生表的“性别”字段上只有“男”与“女”两个不同值,因此就无须建立索引,如果建立索引不但不会提高查询效率,反而会严重降低数据更新速度。
- 当唯一性是某种数据本身的特征时,指定唯一索引。使用唯一索引需能确保定义的列的数据完整性,以提高查询速度。
- 在频繁进行排序或分组(即进行group by或order by操作)的列上建立索引,如果待排序的列有多个,可以在这些列上建立组合索引。
2.8 说说索引是越多越好吗
索引并非越多越好,一个表中如有大量的索引,不仅占用磁盘空间,还会影响INSERT、DELETE、UPDATE等语句的性能,因为在表中的数据更改时,索引也会进行调整和更新。
2.9 说说数据库索引失效了怎么办
可以采用以下几种方式,来避免索引失效:
- 使用组合索引时,需要遵循“最左前缀”原则。
- 不在索引列上做任何操作,例如计算、函数、类型转换,会导致索引失效而转向全表扫描。
- 尽量使用覆盖索引(之访问索引列的查询),减少 select * 覆盖索引能减少回表次数。
- MySQL在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描。
- LIKE以通配符开头(%abc)MySQL索引会失效变成全表扫描的操作。
- 字符串不加单引号会导致索引失效(可能发生了索引列的隐式转换)。
- 少用or,用它来连接时会索引失效。
2.10 所有的字段都适合创建索引吗
不是。下列几种情况,是不适合创建索引的:
- 频繁更新的字段不适合建立索引;
- where条件中用不到的字段不适合建立索引;
- 数据比较少的表不需要建索引;
- 数据重复且分布比较均匀的的字段不适合建索引,例如性别、真假值;
- 参与列计算的列不适合建索引。
2.11 说说索引的实现原理
在 MySQL 中,索引的实现原理主要是使用 B-Tree(B-树)算法。B-Tree 是一种平衡的多叉树结构,它的特点是节点拥有多个子节点,能够在较快的时间内确定数据项的位置,具有较高的搜索和插入效率。
一个 B-Tree 索引由一个根节点、若干个中间节点和若干个叶子节点组成。其中,根节点和中间节点不包含数据,只包含索引值和指向子节点的指针;叶子节点包含索引值和具体数据行的指针。
当需要查询、添加、修改或删除数据时,MySQL 会首先在 B-Tree 索引的根节点上进行搜索。如果搜索的索引值在根节点中存在,则会继续在其对应的子节点中进行搜索,直到找到叶子节点,然后返回该数据行的指针;如果搜索的索引值在根节点中不存在,则终止搜索。
当需要向 B-Tree 索引中添加、修改或删除数据时,MySQL 也会先搜索到该数据行在 B-Tree 索引中的位置,然后进行相应的操作。
B-Tree 索引的实现原理使得 MySQL 能够高效地进行查找、插入、删除和修改等操作,并且能够适应不同的查询和数据变化。但是要注意,如果索引被滥用,也会增加数据的存储空间和降低数据的修改效率,因此需要在实现时进行合理的索引设计和优化。
2.12 简述数据库索引的重构过程
什么时候需要重建索引呢
- 表上频繁发生update,delete操作;
- 表上发生了alter table …move操作(move操作导致了rowid变化)。
怎么判断索引是否应该重建
- 一般看索引是否倾斜的严重,是否浪费了空间,对索引进行结构分析:
analyze index index_name validate structure;
- 在相同的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重建索引的过程:
- Rebuild以index fast full scan或table full scan方式(采用那种方式取决于cost)读取原索引中的数据来构建一个新的索引,重建过程中有排序操作,rebuild online执行表扫描获取数据,重建过程中有排序的操作;
- Rebuild会阻塞DML操作,rebuild online不会阻塞DML操作;
- rebuild online时系统会产生一个SYS_JOURNAL_xxx的IOT类型的系统临时日志表,所有rebuild online时索引的变化都记录在这个表中,当新的索引创建完成后,把这个表的记录维护到新的索引中去,然后drop掉旧的索引,rebuild online就完成了。
重建索引过程中的注意事项:
- 执行rebuild操作时,需要检查表空间是否足够;
- 虽然说rebuild online操作允许DML操作,但还是建议在业务不繁忙时间段进行;
- Rebuild操作会产生大量Redo Log。
2.13 简述MySQL的索引为什么用B+树
B+树由B树和索引顺序访问方法演化而来,它是为磁盘或其他直接存取辅助设备设计的一种平衡查找树,在B+树中,所有记录节点都是按键值的大小顺序存放在同一层的叶子节点,各叶子节点通过指针进行链接。如下图:
MySQL 的索引采用 B+ 树的主要原因是 B+ 树拥有较高的读写效率和更好的数据范围扫描性能。
B+ 树是一种平衡树结构,它具有以下特点:
- 叶子节点顺序存储:B+ 树中的所有数据都存储在叶子节点上,并按照顺序存放。这样,在进行数据范围扫描时能更快地访问到需要的数据,大大提高了效率。
- 中间节点不保存数据:B+ 树中的中间节点仅仅保存索引信息,而不保存实际数据。这样可以节省空间,并且能够容易维护平衡树的高度。
- 叶子节点使用链表连接:每个叶子节点之间通过链表进行连接,可以方便地进行遍历。
因此,B+ 树适用于需要大量范围查询和排序的场景,如 SQL 语句中的多个 ORDER BY 或 GROUP BY 子句、LIKE 关键字匹配和地理位置查找等。
相对于 B 树,B+ 树的优势在于拥有更好的读写效率和更容易维护平衡树高度。另外,由于 B+ 树中数据只存储在叶子节点上,因此可以更大限度地利用内存,提升查询效率。综合来看,B+ 树结构相对于 B 树结构更加适合作为MySQL的索引结构。
2.14 联合索引的存储结构是什么,它的有效方式是什么
联合索引是指对表上的多个列进行索引,联合索引的创建方法与单个索引创建的方法一样,不同之处仅在于有多个索引列。从本质上来说,联合索引还是一棵B+树,不同的是联合索引的键值数量不是1,而是大于等于2,参考下图。另外,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,所以使用联合索引时遵循最左前缀集合。
2.15 说说MySQL的Hash索引和B树索引有什么区别
hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据。B+树底层实现是多路平衡查找树,对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。它们有以下的不同:
- hash索引进行等值查询更快(一般情况下),但是却无法进行范围查询。因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询。而B+树的的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似),天然支持范围。
- hash索引不支持使用索引进行排序,原理同上。
- hash索引不支持模糊查询以及多列索引的最左前缀匹配,原理也是hash函数的不可预测。
- hash索引任何时候都避免不了回表查询数据,而B+树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询。
- hash索引虽然在等值查询上较快,但是不稳定,性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差。而B+树的查询效率比较稳定,对于所有的查询都是从根节点到叶子节点,且树的高度较低。
因此,在大多数情况下,直接选择B+树索引可以获得稳定且较好的查询速度。而不需要使用hash索引。
2.16 说说聚簇索引和非聚簇索引有什么区别
聚簇索引和非聚簇索引是关系型数据库中常见的两种索引类型,它们的主要区别在于索引的存储方式和访问方式。
- 存储方式
聚簇索引是将表的数据存储和索引存储结合在一起的索引方式。具体而言,聚簇索引将表的主键和数据存储在同一颗 B-Tree 中,因此称为聚簇(Clustered)索引。这意味着表的数据存储方式与聚簇索引的存储方式相关联。如果表中没有主键,则聚簇索引会隐式地将一个列或一组列作为主键。
非聚簇索引是将索引和表的数据存储分开的索引方式。它的索引和表的数据存储在不同的位置。非聚簇(Non-clustered)索引将索引叶子节点指向表的主键或唯一标识符,这样就可以通过索引快速地查找到表中的数据。
- 访问方式
由于聚簇索引将表和索引数据存储在同一颗 B-Tree 中,因此查询聚簇索引时可以直接将数据读取到内存。这样可以提高查询效率,同时也意味着聚簇索引并不是很适合频繁进行插入、更新和删除操作的表,因为这些操作会导致树的重构。
非聚簇索引由于和表的数据存储分开,因此可以适用于频繁进行插入、更新和删除操作的表。同时,由于非聚簇索引在查询时需要进行两次查找(一次查找索引,一次查找实际数据),因此相对于聚簇索引查询需要更多的 IO 操作,这可能会导致非聚簇索引的查询效率相对较低。
2.17 说说 select in 语句中如何使用索引
索引是否起作用,主要取决于字段类型:
- 如果字段类型为字符串,需要给in查询中的数值与字符串值都需要添加引号,索引才能起作用。
- 如果字段类型为int,则in查询中的值不需要添加引号,索引也会起作用。
IN的字段,在联合索引中,按以上方法,也会起作用。
2.18 说说模糊查询语句中如何使用索引
在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%’ ,这种语句是可以使用索引的。
3. 事务
3.1 说说你对数据库事务的了解
数据库事务是指一系列的数据库操作(增、删、改),这些操作要么全部执行成功,要么全部失败回滚,保证数据库的一致性和完整性。数据库事务具有以下四个特性(ACID):
- 原子性(Atomicity):事务中所有操作要么全部成功,要么全部失败回滚,保证数据库操作的原子性。
- 一致性(Consistency):在事务开始和结束时,数据库中的数据必须满足一定的约束条件。这意味着一旦事务执行失败,所有修改将会被回滚到事务开始前的状态。
- 隔离性(Isolation):在多个并发事务同时执行时,每个事务都应该被视为独立的,事务之间不应该相互干扰。这意味着同时执行的事务应该像它们是唯一执行的事务那样被处理。
- 持久性(Durability):事务成功完成后,对数据库的修改将永久保存。即使系统出现故障,也应该保证事务提交后的状态得以持久化。
为了实现事务的隔离性,数据库采用了一些隔离级别,包括 read uncommitted、read committed、repeatable read 和 serializable。不同的隔离级别提供了不同的并发控制机制,确保事务之间的隔离性。
总之,数据库事务通过保证一系列数据库操作的一致性和完整性,提供了数据库操作的可靠和安全性。
3.2 事务有哪几种类型,它们之间有什么区别
事务可以分为以下几种类型:
- 扁平事务:是事务类型中最简单的一种,而在实际生产环境中,这可能是使用最为频繁的事务。在扁平事务中,所有操作都处于同一层次,其由BEGIN WORK开始,由COMMIT WORK或ROLLBACK WORK结束。处于之间的操作是原子的,要么都执行,要么都回滚。
- 带有保存点的扁平事务:除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态,这是因为可能某些事务在执行过程中出现的错误并不会对所有的操作都无效,放弃整个事务不合乎要求,开销也太大。保存点(savepoint)用来通知系统应该记住事务当前的状态,以便以后发生错误时,事务能回到该状态。
- 链事务:可视为保存点模式的一个变种。链事务的思想是:在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。注意,提交事务操作和开始下一个事务操作将合并为一个原子操作。这意味着下一个事务将看到上一个事务的结果,就好像在一个事务中进行的。
- 嵌套事务:是一个层次结构框架。有一个顶层事务(top-level transaction)控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务(subtransaction),其控制每一个局部的变换。
- 分布式事务:通常是一个在分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。对于分布式事务,同样需要满足ACID特性,要么都发生,要么都失效。
对于MySQL的InnoDB存储引擎来说,它支持扁平事务、带有保存点的扁平事务、链事务、分布式事务。对于嵌套事务,MySQL数据库并不是原生的,因此对于有并行事务需求的用户来说MySQL就无能为力了,但是用户可以通过带有保存点的事务来模拟串行的嵌套事务。
3.3 说说MySQL的ACID特性分别是怎么实现的
MySQL 作为一种关系型数据库,支持事务及 ACID 特性,具体实现如下:
- Atomicity:MySQL 实现事务的原子性主要依靠 redo log 和 undo log 机制。当事务执行时,会生成 redo log,记录了事务进行的所有操作。如果事务在执行过程中遇到错误,可以通过 redo log 将其回滚(rollback)到事务开始前的状态。同时,MySQL 还使用 undo log 来恢复可能会被事务回滚的修改,确保回滚过程中的一致性。
- Consistency:MySQL 通过约束、触发器等机制来保证数据的一致性。在事务开始和结束时,MySQL 检查所有约束条件是否满足,如果不满足,事务不会被提交。
- Isolation:MySQL 的隔离级别实现了事务的隔离性,通过锁定机制来控制事务的并发访问。MySQL 支持四种隔离级别,分别是 read uncommitted、read committed、repeatable read 和 serializable。不同隔离级别之间会采用不同的锁机制,以保证事务的隔离性。
- Durability:MySQL 通过将事务操作记录写入磁盘来保证事务的持久性。这些操作记录包括 redo log 和 undo log,以及 MySQL 内部的存储引擎日志和二进制日志。这些日志都会被写入磁盘,保证了事务提交后的数据可以永久保存。
总之,MySQL 通过使用 redo log、undo log、约束、触发器、隔离级别、锁机制以及日志记录等机制,保证了事务的 ACID 特性,为数据库操作提供了可靠和安全的支持。
3.4 说说MySQL的事务隔离级别
MySQL 支持四种事务隔离级别,分别为 READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。不同的隔离级别会使用不同的锁机制和并发控制方式,以确保事务之间的隔离性和数据一致性。具体介绍如下:
- READ UNCOMMITTED(读未提交):在该隔离级别下,一个事务可以读取另一个事务未提交的数据。因此,该隔离级别非常容易出现脏读、不可重复读和幻读等问题,因此不建议使用该隔离级别。
- READ COMMITTED(读提交):在该隔离级别下,一个事务只能读取另一个事务已经提交的数据。这可以避免脏读问题,但是可能会出现不可重复读和幻读问题。MySQL 默认使用该隔离级别。
- REPEATABLE READ(可重复读):在该隔离级别下,事务开始后,看到的所有数据都是该事务开始时可见的,包括其他事务提交的数据。这可以避免不可重复读问题,但是可能会出现幻读问题。为了实现该隔离级别,MySQL 使用多版本并发控制(MVCC)机制来保证读取数据的一致性。
- SERIALIZABLE(串行化):在该隔离级别下,所有的事务串行执行。这可以避免所有并发问题,但是会导致并发性能大大降低。因此,该隔离级别很少使用。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | 可能 | 可能 | 可能 |
READ COMMITTED | 不可能 | 可能 | 可能 |
REPEATABLE READ | 不可能 | 不可能 | 可能 |
SERIALIZABLE | 不可能 | 不可能 | 不可能 |
3.5 说说MySQL的事务隔离级别是怎么实现的
MySQL 的事务隔离级别主要依赖于锁和 MVCC 机制来实现,具体实现如下:
- READ UNCOMMITTED:在该隔离级别下,MySQL 不会使用任何锁来控制并发读取,允许读取未提交的数据。这种机制可能会导致脏读问题。
- READ COMMITTED:在该隔离级别下,MySQL 使用共享锁(Shared Lock)和排他锁(Exclusive Lock)来控制并发读取。MySQL 在读取数据时会获得共享锁,只有在事务提交后才会释放锁。这种机制可以避免脏读问题,但是可能会导致不可重复读问题。
- REPEATABLE READ:在该隔离级别下,MySQL 使用快照和 MVCC 机制来控制事务的并发读取。当一个事务读取数据时,MySQL 会将读取的数据拷贝成一个快照,该快照会保存到事务结束,而不会受其他并发事务的影响。这种机制可以避免脏读和不可重复读问题,但是可能会导致幻读问题。
- SERIALIZABLE:在该隔离级别下,MySQL 使用表级锁来控制所有并发事务的访问顺序,确保事务串行执行。这种机制可以避免所有并发问题,但是会导致并发性能大大降低。
3.6 说说事务可以嵌套吗
可以,因为嵌套事务也是众多事务分类中的一种,它是一个层次结构框架。有一个顶层事务控制着各个层次的事务,顶层事务之下嵌套的事务被称为子事务,它控制每一个局部的变换。
需要注意的是,MySQL数据库不支持嵌套事务。
3.7 说说如何实现可重复读
MySQL的InnoDB引擎,在默认的REPEATABLE READ的隔离级别下,实现了可重复读,同时也解决了幻读问题。它使用Next-Key Lock算法实现了行锁,并且不允许读取已提交的数据,所以解决了不可重复读的问题。另外,该算法包含了间隙锁,会锁定一个范围,因此也解决了幻读的问题。
3.8 说说如何解决幻读问题
MySQL的InnoDB引擎,在默认的REPEATABLE READ的隔离级别下,实现了可重复读,同时也解决了幻读问题。它使用Next-Key Lock算法实现了行锁,并且不允许读取已提交的数据,所以解决了不可重复读的问题。另外,该算法包含了间隙锁,会锁定一个范围,因此也解决了幻读的问题。
3.9 说说MySQL事务如何回滚
在MySQL默认的配置下,事务都是自动提交和回滚的。当显示地开启一个事务时,可以使用ROLLBACK语句进行回滚。该语句有两种用法:
- ROLLBACK:要使用这个语句的最简形式,只需发出ROLLBACK。同样地,也可以写为ROLLBACK WORK,但是二者几乎是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。
- ROLLBACK TO [SAVEPOINT] identifier :这个语句与SAVEPOINT命令一起使用。可以把事务回滚到标记点,而不回滚在此标记点之前的任何工作。
4. 锁
4.1 说说数据库的锁
数据库锁是为了保证数据多用户并发操作时的安全性而引入的一种机制。数据库锁主要分为两类:行级锁和表级锁。
- 行级锁:行级锁是针对数据表中的某一行数据进行锁定,锁住某一行的其他事务不能操作这一行。行级锁可以分为共享锁和排他锁两种类型。
- 共享锁(Shared Lock):允许多个事务同时读取一行数据,但是不允许任何事务对该行数据进行修改。
- 排他锁(Exclusive Lock):只允许一个事务对一个数据行进行修改,其他事务不能读取和修改。
- 表级锁:表级锁是针对整个数据表进行锁定,锁住整个表的其他事务不能对整个表进行操作。表级锁可以分为共享锁和排他锁两种类型。
- 共享锁(Shared Lock):允许多个事务同时读取数据表,但是不允许任何事务对整个表进行修改。
- 排他锁(Exclusive Lock):只允许一个事务对整个表进行修改,其他事务不能读取和修改。
除了基础锁机制之外,数据库还提供了其他类型的锁来保证数据一致性,如:间隙锁(Gap Lock)、意向锁(Intention Lock)等。
总之,数据库锁是为了保证数据多用户并发操作时的安全性而引入的一种机制,可分为行级锁和表级锁。在使用锁机制时,需要根据具体情况选择合适的锁粒度和锁类型,以保证数据操作的正确性和高效性。
4.2 说说间隙锁
InnoDB存储引擎有3种行锁的算法,间隙锁(Gap Lock)是其中之一。间隙锁用于锁定一个范围,但不包含记录本身。它的作用是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生。
4.3 说说InnoDB中行级锁是怎么实现的
InnoDB行级锁是通过给索引上的索引项加锁来实现的。只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
当表中锁定其中的某几行时,不同的事务可以使用不同的索引锁定不同的行。另外,不论使用主键索引、唯一索引还是普通索引,InnoDB都会使用行锁来对数据加锁。
4.4 说说数据库在什么情况下会发生死锁
数据库死锁是指多个事务在同时请求锁资源时出现循环依赖,导致所有事务都被阻塞,无法继续执行的情况。数据库死锁一般发生在以下情况:
- 并发事务数量过多:当读写并发的事务数量逐渐增多时,获取锁资源的竞争也会变得更加激烈,如果没有足够的资源或者锁机制不够完善,很容易出现死锁问题。
- 锁粒度过大:如果数据库的锁粒度过大,比如在表级别进行锁定,则会在某些事务请求表的不同部分锁时发生死锁。
- 锁等待超时:当事务请求锁资源的时间超过了设定的锁等待时间,并且锁资源一直没有被释放,就会发生死锁。
- 锁竞争顺序不当:如果多个事务按照不同的竞争顺序请求锁资源,则可能会出现一种情况,即 A 事务获取了 B 事务想要的锁资源,而 B 事务又获取了 A 事务想要的锁资源,从而发生死锁。
为避免数据库死锁问题,可以采取以下措施:
- 优化数据库结构:如果数据库设计不合理,可能导致表的锁粒度过大或过小。这时需要进行优化,分表或分区等操作,使锁粒度合理。
- 降低数据库并发度:可以通过限制并发访问量、优化 SQL 语句、加强锁机制等方式来降低数据库的并发度,减少死锁的发生。
- 减少事务长度:事务的长度越长,在执行过程中锁的时间就越长,也就越容易出现死锁问题。可以通过增加业务操作数量,将一次较长的事务拆成多个短事务,从而减少锁定时间,排除死锁。
- 实时监控锁信息:数据库可以通过实时监控锁信息来发现潜在的死锁问题,并自动或手动解决锁等待超时或检测到死锁问题。
4.5 说说数据库死锁的解决办法
解决死锁问题最简单的一种方法是超时,即当两个事务互相等待时,当一个等待时间超过设置的某一阈值时,其中一个事务进行回滚,另一个等待的事务就能继续进行。
除了超时机制,当前数据库还都普遍采用wait-for graph(等待图)的方式来进行死锁检测。较之超时的解决方案,这是一种更为主动的死锁检测方式。InnoDB存储引擎也采用的这种方式。wait-for graph要求数据库保存以下两种信息:
- 锁的信息链表;
- 事务等待链表;
通过上述链表可以构造出一张图,而在这个图中若存在回路,就代表存在死锁,因此资源间相互发生等待。这是一种较为主动的死锁检测机制,在每个事务请求锁并发生等待时都会判断是否存在回路,若存在则有死锁,通常来说InnoDB存储引擎选择回滚undo量最小的事务。
5. 优化
5.1 说说你对数据库优化的理解
MySQL数据库优化是多方面的,原则是减少系统的瓶颈,减少资源的占用,增加系统的反应速度。例如,通过优化文件系统,提高磁盘I\O的读写速度;通过优化操作系统调度策略,提高MySQL在高负荷情况下的负载能力;优化表结构、索引、查询语句等使查询响应更快。
针对查询,我们可以通过使用索引、使用连接代替子查询的方式来提高查询速度。
针对慢查询,我们可以通过分析慢查询日志,来发现引起慢查询的原因,从而有针对性的进行优化。
针对插入,我们可以通过禁用索引、禁用检查等方式来提高插入速度,在插入之后再启用索引和检查。
针对数据库结构,我们可以通过将字段很多的表拆分成多张表、增加中间表、增加冗余字段等方式进行优化。
5.2 如何优化MySQL的查询
- 使用索引
如果查询时没有使用索引,查询语句将扫描表中的所有记录。在数据量大的情况下,这样查询的速度会很慢。如果使用索引进行查询,查询语句可以根据索引快速定位到待查询记录,从而减少查询的记录数,达到提高查询速度的目的。
索引可以提高查询的速度,但并不是使用带有索引的字段查询时索引都会起作用。有几种特殊情况,在这些情况下有可能使用带有索引的字段查询时索引并没有起作用。
- 使用LIKE关键字的查询语句
在使用LIKE关键字进行查询的查询语句中,如果匹配字符串的第一个字符为“%”,索引不会起作用。只有“%”不在第一个位置,索引才会起作用。 - 使用多列索引的查询语句
MySQL可以为多个字段创建索引。一个索引可以包括16个字段。对于多列索引,只有查询条件中使用了这些字段中的第1个字段时索引才会被使用。 - 使用OR关键字的查询语句
查询语句的查询条件中只有OR关键字,且OR前后的两个条件中的列都是索引时,查询中才使用索引。否则,查询将不使用索引。
- 优化子查询
使用子查询可以进行SELECT语句的嵌套查询,即一个SELECT查询的结果作为另一个SELECT语句的条件。子查询可以一次性完成很多逻辑上需要多个步骤才能完成的SQL操作。
子查询虽然可以使查询语句很灵活,但执行效率不高。执行子查询时,MySQL需要为内层查询语句的查询结果建立一个临时表。然后外层查询语句从临时表中查询记录。查询完毕后,再撤销这些临时表。因此,子查询的速度会受到一定的影响。如果查询的数据量比较大,这种影响就会随之增大。
在MySQL中,可以使用连接(JOIN)查询来替代子查询。连接查询不需要建立临时表,其速度比子查询要快,如果查询中使用索引,性能会更好。
5.3 怎样插入数据才能更高效
影响插入速度的主要是索引、唯一性校验、一次插入记录条数等。针对这些情况,可以分别进行优化。
对于MyISAM引擎的表,常见的优化方法如下:
- 禁用索引
对于非空表,插入记录时,MySQL会根据表的索引对插入的记录建立索引。如果插入大量数据,建立索引会降低插入记录的速度。为了解决这种情况,可以在插入记录之前禁用索引,数据插入完毕后再开启索引。对于空表批量导入数据,则不需要进行此操作,因为MyISAM引擎的表是在导入数据之后才建立索引的。 - 禁用唯一性检查
插入数据时,MySQL会对插入的记录进行唯一性校验。这种唯一性校验也会降低插入记录的速度。为了降低这种情况对查询速度的影响,可以在插入记录之前禁用唯一性检查,等到记录插入完毕后再开启。 - 使用批量插入
插入多条记录时,可以使用一条INSERT语句插入一条记录,也可以使用一条INSERT语句插入多条记录。使用一条INSERT语句插入多条记录的情形如下,而这种方式的插入速度更快。
INSERT INTO fruits VALUES
('x1', '101', 'mongo2', '5.7'),
('x2', '101', 'mongo3', '5.7'),
('x3', '101', 'mongo4', '5.7');
- 使用LOAD DATA INFILE批量导入
当需要批量导入数据时,如果能用LOAD DATA INFILE语句,就尽量使用。因为LOAD DATA INFILE语句导入数据的速度比INSERT语句快。
对于InnoDB引擎的表,常见的优化方法如下:
- 禁用唯一性检查
插入数据之前执行 set unique_checks=0 来禁止对唯一索引的检查,数据导入完成之后再运行 set unique_checks=1 。这个和MyISAM引擎的使用方法一样。 - 禁用外键检查
插入数据之前执行禁止对外键的检查,数据插入完成之后再恢复对外键的检查。 - 禁用自动提交
插入数据之前禁止事务的自动提交,数据导入完成之后,执行恢复自动提交操作。
5.4 说说表中包含几千万条数据该怎么办
建议按照如下顺序进行优化:
- 优化SQL和索引;
- 增加缓存,如memcached、redis;
- 读写分离,可以采用主从复制,也可以采用主主复制;
- 使用MySQL自带的分区表,这对应用是透明的,无需改代码,但SQL语句是要针对分区表做优化的;
- 做垂直拆分,即根据模块的耦合度,将一个大的系统分为多个小的系统;
- 做水平拆分,要选择一个合理的sharding key,为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表。
5.5 说说MySQL的慢查询优化
慢查询是指执行时间超过阈值(默认为10秒)的 SQL 查询语句,通常情况下这些查询语句会给数据库带来很大的压力,影响系统的性能和稳定性,因此需要对慢查询进行优化。下面是一些 MySQL 慢查询优化的方法:
- 索引优化:对表中常用的字段建立索引,可以大大提高查询效率。在使用索引时还需要注意索引的选用(单列索引还是复合索引)、索引的覆盖(是否能够避免全表扫描)等问题。
- SQL 语句优化:尽量避免使用子查询和临时表。在查询数据时,尽量使用 JOIN 连接查询,避免使用 UNION 和 UNION ALL,因为它们会耗费更多的资源。
- 数据库服务器优化:适当调整数据库服务器的参数,如增加缓存区大小、调整线程池参数、优化 SQL 模式和字符集等,也可以有效提高查询性能和减少 SQL 查询的响应时间。
- 分区表(Partitioning):将表水平分割成多个逻辑分区,将来自用户的数据写入适当的表分区,查询时只查询包含相应数据的数据分区,从而提高查询效率。
- 调整查询执行顺序:优化查询语句执行计划,可以采用 FORCE INDEX 强制使用索引、使用 EXPLAIN 分析查询执行情况、调整查询条件的顺序等方式来改善查询执行效率。
总之,MySQL 慢查询优化需要综合考虑索引优化、SQL 语句优化、数据库服务器优化、分区表、查询执行顺序等多种因素,根据实际情况进行优化,以提高查询效率和减少响应时间。
5.6 说说你对explain的了解
MySQL中提供了EXPLAIN语句和DESCRIBE语句,用来分析查询语句,EXPLAIN语句的基本语法如下:
EXPLAIN [EXTENDED] SELECT select_options
使用EXTENED关键字,EXPLAIN语句将产生附加信息。执行该语句,可以分析EXPLAIN后面SELECT语句的执行情况,并且能够分析出所查询表的一些特征。下面对查询结果进行解释:
- id:SELECT识别符。这是SELECT的查询序列号。
- select_type:表示SELECT语句的类型。
- table:表示查询的表。
- type:表示表的连接类型。
- possible_keys:给出了MySQL在搜索数据记录时可选用的各个索引。
- key:是MySQL实际选用的索引。
- key_len:给出索引按字节计算的长度,key_len数值越小,表示越快。
- ref:给出了关联关系中另一个数据表里的数据列名。
- rows:是MySQL在执行这个查询时预计会从这个数据表里读出的数据行的个数。
- Extra:提供了与关联操作有关的信息。
5.7 说说explain关注什么
EXPLAIN 是 MySQL 中的一个关键字,用于将一个 SELECT 查询语句转化为可读的执行计划(execution plan)。执行计划是分析查询语句时,MySQL 对优化器进行的解释,展示了 MySQL 如何解析一个查询的每个组成部分,并从哪些表中获取数据、使用哪些索引、排序方式和连接方式。EXPLAIN 所提供的信息可以帮助我们更好地理解和优化查询语句。在使用 EXPLAIN 时,应该关注以下几个方面:
- 是否使用了索引:如果查询语句中的字段可以使用索引,而执行计划中却没有使用索引,则需要考虑对查询语句或索引进行优化。
- 执行计划中的 JOIN 顺序:当查询语句中有多个表之间的 JOIN 操作时,MySQL 会根据优化器的规则来决定 JOIN 执行的顺序,我们应该尽量让 MySQL 按照优化的路径来执行 JOIN。
- 执行计划中的扫描类型:MySQL 在执行查询时,有多种扫描类型,包括全表扫描、索引扫描、范围扫描等。我们应该尽可能地使用索引扫描,避免全表扫描,以提高查询效率。
- 是否有分组、排序或聚合操作:如果查询语句中包含 GROUP BY、ORDER BY 或聚合函数等操作,执行计划应该显示使用何种算法来进行相应的操作。
- 数据表关联顺序:行数较小的表放在前面,优先进行关联操作,可以加快联接查询速度。
通过分析 EXPLAIN 的执行计划,我们可以明确查询语句中的瓶颈,并对索引、表结构和查询方式等进行优化,从而提高查询效率。
6. 其他
6.1 简述数据库设计的三大范式
数据库设计的三大范式(Normalization)是一种设计方法,旨在将重复数据以及数据不一致性等问题最小化,并提高数据的可靠性和完整性。这三个范式依次为:
- 第一范式(1NF):数据表中的每个数据都是原子性的,不可再分解。也就是每个列都应该是单一的,不允许保存多个值。例如,一个学生信息表中的每个学生只能有一个学生学号,不能将学号以逗号分割的形式保存在同一列中。
- 第二范式(2NF):数据表中的每个数据与主键相关,不存在部分相关。也就是说,在一个关系数据库中,每个表只描述一种实体,就是说,不存在部分依赖一个复合主键的非主属性。例如,一个订单明细表,如果有两个属性 order_id 和 product_id 对应一个唯一的 order_line_id,则应该将这些属性拆分成两个表。
- 第三范式(3NF):数据表中的数据不依赖于其他非主键列。也就是说,不存在传递依赖关系,即任何非主键列都不能由其他非主键列派生而来。例如,一个包含学生姓名和地址的表,如果这些信息和学生成绩没有直接关系,则应该将地址信息拆分出来成为一个表和学生表通过外键进行关联。
6.2 说说你对MySQL引擎的了解
MySQL 支持多个存储引擎(storage engine),不同的存储引擎对于数据的处理方式和性能表现是不同的,可以根据需求选择不同的存储引擎进行使用,常见的存储引擎包括:
- InnoDB:默认的存储引擎,支持事务和行级锁定,具有较好的并发性能。适用于需要事务支持、高并发或数据频繁更新的应用。
- MyISAM:不支持事务,支持表级锁定,具有较快的查询性能,适用于读密集型应用。
- Memory:将表存放在内存中,速度非常快,但是不支持持久化(重启后数据会丢失),适用于需要快速读写数据且数据不需要长期保存的临时应用。
- Archive:专门用来处理归档数据,具有高压缩比和快速插入的特点,但只支持 INSERT 和 SELECT 操作,不支持 UPDATE 和 DELETE 操作,适用于日志、文档等归档数据的存储。
- NDB Cluster:可以支持 MySQL 集群,支持事务和行级锁定,具有较好的并发性能和可扩展性,适用于大规模的高并发应用环境。
6.3 说说你对redo log、undo log、binlog的了解
在 MySQL 中,有三个常见的日志文件类型,分别是 redo log、undo log 和 binlog。
- redo log:是 MySQL 中的事务日志,用于记录事务修改的数据,以保证数据的持久性。redo log 是 InnoDB 存储引擎特有的日志,每个 InnoDB 存储引擎都有自己的 redo log。当一个事务提交时,它所修改的数据会先写入 redo log,待写入完成后,再将数据写入磁盘上的数据文件中。如果系统发生异常而未能将数据写入数据文件,MySQL 重启后可以通过 redo log 恢复数据。
- undo log:记录了事务操作前的数据,用于回滚事务。当一个事务在执行期间发生了错误,需要回滚时,MySQL 可以使用 undo log 恢复修改前的数据。同时,在 MVCC(多版本并发控制)中,undo log 也用于维护数据的历史版本。undo log 面向所有的存储引擎,是 MySQL 全局的日志。
- binlog:是 MySQL 的二进制日志,用于记录数据库中所有更新、插入、删除等操作。binlog 只记录对数据的修改操作,而不记录查询等操作,可以用于数据恢复、主从复制以及数据审计等。binlog 面向所有的存储引擎,是 MySQL 全局的日志。
6.4 说说你对MVCC的了解
InnoDB默认的隔离级别是RR(REPEATABLE READ),RR解决脏读、不可重复读、幻读等问题,使用的是MVCC。MVCC全称Multi-Version Concurrency Control,即多版本的并发控制协议。它最大的优点是读不加锁,因此读写不冲突,并发性能好。InnoDB实现MVCC,多个版本的数据可以共存,主要基于以下技术及数据结构:
- 隐藏列:InnoDB中每行数据都有隐藏列,隐藏列中包含了本行数据的事务id、指向undo log的指针等。
- 基于undo log的版本链:每行数据的隐藏列中包含了指向undo log的指针,而每条undo log也会指向更早版本的undo log,从而形成一条版本链。
- ReadView:通过隐藏列和版本链,MySQL可以将数据恢复到指定版本。但是具体要恢复到哪个版本,则需要根据ReadView来确定。所谓ReadView,是指事务(记做事务A)在某一时刻给整个事务系统(trx_sys)打快照,之后再进行读操作时,会将读取到的数据中的事务id与trx_sys快照比较,从而判断数据对该ReadView是否可见,即对事务A是否可见。
6.5 说说MySQL主从同步是如何实现的
MySQL 主从同步是一种将主数据库中的数据自动同步到从数据库中的机制,这种机制可以提升数据的冗余性和可用性,同时在读写分离的架构中也有广泛应用。
复制(replication)是MySQL数据库提供的一种高可用高性能的解决方案,一般用来建立大型的应用。总体来说,replication的工作原理分为以下3个步骤:
- 主服务器(master)把数据更改记录到二进制日志(binlog)中。
- 从服务器(slave)把主服务器的二进制日志复制到自己的中继日志(relay log)中。
- 从服务器重做中继日志中的日志,把更改应用到自己的数据库上,以达到数据的最终一致性。
复制的工作原理并不复杂,其实就是一个完全备份加上二进制日志备份的还原。不同的是这个二进制日志的还原操作基本上实时在进行中。这里特别需要注意的是,复制不是完全实时地进行同步,而是异步实时。这中间存在主从服务器之间的执行延时,如果主服务器的压力很大,则可能导致主从服务器延时较大。复制的工作原理如下图所示,其中从服务器有2个线程,一个是I/O线程,负责读取主服务器的二进制日志,并将其保存为中继日志;另一个是SQL线程,复制执行中继日志。