第五篇:MySQL——数据库就是个存数据的“大仓库”

第五篇:MySQL——数据库就是个存数据的“大仓库” 别再背B树的定义了你只需要知道“为什么偏偏是B树而不是其他树”写在前面MySQL是面试的绝对C位。你几乎不可能遇到一场不考MySQL的Java面试。很多人觉得MySQL难是因为知识点太散索引、事务、隔离级别、锁、日志、优化……每个点都能问出花来。但它们的本质都很简单。MySQL就是一套管理数据的软件它要解决的核心问题只有三个怎么存得快→ 存储引擎怎么查得快→ 索引怎么不出乱子→ 事务锁把这三个问题搞明白MySQL就通了。一、存储引擎InnoDB为什么是默认的面试高频题“InnoDB和MyISAM有什么区别为什么现在都用InnoDB”1.1 核心区别就记两条对比维度InnoDBMyISAM事务支持不支持锁粒度行锁表锁外键支持不支持崩溃恢复支持有redo log不支持缓存缓存数据和索引只缓存索引划重点生产环境100%用InnoDB。MyISAM的优势只剩下“读操作快”因为表锁竞争小和“占用空间小”但在事务、并发、数据安全面前这些都是细枝末节。1.2 MyISAM还有存在的意义吗有。只读报表、数据仓库、日志表。读多写少且不需要事务的场景MyISAM的查询速度确实比InnoDB快因为它的索引和数据分开存储且表结构更轻量。二、索引重中之重索引是面试的头号重灾区。面试官会从“索引是什么”一路追问到“为什么B树”。2.1 索引的本质索引就是目录。你想在一本书里找“Java并发”这个知识点如果没有目录你得一页一页翻全表扫描。有了目录你直接翻到对应章节走索引。数据库的索引也是一样它是一种数据结构用来帮助快速定位数据。2.2 B树面试从头到尾就考这一个数据结构面试官“为什么MySQL用B树做索引而不是Hash、二叉树、B树”这是一个可以讲十分钟的问题而且每个追问都是递进的。第一问为什么不用哈希表Hash查找确实是O(1)快。但哈希不支持范围查询。你查WHERE age 18哈希索引直接废掉因为哈希只能做等值匹配和IN。任何范围、排序、模糊查询哈希都歇菜。第二问为什么不用二叉搜索树/红黑树数据在磁盘上。二叉树的每个节点只有一个分叉数据量大了比如1000万条树的高度可能有二三十层。每向下查一层就是一次磁盘I/O。查一条数据要读二三十次磁盘每次大约10ms加起来两三百毫秒这谁受得了而且磁盘IO是机械寻道随机IO极慢必须减少IO次数。红黑树虽然平衡但本质还是二叉树高度降不下来。第三问那B树呢B树是多叉树每个节点有多个分叉树矮了很多B树已经很好了但MySQL选了B树区别在于B树每个节点都存数据。非叶子节点既存索引又存数据所以每个节点能存的“目录项”很少树还是偏高。B树只有叶子节点存数据非叶子节点只存索引目录。因为非叶子节点变“轻”了一页16KB能存更多索引。同样2000万条数据B树只需要3层。再加一条B树的叶子节点用双向链表串起来所以做范围查询,,BETWEEN特别快——找到起点沿着链表往后走就行。B树的范围查询需要做中序遍历效率差很多。第四问那MySQL里B树一个节点多大16KB默认。这是InnoDB页的大小。你可以通过SHOW GLOBAL STATUS LIKE Innodb_page_size查看。2.3 聚簇索引 vs 非聚簇索引回表是什么聚簇索引Clustered IndexInnoDB的主键就是聚簇索引。叶子节点存的一整行数据。所以你通过主键SELECT * FROM user WHERE id 1直接拿到所有列不需要额外操作。非聚簇索引二级索引、辅助索引你在非主键字段上建的普通索引CREATE INDEX idx_name ON user(name)。叶子节点存的是主键值而不是整行数据。执行SELECT * FROM user WHERE name 张三先走name索引找到对应的主键id再拿着id去聚簇索引里查整行数据。这个过程叫“回表”。面试官追问“怎么避免回表”覆盖索引Covering Index。如果你查询的字段全都在索引里那就不需要回表了。-- 假设你有索引 (name, age)SELECTname,ageFROMuserWHEREname张三;因为name和age都在索引的叶子节点里MySQL直接从索引返回数据不用回表查聚簇索引。这就是“覆盖索引”。要不要把所有字段都塞进索引里千万别。索引本身要占磁盘和内存维护索引也有代价增删改要更新索引。覆盖索引只在高频查询的核心字段上考虑。2.4 最左前缀原则联合索引(a, b, c)查询条件必须从a开始否则索引失效。-- 走索引WHEREa1WHEREa1ANDb2WHEREa1ANDb2ANDc3-- 不走索引WHEREb2-- 跳过了aWHEREa1ANDc3-- 跳过了b为什么B树的索引是按(a, b, c)的顺序排序的。先排aa相同再排bb相同再排c。所以你不能跳过a直接用b来查——就像电话本按“姓名”排你不可能直接翻到所有叫“小明”的人。2.5 索引失效的经典场景面试必问你建的索引执行时不一定走。下面这些场景MySQL会放弃索引LIKE ‘%xxx’以通配符开头的模糊查询%在前面。因为B树按前缀排序你不知道开头是什么只能扫全表。LIKE xxx%可以走索引。对索引列做函数操作WHERE DATE(create_time) 2024-01-01索引失效。写成WHERE create_time 2024-01-01 AND create_time 2024-01-02才走索引。隐式类型转换WHERE phone 13800138000phone是varchar类型但传了数字MySQL会隐式转换成数字索引失效。传字符串13800138000才行。OR条件WHERE a 1 OR b 2如果a和b不是都有索引可能全表扫描MySQL优化器可能选择回表合并不一定完全失效但建议用UNION代替OR。! 或 不等于操作索引基本失效因为不等于没法走树的查找逻辑。IS NULL / IS NOT NULL视情况IS NULL在索引列上通常能走IS NOT NULL通常不走因为数据量太大。面试官追问“怎么看一条SQL有没有走索引”EXPLAIN命令看type和key列typeconst/eq_ref/ref/range→ 走了索引很好。typeALL→ 全表扫描完蛋。key列显示用了哪个索引。三、事务ACID和隔离级别MySQL的事务面试必考。核心就两条ACID是什么隔离级别有哪些3.1 ACID原子性Atomicity事务里的操作要么全成功要么全失败靠undo log实现回滚时把数据改回去。一致性Consistency事务前后数据状态是合法的比如转账前后总金额不变。隔离性Isolation多个事务同时执行互不干扰靠锁 MVCC实现。持久性Durability事务提交了数据就永久保存了靠redo log实现。面试官不会让你背ACID全称他真正想问的是隔离级别以及隔离级别怎么实现的。3.2 四种隔离级别隔离级别脏读不可重复读幻读读未提交Read Uncommitted可能可能可能读已提交Read Committed不会可能可能可重复读Repeatable ReadMySQL默认不会不会可能InnoDB通过间隙锁基本避免串行化Serializable不会不会不会脏读读到别的事务还没提交的数据万一人家回滚了你就读到假数据了。不可重复读同一事务内两次读同一行数据结果不一样因为别的事务改了这条数据并提交了。幻读同一事务内两次SELECT COUNT(*)记录数不一样因为别的事务插入了新数据。3.3 隔离级别的实现——MVCC多版本并发控制MVCC是InnoDB的杀手锏。说白了就是每行数据存多个版本读的时候看你该看到哪个版本。每次事务开始时系统分配一个事务ID递增的。每行数据后面藏着两个隐藏列创建版本号和删除版本号。读数据时只读“创建版本号 ≤ 当前事务ID”且“删除版本号 当前事务ID或为null”的数据。“读已提交”和“可重复读”的区别就在生成ReadView的时机读已提交每次执行SELECT语句都重新生成一个ReadView所以能看到别的事务新提交的数据 → 不可重复读。可重复读第一次执行SELECT时生成ReadView整个事务都用这一个ReadView所以始终读到的是“事务开始时”的快照数据 → 重复读一致。这就是“MVCC”——读不加锁读写不冲突。四、锁行锁、表锁、间隙锁面试官问完隔离级别紧接着就问“可重复读级别下怎么防止幻读的”4.1 行锁和表锁表锁Table Lock锁整张表。MyISAM和InnoDB都支持但InnoDB更精细。行锁Row Lock只锁一行。InnoDB独有也是InnoDB比MyISAM高并发能力强的根本原因。4.2 间隙锁Gap Lock——防幻读的利器间隙锁是InnoDB独有的在可重复读级别下默认开启。行锁锁的是已存在的行。间隙锁锁的是**“间隙”**比如索引值在(10, 20)之间的空白区域。临键锁Next-Key Lock 行锁 间隙锁。为什么需要间隙锁防止幻读。假设你执行SELECT * FROM user WHERE age BETWEEN 10 AND 20 FOR UPDATEInnoDB不仅会锁住age在10-20之间的行还会锁住(10,20)这个间隙——防止其他事务在这个区间插入新数据。所以你在事务里多次查询结果始终一样不会出现“幻影行”。4.3 行锁是在索引上加的超级重要InnoDB的行锁是在索引上加的如果你的查询条件走了索引行锁只锁那一行。如果你的查询条件没走索引InnoDB找不到要锁哪行就只能给每一行都加锁——效果等价于锁表性能急剧下降。这也就是为什么索引失效不仅影响查询速度还影响并发性能。五、日志体系redo log、undo log、binlogMySQL的日志有三剑客各有各的用处。5.1 redo log重做日志——保证持久性物理日志记录的是“在XXX数据页上做了XXX修改”。作用内存里的数据还没来得及刷盘MySQL突然宕机了。重启后MySQL读redo log把没来得及写盘的数据重做一遍。数据不丢。环形写入redo log是固定大小的文件写满了就覆盖旧的但会检查对应数据是否已刷盘确保安全。5.2 undo log回滚日志——保证原子性逻辑日志记录的是“怎么把数据改回去”比如你执行了UPDATEundo log记录反向的UPDATE。作用事务执行到一半失败了执行ROLLBACK靠undo log把数据恢复成修改前的样子。另外MVCC的多版本数据也是存在undo log里的。5.3 binlog归档日志——主从复制的命脉逻辑日志记录的是“SQL语句”或“行变更”。作用主从复制、数据恢复。redolog和binlog的区别redolog是InnoDB自己用的崩溃恢复binlog是MySQL Server层用的主从同步。所以即使你用MyISAM引擎也有binlog但没有redolog。5.4 两阶段提交保证redolog和binlog一致这是一个高频面试题。场景一条UPDATE语句执行完redolog写好了binlog还没来得及写MySQL宕机了。重启后redolog说“改了”binlog说“没改”主从同步就乱了。解决方案两阶段提交。prepare阶段redolog写入“准备”状态数据写入内存。commit阶段binlog写入成功redolog改为“提交”状态事务真正提交。如果MySQL在prepare和commit之间宕机重启后会检查binlog是否完整完整就提交不完整就回滚。六、SQL优化实际开发最重要面试官一定会问“你们线上的慢SQL怎么优化的”标准流程你按这个说稳开启慢查询日志slow_query_log ONlong_query_time 2超过2秒记下来。拿到慢SQL语句。用EXPLAIN看执行计划type是ALL还是index还是rangeALL说明全表扫描必须改。possible_keys可能用的索引。key实际用的索引如果是NULL说明没走索引。rows预估扫描行数越大越慢。Extra有没有Using filesort文件排序、Using temporary临时表有的话赶紧优化。针对性优化没走索引 → 建索引。走了索引但rows大 → 看看是不是索引区分度太低比如性别换个字段。有文件排序 → 排序字段加索引。有临时表 → 可能是GROUP BY或DISTINCT优化SQL逻辑。七、分库分表大厂面试必问数据量大了一张表扛不住了怎么办分库分表。7.1 垂直分库 vs 水平分表垂直分库按业务模块拆比如订单库、用户库、商品库。本质是微服务化的数据层落地。水平分表同一个表数据量太大比如订单表有1亿行按某个字段比如user_id取模拆到多个表里order_0、order_1……order_15。7.2 分库分表带来的问题面试核心考点跨库事务原来在一个库里一个事务能搞定现在要跨多个库只能用分布式事务Seata、TCC、事务消息。分布式ID不能再用MySQL自增ID了多个表自增会重复。用雪花算法Snowflake或美团Leaf。跨库JOIN原来JOIN两张表很简单现在两张表在不同库没法JOIN。要么数据冗余宽表要么代码里多次查询组装。分页查询LIMIT 10 OFFSET 100在单表好办多表就要把所有表的前110条取出来合并排序再取第100-110条很复杂。所以分库分表后分页查询要改成**“业务兜底”**比如只支持按时间下翻不支持随意跳页。7.3 常见中间件ShardingSphere-JDBC轻量级在应用层做分片国内用得最多。MyCat独立部署的中间件相当于一个代理层。八、面试官真正想听什么场景1“你们SQL优化怎么做的”普通回答“建索引看Explain。”加分回答“我们有个报表查询一开始20多秒EXPLAIN看到typeALL。我一看WHERE条件有create_time范围查询和user_id等值查询就建了个联合索引(create_time, user_id)但发现create_time区分度太差每天的数据太多。后来改成只查最近7天 按月分表再对user_id单独建索引查询时间从20秒降到了200ms。”场景2“MySQL怎么保证数据不丢的”普通回答“redolog。”加分回答“刷盘策略很关键。innodb_flush_log_at_trx_commit这个参数默认是1每次提交都刷盘性能低但最安全。线上金融类系统必须是1。如果设成2是每秒刷一次性能高但可能丢1秒的数据。我们普通业务系统设成2也能接受。”场景3“InnoDB和MyISAM你们怎么选的”加分回答体现思考“除了历史遗留系统新项目全部用InnoDB。唯一一个例外是日志表因为日志只插入不修改且不需要事务我们用MyISAM查询速度确实快一点。但后来还是改成了InnoDB因为MyISAM不支持崩溃恢复日志丢了也很麻烦。”小结核心概念大白话面试考点InnoDB vs MyISAM支持事务行锁 vs 不支持线上只用InnoDBB树矮胖的多叉树为什么比红黑树/B树适合磁盘聚簇索引主键索引叶子存整行回表、覆盖索引最左前缀联合索引必须从左开始索引失效场景MVCC每行存多个版本读已提交和可重复读的区别间隙锁锁间隙防幻读可重复读下默认开启redolog崩溃恢复两阶段提交保证一致binlog主从复制和redolog的区别分库分表数据太多就拆分布式ID、跨库JOIN、分页难题记住一句话MySQL面试不考背书考的是你遇到一个慢SQL怎么分析、怎么解决以及遇到高并发怎么用索引和事务来保证数据不出乱子。下一篇我们讲Redis——分布式共享Map到底有多强缓存三大杀手怎么破下期见。