尚硅谷Java面试题第四季-MySQL面试题

1.如何建立复合索引,一般加在哪些字段?建索引的理论依据或者经验


2.Innodb的行锁到底锁了什么?



结论:
InnoDB的行锁,是通过锁住索引来实现的,如果加锁查询的时候没有使用到索引,会将整个聚簇索引都锁住,相当于锁表了。

命中索引->锁行,没有命中->锁表。

3.什么是回表?

答案:通过辅助索引(二级索引)拿到主键后再回到主键索引查询的过程,就叫做「回表」。

1)B+树检索原理

由于B+树的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+树后其结构如下图所示:

B+树中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+树的高度。

B+树算法: 通过继承了B树的特征,B+树相比B树,新增叶子节点与非叶子节点关系。

叶子节点中包含了键值和数据,根节点和枝节点中,只是包含键值和子节点引用,不包含数据。

通过非叶子节点查询叶子节点获取对应的数据,所有相邻的叶子节点包含非叶子节点使用链表进行结合,叶子节点是顺序排序并且相邻节点有顺序引用的关系。

2)总结

回表是指数据库根据索引(非主键)找到了指定的记录所在行后,还需要根据主键再次到数据块里获取数据的操作。这个过程在MySQL中可能涉及两次查询:首先通过索引扫描找到满足条件的记录的主键值,然后再通过主键值去主键索引中查找完整的行记录。

具体来说,在InnoDB存储引擎中,当通过非聚簇索引查询数据时,由于索引中不包含完整的行数据,因此需要根据索引中找到的主键值再去聚簇索引中查找完整的行数据,这个过程就是回表。

然而,回表操作虽然提供了更全面的数据信息,但也带来了一些问题和局限性。首先,回表操作需要访问两次索引,增加了IO开销和CPU消耗,对查询性能有一定的影响。特别是在高并发、大数据量的情况下,回表可能成为性能瓶颈。其次,由于回表操作是基于物理地址来获取数据,如果在回表过程中发生了数据修改(如DELETE、UPDATE),则可能会读取到不一致或错误的数据。

因此,在设计索引时,需要结合具体的业务场景和查询需求,选择合适的索引策略,以尽量减少回表的次数和影响,提高查询效率和性能。例如,可以通过使用覆盖索引来避免回表操作,覆盖索引是指索引包含了查询所需的所有字段的值,因此可以直接通过索引获取查询结果,而无需再去访问数据表。

4.如果一张表数据量级是千万级别以上的,要给这张表添加索引,需要怎么做呢?

答:建新表+建索引+导数据+废旧表(腾笼换鸟)

给表添加索引的时候,是会对表加锁的。 如果不谨慎操作可能出现生产事故的,比如过程中发生了数据修改(如DELETE、UPDATE),则可能会读取到不一致或错误的数据。可以参考以下方法:

  1. 先创建一张跟原表A数据结构相同的新表B;
  2. 在新表B添加需要加上的新索引;
  3. 把原表A数据导到新表B;
  4. rename新表B为原表的表名A,原表A换别的表名。

5.MySQL删除重复数据-解决线上数据库存在重复数据的问题?

0.目标:去掉同一字段下的重复数据或者仅仅留下一条

案例:

c 复制代码
CREATE TABLE `student` (

  `id` INT(11) NOT NULL AUTO_INCREMENT,

  `name` VARCHAR(20) DEFAULT NULL,

  `age` INT(11) DEFAULT NULL,

  PRIMARY KEY (`id`)

) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

INSERT INTO `student` (`id`, `name`, `age`) VALUES ('1', 'cat', 22);
INSERT INTO `student` (`id`, `name`, `age`) VALUES ('2', 'dog', 33);
INSERT INTO `student` (`id`, `name`, `age`) VALUES ('3', 'fish', 44);
INSERT INTO `student` (`id`, `name`, `age`) VALUES ('4', 'cat', 55);
INSERT INTO `student` (`id`, `name`, `age`) VALUES ('5', 'dog', 11);
INSERT INTO `student` (`id`, `name`, `age`) VALUES ('6', 'cat', 45);

SELECT * FROM student;

1.如何编写查看有重复记录的SQL?
公式:

java 复制代码
Select * From 表 Where 重复字段 In (Select 重复字段 From 表 Group By 重复字段 Having Count(1)>1)

案例:

c 复制代码
SELECT * FROM student WHERE NAME IN (SELECT NAME FROM student GROUP BY NAME HAVING COUNT(1)>1)

2.如何编写删除重复记录的SQL?
1)重复的数据全部删除

c 复制代码
DELETE FROM student WHERE NAME IN (
 SELECT t.NAME FROM ( SELECT NAME FROM student GROUP BY NAME HAVING COUNT( 1 ) > 1 ) t
)

2)重复的数据要求删除,仅保留一条

java 复制代码
# 第一步:select看看

SELECT * FROM student WHERE id NOT IN (
	SELECT t.id FROM( 
		SELECT MIN( id ) AS id FROM student GROUP BY `name` ) t 
		 )

#第二步:delete搞定

DELETE FROM student WHERE id NOT IN (  
	SELECT  t.id   FROM ( 
		SELECT MIN( id ) AS id FROM student GROUP BY `name` ) t  
		)

6.MySQL千万级数据分页的优化


1.优化前:

java 复制代码
/*偏移量为100,取30*/
SELECT SQL_NO_CACHE
  a.empno,
  a.ename,
  a.job,
  a.sal,
  b.deptno,
  b.dname 
FROM
  emp a 
  LEFT JOIN dept b 
    ON a.deptno = b.deptno 
ORDER BY a.id DESC 
LIMIT 100, 30;
java 复制代码
/*偏移量为4950000,取30*/
SELECT SQL_NO_CACHE
  a.empno,
  a.ename,
  a.job,
  a.sal,
  b.deptno,
  b.dname 
FROM
  emp a 
  LEFT JOIN dept b 
    ON a.deptno = b.deptno 
ORDER BY a.id DESC 
LIMIT 4950000, 30;

其实是因为limit后面的偏移量太大导致的。比如 limit 4950000,30。

这个等同于数据库要扫描出 4950030条数据,然后再丢弃前面的 49500000条数据返回剩下30条数据给用户,这种取法明显不合理。

比如:

limit 10000,20的意思扫描满足条件的10020行,扔掉前面的10000行,返回最后的20行,问题就在这里。 ​

假如 LIMIT 2000000, 30

扫描了200万+ 30行,慢的都堵死甚至会导致磁盘io 100%消耗。 ​ 但是: limit 30 这样的语句仅仅扫描30行。

2.说明:
因为limit后面的偏移量太大导致的。

比如像上面的 limit 4950000,30 ,这个等同于数据库要扫描出 4950030条数据,然后再丢弃前面的 4950000条数据,返回剩下30条数据给用户,这种取法明显不合理

分页操作通常会使用limit加上偏移量的办法实现,同时再加上合适的order by子句。
生产危险隐患:当偏移量非常大的时候,它会导致MySQL扫描大量不需要的行然后再抛弃掉。

3.解决方法

1)建索引

java 复制代码
CREATE INDEX idx_emp id ON emp(id);
CREATE INDEX idx_emp_depno ON emp(deptno);
CREATE INDEX idx dept depno ON dept(deptno);

已经有600W的数据下建索引耗时 参考:

4.优化
方法一:使用索引覆盖+子查询优化

因为我们有主键id,并且在上面建了索引,所以可以先在索引树中找到开始位置的 id值,再根据找到的id值查询行数据。

java 复制代码
/*子查询获取偏移100条的位置的id,在这个位置上往后取30*/
SELECT 
  a.empno,
  a.ename,
  a.job,
  a.sal,
  b.deptno,
  b.dname 
FROM
  emp a 
  LEFT JOIN dept b 
    ON a.deptno = b.deptno 
WHERE a.id >= (SELECT id FROM emp ORDER BY id LIMIT 100,1)
ORDER BY a.id LIMIT 30;
java 复制代码
/*子查询获取偏移4950000条的位置的id,在这个位置上往后取30*/
SELECT 
  a.empno,
  a.ename,
  a.job,
  a.sal,
  b.deptno,
  b.dname 
FROM
  emp a 
  LEFT JOIN dept b 
    ON a.deptno = b.deptno 
WHERE a.id >= (SELECT id FROM emp ORDER BY id LIMIT 4950000,1)
ORDER BY a.id LIMIT 30;

方法二:起始位置重定义

记住上次查找结果的主键位置,避免使用偏移量 offset。

java 复制代码
/*记住了上次的分页的最后一条数据的id是100,这边就直接跳过100,从101开始扫描表*/
SELECT 
  a.empno,
  a.ename,
  a.job,
  a.sal,
  b.deptno,
  b.dname 
FROM
  emp a 
  LEFT JOIN dept b 
    ON a.deptno = b.deptno 
WHERE a.id > 100 ORDER BY a.id LIMIT 30; 
java 复制代码
/*记住了上次的分页的最后一条数据的id是4950000,这边就直接跳过4950000,从4950001开始扫描表*/
SELECT 
  a.empno,
  a.ename,
  a.job,
  a.sal,
  b.deptno,
  b.dname 
FROM
  emp a 
  LEFT JOIN dept b 
    ON a.deptno = b.deptno 
WHERE a.id > 4950000 ORDER BY a.id LIMIT 30;

方法三:服务降级不让使用

阿里的方案:

配置limit的偏移量和获取数一个最大值,超过这个最大值,就返回空数据。因为按照业务来说,超过这个值你已经不是在分页了,而是在刷数据了,如果确认要找数据,应该输入合适条件来缩小范围,而不是一页一页分页。

客户请求request的时候 如果offset大于某个数值就先返回一个4xx的错误,避免黑客攻击or刷单行为呢,正常人,很少翻查10页以后的内容。

另外,该有的限流、降级也应该考虑进去。比如工具多线程调用,在短时间频率内5000次调用,可以使用计数服务判断并反馈用户调用过于频繁,直接给予断掉。

相关推荐
这孩子叫逆2 小时前
6. 什么是MySQL的事务?如何在Java中使用Connection接口管理事务?
数据库·mysql
掘根4 小时前
【网络】高级IO——poll版本TCP服务器
网络·数据库·sql·网络协议·tcp/ip·mysql·网络安全
Bear on Toilet5 小时前
初写MySQL四张表:(3/4)
数据库·mysql
无妄啊______5 小时前
mysql笔记9(子查询)
数据库·笔记·mysql
Looooking5 小时前
MySQL 中常用函数使用
数据库·mysql
island13145 小时前
从 InnoDB 到 Memory:MySQL 存储引擎的多样性
数据库·学习·mysql
ZZDICT6 小时前
MySQL 子查询
数据库·mysql
柳鲲鹏6 小时前
编译成功!QT/6.7.2/Creator编译Windows64 MySQL驱动(MinGW版)
开发语言·qt·mysql
一个很帅的帅哥6 小时前
实现浏览器的下拉加载功能(类似知乎)
开发语言·javascript·mysql·mongodb·node.js·vue·express
dbln7 小时前
MySQL之表的约束
数据库·mysql