MySQL 面试题
1.说一下 MySQL 执行一条查询语句的内部执行过程?
答:MySQL 执行一条查询的流程如下:
- 客户端先通过连接器连接到 MySQL 服务器;
- 连接器权限验证通过之后,先查询是否有查询缓存,如果有缓存(之前执行过此语句)则直接返回缓存数据,如果没有缓存则进入分析器;
- 分析器会对查询语句进行语法分析和词法分析,判断 SQL 语法是否正确,如果查询语法错误会直接返回给客户端错误信息,如果语法正确则进入优化器;
- 优化器是对查询语句进行优化处理,例如一个表里面有多个索引,优化器会判别哪个索引性能更好;
- 优化器执行完就进入执行器,执行器则开始执行语句进行查询比对了,直到查询到满足条件的所有数据,然后进行返回。
2.MySQL 查询缓存有什么优缺点?
答:MySQL 查询缓存功能是在连接器之后发生的,它的优点是效率高,如果已经有缓存则会直接返回结果。
查询缓存的缺点是失效太频繁导致缓存命中率比较低,任何更新表操作都会清空查询缓存,因此导致查询缓存非常容易失效。
3.MySQL 的常用引擎都有哪些?
答:MySQL 的常用引擎有 InnoDB、MyISAM、Memory 等,从 MySQL 5.5.5 版本开始 InnoDB 就成为了默认的存储引擎。
4.常用的存储引擎 InnoDB 和 MyISAM 有什么区别?
答:
- InnoDB 支持事务,而 MyISAM 不支持事务
- InnoDB 支持行级锁,MyISAM 不支持行级锁,只支持到表锁;
- InnoDB 支持外键,MyISAM 不支持外键;
- MyISAM 支持 FULLTEXT 类型的全文索引,InnoDB 不支持 FULLTEXT 类型的全文索引,但是 InnoDB 可以使用 sphinx 插件支持全文索引,并且效果更好;
- InnoDB 主键查询性能高于 MyISAM。
5.什么叫回表查询?
答:普通索引查询到主键索引后,回到主键索引树搜索的过程,我们称为回表查询。
通过explain分析当extra为NULL,意味着本次查询进行了“回表”操作,我们知道innodb采用B+树的索引结构,主键和数据绑定在一起,主键索引b+树的叶子节点存储了数据信息,而普通索引叶子节点存储的是主键值。因此,我们可以得知当通过普通索引查询时无法直接定位行记录,通常情况下,需要扫描两遍索引树。
(1)id为PK,聚集索引,叶子节点存储行记录;
(2)name为KEY,普通索引,叶子节点存储PK值,即id;
既然从普通索引无法直接定位行记录,那普通索引的查询过程是怎么样的呢?通常情况下,需要扫描两遍索引树。
例如:select * from t where name=‘lisi’;是如何执行的呢?
1)先扫描name索引树,找到主键值id=5。
2)再扫描主键索引,找到对应行。
6.InnoDB引擎表的主键删掉,是不是就没有主键
答:不是,如果把主键删掉了,那么 InnoDB 会自己生成一个长度为 6 字节的 rowid 作为主键。
7.清空表的所有数据性能最好的语句是?
A:delete from t B:drop table t C:truncate table t
truncate table t 清除表数据不会写日志,后面不能带条件,是把表的所有记录都删掉;
delete 删除可以带条件,但是要把删除的记录写到日志中,写到undolog日志文件中,因此 truncate 的效率要高于 delete,它们都只会删除表的数据,索引、外键、表结构都不删除
drop table直接把表都删除了
8.唯一索引和普通索引哪个性能更好?
答:唯一索引和普通索引的性能对比分为以下两种情况:
- 对于查询来说两者都是从索引树进行查询,性能几乎没有任何区别;
- 对于更新操作来说,因为唯一索引需要先将数据读取到内存,然后需要判断是否有冲突,因此比普通索引多了判断操作,从而性能就比普通索引性能要低。
9.mysql索引问题?
- 为什么要有索引:主要是提高查询速度,减少io次数。
- 什么是索引:就是一种数据结构,保存到磁盘文件中,在mysql8中索引和表数据共一个文件(*.ibd)文件,索引相当于字典的音序表,如果要查某个字,如果不使用音序表,则需要从几百页中逐页去查
- 索弓|的数据结构有哪些:B+ Tree索引和Hash索引和B-tree索引,Inodb存储引擎默认是B+Tree索引。Memory存储引擎默认Hash索引;myIsam是用的B-tree结构索引
10.事务是什么?它有什么特性?实现原理是什么?
答:事务是一系列的数据库操作,是数据库应用的基本单位。
1.在 MySQL 中只有 InnoDB 引擎支持事务,它的四个特性如下:
-
原子性(Atomic),要么全部执行,要么全部不执行;事务的原子性是通过undolog回滚日志来实现的,记录更新的相反操作
-
一致性(Consistency),事务的执行使得数据库从一种正确状态转化为另一种正确状态;通过原子性,持久性,隔离性来实现的
-
隔离性(Isolation),在事务正确提交之前,不允许把该事务对数据的任何改变提供给其他事务;通过读写锁+MVCC来实现的
-
持久性(Durability),事务提交后,其结果永久保存在数据库中。通过redo log来实现的
2.实现原理
-
原子性原理:通过undolog回滚日志来实现的,undo log是一种用于撤销回退的日志, 可以理解为当delete一条记录时, undo log中会记录一条对应的insert记录;当insert一条记录时, undo log中会一条对应的delete记录;当update一条记录时, undo log中记录一条对应相反的update记录,如下:
update user set name = "李四" where id = 1; ---修改之前name=张三
此时undo log会记录一条相反的update语句,如下:
update user set name = "张三" where id = 1;
如果这个修改出现异常,可以使用undo log日志来实现回滚操作,以保证事务的一致性。
这里要需要说明一下,在 MySQL5.6.3 之前的版本中,这个 undo tablespace 是和 system tablespace 系统表空间存放在一起的,也就是没有单独的 undo log 文件,直接存放在 ibdata1 文件里边,在 MySQL5.6.3 之后的版本中,MySQL支持将undo log tablespace单独剥离出来,从8.0.3版本开始,默认undo tablespace的个数从0调整为2,也就是在8.0版本中,独立undo tablespace被默认打开
-
持久性原理:Redo Log(重做日志)记录的是新数据的备份。在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化到磁盘。当系统崩溃时,虽然数据没有持久化,但是Redo Log经持久化。系统可以根据Redo Log的内容刷新到磁盘
redolog 的存储形式
show variables like "%innodb_log%”;
重要参数说明:
- innodb_log_buffer_size :redolog缓存区的大小,默认 8388608,即8M
- innodb_log_file_size:redolog文件的大小,默认 50331648,即48M
- innodb_log_files_in_group :日志文件组中文件数量
- innodb_log_group_home_dir:日志文件位置,在/var/lib/mysql下ib_logfile0 和ib_logfile1文件
innodb的redo log 是固定大小的,可以配置一组4个文件,每个文件大小是1gb,从头开始写,写到末尾就回到开头循环写,write pos 是当前记录的位置,一边写一边后移。checkpoint是当前要擦除的位置,也是往后推移且循环的,擦除记录前要把记录更新到数据文件
11.innodb_flush_log_at_trx_commit参数有什么用?
答:InnoDB提供了一个参数innodb_flush_log_at_trx_commit 控制redolog日志刷新到磁盘的策略
mysql> set @@global.innodb_flush_log_at_trx_commit=2;
-
当 innodb_flush_log_at_trx_commit 值为1时(默认)。 事务每次提交都必须将log buffer中的日志写入os buffer并调用fsync()写入磁盘中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO性能较差。
-
当 innodb_flush_log_at_trx_commit 值为0时。 事务提交时不将log buffer写入到os buffer,而是每秒写入os buffer并调用fsync()写入到log file on disk中。这实际上相当于在内存中维护了一个用户设计的缓冲区,它减少了和os buffer之间的数据传输,有更好的性能。每秒写入磁盘,系统崩溃会丢失1s的数据
-
当 innodb_flush_log_at_trx_commit 值为2时。 每次提交都仅写入os buffer,然后每秒调用fsync()将os buffer中的日志写入到log file on disk中。虽然说我们是每秒调用fsync()将os buffer中的日志写入到log file on disk中,但是平时即使不调用fsync,数据也会2自主地逐渐进入磁盘。所以当发生系统崩溃,相比第二种情况,会丢失较少的数据。
11.MySQL 中有几种事务隔离级别?分别是什么?
答:MySQL 中有四种事务隔离级别, MySQL 默认使用 repetable read 的事务隔离级别。分别是:
- read uncommited,未提交读,读到未提交数据;
- read committed,读已提交,也叫不可重复读,两次读取到的数据不一致;
- repetable read,可重复读;
- serializable,串行化,读写数据都会锁住整张表,数据操作不会出错,但并发性能极低,开发中很少用到。
12.如何设置 MySQL 的事务隔离级别?
答:MySQL 事务隔离级别 mysql.cnf 文件里设置的(默认目录 /etc/my.cnf),在文件的文末添加配置:
transaction-isolation = REPEATABLE-READ
set @@tx_isolation = 隔离级别
可用的配置值:READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE。
13.MySQL 出现了中文乱码该如何解决?
答:解决 MySQL 中文乱码的问题,可以设置全局编码或设置某个数据库或表的编码为 utf8。 设置全局编码:
- set character_set_client=‘utf8’; 客户端使用的编码,如GBK, UTF8, 比如你写的sql语句是什么编码的
- set character_set_connection=‘utf8’; 连接使用的编码,这个编码的作用是服务器使用character_set_connection和collation_connection系统变量。它将客户端发送的查询从character_set_client系统变量转换到character_set_connection
- set character_set_results=‘utf8’; 查询返回的结果集的编码(从数据库读取的数据是什么编码的)
- alter database db character set utf8; 默认数据库使用的字符集
- alter table t character set utf8;设置表的编码:
14.InnoDB 为什么要使用 B+ 树,而不是 B 树、Hash、红黑树或二叉树?
答: •B 树:不管叶子节点还是非叶子节点,都会保存数据,这样导致在非叶子节点中能保存的指针数量变少(有些资料也称为扇出),指针少的情况下要保存大量数据,只能增加树的高度,导致 IO 操作变多,查询性能变低。 •Hash:虽然可以快速定位,但是没有顺序,IO 复杂度高。 •二叉树:树的高度不均匀,不能自平衡,查找效率跟数据有关(树的高度),并且 IO 代价高。 •红黑树:树的高度随着数据量增加而增加,IO 代价高。
15.mysql锁的类型有哪些?
参考mysql里的锁笔记
16.MySQL 是如何处理死锁?
答:MySQL 对待死锁常见的两种策略: •通过 innodb_lock_wait_timeout 来设置超时时间,一直等待直到超时; •发起死锁检测,发现死锁之后,主动回滚死锁中的某一个事务,让其他事务继续执行。
SHOW GLOBAL VARIABLES LIKE 'innodb_lock_wait_timeout'; ##默认是50s
SET GLOBAL innodb_lock_wait_timeout=500; //单位时间是秒
SHOW GLOBAL VARIABLES LIKE 'innodb_lock_wait_timeout';
17.什么是全局锁?它的应用场景有哪些?
答:全局锁就是对整个数据库实例加锁,当需要同步数据的时候,也就是做全量逻辑备份,必须要全局锁,这个时候整个库会处于完全的只读状态。
18.使用全局锁会导致什么问题?
答:使用全局锁会使整个系统不能执行更新操作,所有的更新业务会出于等待状态;
19.InnoDB 如何实现行锁?
答:只有通过索引条件检索数据,InnoDB 才使用行级锁,否则 InnoDB 将使用表锁。使用 for update 来实现行锁,具体脚本如下: select * from t where id=1 for update 其中 id 字段必须有索引。所以是表锁还是行锁就看表有没有创建索引
20.MySQL 性能指标都有哪些?如何得到这些指标?
答:MySQL 最重要的性能指标有以下两个:
- QPS(Queries Per Second)意思是“每秒查询率”,是一台服务器每秒能够处理的查询次数,用户发起查询请求到服务器做出响应这算一次,如一秒内用户完成了50次查询请求,那此时服务器QPS就是50,通过status命令查看
mysql> status;
####queries per second avg: 0.022
mysql> show global status like 'quest%';
####Questions 查询次数
- TPS(Transactions Per Second),每秒处理事务数。 一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程,这些性能指标可以通过 show status 来查询当前数据库状态的结果信息中估算出来,show status 会有 300 多条状态信息记录,其中以下这些信息 QPS 和 TPS 有关系
21.MySQL 中的重要日志分为哪几个?
① 错误日志 :用来记录 MySQL 服务器运行过程中的错误信息,比如,无法加载 MySQL 数据库的数据文件,或权限不正确等都会被记录在此,还有主从复制环境下,从服务器进程的信息也会被记录进错误日志。默认情况下,错误日志是开启的,且无法被禁止。
查看错误日志文件
SHOW VARIABLES LIKE 'log_error';
##linux默认位置## /var/log/mysqld.log
##window默认位置## C:\dev\mysql\mysql-8.0.19-winx64\data\QH-20210227YZPF.err
将 log_error 选项加入到 MySQL 配置文件的 [mysqld] 组中,形式如下:
[mysqld]
log-error=dir/{filename} ###重启 MySQL 服务后,参数开始生效,可以在指定路径下看到 filename.err 的文件
② 查询(通用)日志 :查询日志在 MySQL 中被称为 general log(通用日志),查询日志里的内容不要被“查询日志”误导,认为里面只存储 select 语句,其实不然,查询日志里面记录了数据库执行的所有命令,不管语句是否正确,如增删改查语句都会被记录,在并发操作非常多的场景下,查询信息会非常多,那么如果都记录下来会导致 IO 非常大,影响 MySQL 性能。因此如果不是在调试环境下,是不建议开启查询日志功能的。 查询日志的开启有助于帮助我们分析哪些语句执行密集,执行密集的 select 语句对应的数据是否能够被缓存,同时也可以帮助我们分析问题,因此,可以根据自己的实际情况来决定是否开启查询日志。 查询日志模式是关闭的,可以通过以下命令开启查询日志。
查看通用日志的位置
SHOW VARIABLES LIKE '%general%';
##linux默认位置## /var/lib/mysql/localhost.log value值是off
##window默认位置## C:\dev\mysql\mysql-8.0.19-winx64\data\QH-20210227YZPF.log value值是off
默认通用日志是关闭的,可以开启
set global general_log=1 ##关闭通用日志 set global general_log=0
##重启mysql服务 service mysqld restart
③ 慢日志 slow log:慢查询会导致 CPU、内存消耗过高,当数据库遇到性能瓶颈时,大部分时间都是由于慢查询导致的,慢查询导致IO阻塞,开启慢查询日志,可以让 MySQL 记录下查询超过指定时间的语句,之后运维人员通过定位分析,能够很好的优化数据库性能。默认情况下,慢查询日志是不开启的,只有手动开启了,慢查询才会被记录到慢查询日志中。
查看慢日志的位置
mysql> SHOW VARIABLES LIKE 'slow_query%';
##linux默认位置## /var/lib/mysql/localhost-slow.log value值是off
##window默认位置## C:\dev\mysql\mysql-8.0.19-winx64\data\QH-20210227YZPF-slow.log value值是off
##查询超过多少秒才记录
mysql> SHOW VARIABLES LIKE 'long_query_time'; ###时间以秒为单位
启动和设置慢查询日志
可以通过 log-slow-queries 选项开启慢查询日志。通过 long_query_time 选项来设置时间值,时间以秒为单位
vim /etc/my.cnf
[mysqld]
log-slow-queries=/tmp/mysql-slow.log
long_query_time=n
还可以通过以下命令启动慢查询日志、设置指定时间:
SET GLOBAL slow_query_log=ON/OFF;
#SET GLOBAL slow_query_log_file = /var/log/slow-query.log # 慢查询日志存放目录
SET GLOBAL long_query_time=5; #设置5秒
测试:
select sleep(10); #查询10秒
22.redo log和undo log和bin log
① redo log(重做日志) :在事务频繁提交中,为了避免每一次提交都要往磁盘写, 造成IO性能的问题,MySQL 采用了这样一种缓存机制,先将数据写入内存中,再批量把内存中的数据统一刷回磁盘。为了避免将数据刷回磁盘过程中,因为掉电或系统故障带来的数据丢失问题,InnoDB 采用 redo log 来解决此问题。
② undo log(回滚日志) :用于存储日志被修改前的值,从而保证如果修改出现异常,可以使用 undo log 日志来实现回滚操作。 undo log 和 redo log 记录物理日志不一样,它是逻辑日志,可以认为当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录,反之亦然,当 update 一条记录时,它记录一条对应相反的 update 记录,当执行 rollback 时,就可以从 undo log 中的逻辑记录读取到相应的内容并进行回滚。
mysql> show variables like '%innodb_undo%';
③ bin log(二进制日志) :是一个二进制文件,主要记录所有数据库表结构变更,比如,CREATE、ALTER TABLE 等,以及表数据修改,比如,INSERT、UPDATE、DELETE 的所有操作,bin log 中记录了对 MySQL 数据库执行更改的所有操作,并且记录了语句发生时间、执行时长、操作数据等其他额外信息,但是它不记录 SELECT、SHOW 等那些不修改数据的 SQL 语句。
binlog 的作用如下: •恢复(recovery):某些数据的恢复需要二进制日志。比如,在一个数据库全备文件恢复后,用户可以通过二进制日志进行 point-in-time 的恢复; •复制(replication):其原理与恢复类似,通过复制和执行二进制日志使一台远程的 MySQL 数据库(一般称为 slave 或者 standby)与一台 MySQL 数据库(一般称为 master 或者 primary)进行实时同步; binlog 默认是关闭状态,可以在 MySQL 配置文件(my.cnf)中通过配置参数
show variables like '%log_bin%'; ##查看binlog日志情况
修改mysql的etc下的my.cnf文件
[mysqld]
log_bin=ON #打开binlog日志
##重启服务
service mysqld restart
重启mysql服务之后,在/var/lib/mysql/目录下会看到, ON.000001文件,这是binlog日志的基本文件名,ON.index指定的是binlog文件的索引文件,这个文件管理了所有的binlog文件的目录
23.redo log 和 bin log 有什么区别?
redo log(重做日志)和 binlog(归档日志)都是 MySQL 的重要的日志,它们的区别如下: •redo log 是物理日志,记录的是“在某个数据页上做了什么修改”; •binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1”; •redo log 是 InnoDB 引擎特有的,binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用; •redo log 是循环写的,空间固定会用完,binlog 是可以追加写入的,“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
24.如何分析mysql执行计划?
答:通过explain关键字来分析—具体见笔记部分
26.数据库分片方案有哪些?
MyCat、网易的 DDB 都是中间件代理的典型代表。
27.查询语句的优化方案有哪些?
参考创建索引之后,哪些查询导致索引失效
28.MySQL 毫无规律的异常重启,可能产生的原因是什么?该如何解决?
「参考答案」可能是积累的长连接导致内存占用太多,被系统强行杀掉导致的异常重启,因为在 MySQL 中长连接在执行过程中使用的临时内存对象,只有在连接断开的时候才会释放,这就会导致内存不断飙升,解决方案如下: •定期断开空闲的长连接; •如果是用的是 MySQL 5.7 以上的版本,可以定期执行 mysql_reset_connection 重新初始化连接资源,这个过程会释放之前使用的内存资源,恢复到连接刚初始化的状态。
29.查询一个表的数据,多行的值用一行显示逗号隔开,行转列
例如:查询学生表,显示Id和name两列,一共有10行,现用一行来显示10行记录,
select GROUP_CONCAT(id,userName) from smbms_user;
说明:在用一行显示时,先前的一行一行的记录默认是用逗号隔开
30.判断一个表的两个值的大小
select (case when studentno-gradeid>0 then
studentno else gradeid END)
from student
31.查询员工信息,并计算薪水列,不同范围计算方式不一样
SELECT EMPNO,ENAME,JOB, SAL*(CASE
WHEN EMP.SAL>=1000 AND EMP.SAL<=2000 THEN 500
WHEN EMP.SAL>2000 AND EMP.SAL<=3000 THEN 1000
WHEN EMP.SAL>3000 AND EMP.SAL<=4000 THEN 2000
ELSE 3000
END
) AS X FROM EMP
32.Mysql 存在既更新,不存在就添加
CREATE TABLE `willow_player` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`player_id` bigint(16) NOT NULL DEFAULT '0',
`num` int(11) NOT NULL DEFAULT '0',
`award_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '类型',
`repeat_num` smallint(11) NOT NULL DEFAULT '0' COMMENT '重复轮数',
PRIMARY KEY (`id`),
UNIQUE KEY `num` (`player_id`,`award_type`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=96 DEFAULT CHARSET=utf8mb4;
#======================================================================
insert into willow_player (player_id,award_type,num) values(20001,0,1)
方式一:
insert into willow_player (player_id,award_type,num) values(20001,0,1) on DUPLICATE key update num=num+values(num)
由于player_id
,award_type
创建了联合索引,在表中已经有0,1的数据,此时再次插入就是重复了,此时就会执行修改,INSERT 语句的一部分,如果指定 ON DUPLICATE KEY UPDATE ,并且插入行后会导致在一个UNIQUE索引或PRIMARY KEY中出现重复值,则在出现重复值的行执行UPDATE,如果不会导致唯一值列重复的问题,则插入新行
方式二:
- 首先判断数据是否存在; 2. 如果不存在,则插入;3.如果存在,则更新。
replace into willow_player(id, player_id) select 20002,11; 不存在则插入
replace into willow_player(id, player_id) select 20002,23; 存在则修改
33.mysql批量插入-存储过程
CREATE DEFINER=`root`@`localhost` PROCEDURE `pro100`()
BEGIN
DECLARE i INT;
DECLARE char_str varchar(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
DECLARE return_str varchar(255) DEFAULT '';
DECLARE age INT;
SET i = 1500000;
WHILE i < 3000000 do
SET return_str = substring(char_str, FLOOR(1 + RAND()*62), 8);
SET i = i+1;
SET age = FLOOR(RAND() * 100);
INSERT INTO student (id, name, age) values (i, return_str, age);
END WHILE;
END
调用存储过程
call pro100()
34.exists和in查询原理的区别
exists
1、首先执行一次外部查询,并缓存结果集,如 SELECT * FROM A
2、遍历外部查询结果集的每一行记录R,如果子查询有返回结果,则EXISTS子句返回TRUE,这一行R可作为外部查询的结果行,否则不能作为结果
in
先查询内表,将内表的查询结果当做条件提供给外表查询语句进行比较;
外层小表,内层大表(或者将sql从左到由来看:左面小表,右边大表): exists 比 in 的效率高
外层大表,内层小表(或者将sql从左到由来看:左面大表,右边小表): in 比 exists 的效率高
select * from k15_teacher where exists (select * from k15_teacher where 1 = 1)
select * from k15_teacher where not exists (select * from k15_teacher where 1 = 2)