长大后想做什么?做回小孩!

0%

一次实习面试中关于数据库的问题

2020年6月5日,某互联网公司的 Java开发实习生 ,技术面二面。

面试官:自我介绍、项目提了一嘴、JDK 1.8、集合源码、TCP细节、HTTP和HTTPS、redis数据结构和常用方法、消息队列基础、Spring、SpringCloud、Maven、智力题、手撕算法(排列、DP)。。。

TCP 在之前的学习过程中有一个全面的知识整理:《面试?一篇就搞定!!史上最全TCP面试题 + 解答!》《面试必考问题一文搞定:HTTP和HTTPS?TLS的工作流程?》

redis 也简单整理过一点:《面试必问:Redis 5 种基础数据结构》、《缓存击穿、缓存穿透、缓存雪崩》、《redis 超时删除策略》

前面的一切都比较顺利,问的都不难没出什么大错。

突然,面试官话锋一转:你的简历上怎么没提到关系型数据库的东西呢?工作中一定要用到的啊!学校学过吧?你肯定也用过吧!

:emmmmm(因为数据库这方便确实没怎么复习过,心虚,就没往简历上写)

面试官:这样吧,时间也差不多了,简单问一下吧!从你的这个项目上问吧?

问:项目下单秒杀怎么做的并发优化?答:巴拉巴拉。。。

问:xxxxx?答:阿巴阿巴。。。

问:xxxxx?答:emmmm。。。

虽然最后介于前面回答的还不错,也还是勉强过关了。事后,痛定思痛决定还是要好好学习一下数据库的东西,就从查资料整理这次面试问题开始吧!!只是整理一个大概的学习方向,细节上还是要再去深入学习的。

正文

这个秒杀的业务是怎么保证不会超额售卖呢?

  1. 队列,将所有的请求有序入队,完全串行化处理。到达库存的阈值的时候停止消费队列中的消息,结束秒杀。虽然解决了超卖问题,但是非常容易出现消费速度远远小于请求入队的速度,最终导致内存被大量消耗。

  2. 悲观锁,最容易想到的,更新库存的操作进行加锁。代码里搞 Synchronized ,或者在 SQL 上用 for update,这么做的缺点也很明显,高并发场景下大量请求等待,导致系统连接数飙升等等。

    关于 for update:仅适用于 InnoDB 引擎,且必须在事务(BEGIN/COMMIT)内才能生效,预设是行级锁,但是只有当明确索引的时候才是行锁,否则会升级到表锁。相较于 Myisam 引擎,则只支持表级锁。

  3. 乐观锁,使用版本号的更新,或者先查库存,要进行减库存的时候,再查库存,前后库存一致才能修改,但是并发的时候只有一个线程可以修改成功。

  4. 缓存锁,修改某个数据时,把数据的 id 入缓存,其他线程再来修改本数据时,发现缓存中有这个 id,就阻止。好像可以用一些缓存组件的轻量级锁机制 CAS 实现减库存。(仅做记录,目前还没具体使用过,就不瞎说了)

  5. 分布式锁,(没用过。。)

看你简历上说用了存储过程,这样真的性能好么?

我认为这个是询问存储过程优缺点的:

优点:

  1. 生产环境下可以直接修改存储过程的方式修改业务逻辑或缺陷,并且不用重启服务。
  2. 存储过程经过编译之后存储,执行的时候回避一条一条 SQL 执行要快。(其实,大多数情况下并没有明显的速度优势)
  3. 减少了网络 IO 的影响,尤其是在高并发的情况下。存储过程全部在数据库服务器上执行,避免了业务代码中多条 SQL 分别到别的服务器上去执行所造成的时间消耗。
  4. 方便 DBA 优化,SQL 全部集中在一起。

缺点:

  1. 存储过程总体还是一个过程化的方法,如果业务逻辑很复杂,就比较难处理了。
  2. 不能像代码一样有非常方便的调试器。
  3. 不便于加缓存。
  4. 不便于数据库的分割,数据库分割之后,存储过程难以分清数据存储在哪个库中。
  5. 不支持集群,而且难以横向扩展。

一条慢sql、慢查询怎么排查原因?怎么优化?

总结自——云栖社区——《一条SQL语句执行得很慢的原因有哪些?》

分两种情况:

  1. 大多数情况正常,偶尔很慢:

    • 数据库更新频繁,redo log 很快被写满后数据库暂停其他操作,全身心去将数据同步到磁盘中,这种情况下可能会导致某条”不幸运”的 SQL 执行缓慢。
    • 抢不到锁,要执行的 SQL 需要涉及到的表、行,被加锁了,一直在等锁。(可以用 show processlist 命令来查看当前的状态)
  2. 数据量不变的情况下,一直很慢:

    • 条件字段没有索引,或者有索引但是没有用索引,从而进行了全表扫描。(可以用 EXPLAIN 关键字对查询语句进行估算分析,下文详细介绍)

      一部分会导致索引失效的情况:

      1. 如果条件中有 or ,即使条件字段有索引也不会命中。(这也是为什么尽量少用 or 的原因)只有当条件中只有 or 关键字,并且 or 前后的两个条件的列都有索引时,才能命中索引。
      2. like 语句是以 % 开头不能命中索引,只有当 % 不在开头的时候,索引才起效。
      3. 如果列类型是字符串,条件中的数据要使用引号引起来,否则不走索引。
      4. 联合索引要符合最左原则,否则不走索引。
      5. b+tree 索引 is null 不走索引,但是 is not null 走索引。
      6. 存储引擎不能使用索引中范围条件右边的列。
      7. 在索引列上做计算、函数、类型转换操作,会导致索引失效从而进行全表扫描。
    • 数据库选错索引(系统采样统计导致的“失误”),从而没有走索引,而是全表扫描。PS:这种情况可以使用 show index from table_name 来查询表中索引的相关信息,其中 Cardinality 字段表示索引的基数。如果系统统计的基数和实际差距很大的话,可以使用 analyze table table_name 重新进行统计分析。

    无论是不走索引,还是选错索引,在分析之后都可以使用 FORCE INDEX(index_name)、USE INDEX(index_name)、IGNORE INDEX(index_name) 关键字进行强制使用索引或者忽略索引。

具体内容还是要去看原作者的文章,这里只是笼统地总结一下。

主键索引和非主键索引有什么区别?

主键索引存放的值是整行数据,而非主键索引上存放的是主键的值,所以不难理解上学的时候教材上说:MySQL 的非主键索引是一种二级索引,主键索引是一种聚簇索引。

联合索引只用其中一部分字段能否命中?

联合索引是由多个字段组成的索引,查询时只使用联合索引的一个字段,如果这个字段在联合索引的所有字段的第一个(最左),那就会用到索引,否则就无法使用到索引。

建立原则:例如,a、b两个字段,如果经常用到 a 条件或者 a+b 条件去查询,并且很少单独使用 b 条件查询,那么就可以建立 a,b 联合索引。反之,如果 a、b 分别经常独立被用作查询条件,那就需要分别建立单列索引。尽量选择查询中过滤性最好的字段,而且字段顺序越靠前越好。

MySQL 索引种类

  1. 按数据结构分

    • b+tree 索引 O(logn) 重要!
    • hash 索引 重要!
      • 仅仅满足”=”、”in”、”<=>” 查询,不能使用范围查询。
      • hash 索引查找效率非常高,不需要像 b+tree 一样从根到目标节点。
      • 只有 memory 存储引擎显示支持 hash 索引。
    • FULLTEST 索引
    • r-tree 索引

    这篇文章非常的嗯——> 《MySQL索引背后的数据结构及算法原理 》

  2. 按物理存储分

    • 聚簇索引
    • 非聚簇索引
  3. 从逻辑形式分

    • 主键索引:特殊的唯一索引,不能为空
    • 单列索引
    • 联合索引
    • 唯一索引
    • 空间索引

建立索引的字段怎么选择的?

  1. 频繁作为查询条件的字段(where 后面经常出现的)。。。废话。。
  2. 唯一性太差的字段不适合单独建立索引,即使频繁作为查询条件。
  3. 更新非常频繁的字段不适合创建索引。
  4. 联合索引见上文。

实际工程中,还是要具体问题具体分析的。。。感觉这个问题很水

怎么知道索引是否命中?

部分文字引用自: 《技术指南网》

可以使用 EXPLAIN 语句 ;来估算索引的使用情况。

txbwVg.png

student 表,name 字段建立索引 name_idx

1
2
3
4
5
6
7
8
9
10
11
12
13
EXPLAIN SELECT
*
FROM
student
WHERE
age > (
SELECT
age
FROM
student
WHERE
NAME = '李四'
)

查找所有年龄大于’李四’的学生信息,结果:

txO2sP.png

字段解释:

  1. id:id 越大执行优先级越高,相同则执行顺序从上到下。
  2. select_type:表示每个子句的类型。
    • SIMPLE:简单语句。
    • PRIMARY:如果存在子查询,则最外层语句标记为主语句。
    • UNION:UNION关键字中第二个或后面的语句。
    • DEPENDENT UNION:UNION中的第二个或后面的SELECT语句,取决于外面的查询 。
    • UNION RESULT:UNION的结果)。
    • SUBQUERY:子查询中的第一个语句。
    • DEPENDENT SUBQUERY:子查询中的第一个语句,取决于外层的查询。
    • DERIVED:派生表的语句,FROM 子句的子查询。
    • UNCACHEABLE SUBQUERY:一个子查询的结果不能被缓存,必须重新评估外链接的第一行。
  3. table:表示语句是关于那张表的,有时会是派生表 derivedX ,这里的 X 表示产生派生表的语句的 id 。
  4. type:表示语句在表中查找的方式,又称“访问类型”:ALL、index、range、ref、eq_ref、const、system、NULL(从左到右性能越来越好)
    • ALL:Full Table Scan,全表扫描。
    • index: Full Index Scan,遍历索引树。
    • range:检索给定范围的行,使用一个索引来选择行 。
    • ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值。
    • eq_ref : 类似 ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,例如:多表连接中使用 primary key 或者 unique key 作为关联条件。
    • const、system : 当 MySQL 对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。例如:将主键置于where列表中,MySQL 就能将该查询转换为一个常量,system 是 const 类型的特例,当查询的表只有一行的情况下,使用 system 。
    • NULL : MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如:从一个索引列里选取最小值可以通过单独索引查找完成。
  5. possible_keys: 指出 MySQL 能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但只是 ‘possible’ 所以该索引不一定被查询使用。
  6. key:表示 MySQL 实际决定使用的键(索引),如果没有选择索引,键是 NULL。如果想要强制 MySQL 使用或忽视 possible_keys 列中的索引,在查询中使用 FORCE INDEX(index_name)、USE INDEX(index_name)、IGNORE INDEX(index_name) 即可实现。
  7. key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)。不损失精确性的情况下,长度越短越好。
  8. ref:表示上述表的连接匹配条件,即 使用哪个列或常数与key一起从表中选择行。
  9. rows:表示查询所需记录需要读取的行数(根据表统计信息和索引选用情况估算出的值)。
  10. Extra: 该列包含MySQL解决查询的详细信息。
    • Using where:列数据是仅仅使用了索引中的信息,而没有读取实际的表就返回的。这发生在对表的全部的请求列都是同一个索引的时候,表示 MySQL 服务器将在存储引擎检索行后再进行过滤。
    • Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询。
    • Using filesort:MySQL 中无法利用索引完成的排序操作称为“文件排序”。
    • Using join buffer:强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。
    • Impossible where:这个值强调了 where 语句会导致没有符合条件的行。
    • Select tables optimized away:这个值意味着仅通过使用索引、优化器,可能仅从聚合函数结果中返回一行。

PS:EXPLAIN 不包含触发器、存储过程、自定义函数对查询的影响情况,且不考虑 cache 和 MySQL 执行查询时所做的优化工作。而且结果的部分信息是估算值。只能解释 SELECT 操作,其他操作需要重写为 SELECT 后查看执行计划。


我对数据库的了解还是太少了,不能保证上面的回答是正确且合理的。还请赐教!只是整理一个大概的学习方向,细节上还是要再去深入学习的。

菜鸟本菜,不吝赐教,感激不尽!

更多题解源码和学习笔记:githubCSDNM1ng