精通 MySQL 面试题

试题1:几种常用mysql存储引擎的特点和适用场景?

1. InnoDB

核心特性: 支持完整ACID事务、行级锁、外键约束、崩溃恢复机制。
文件结构: .ibd文件(数据+索引)
索引类型: 聚簇索引(B+树)
**适用场景:**电商订单、金融交易、高并发读写系统

2. MyISAM

核心特性: 不支持事务、表级锁、读取速度快、支持全文索引。
文件结构: .frm(结构)+.MYD(数据)+.MYI(索引)
索引类型: 非聚簇索引(B+树)
**适用场景:**博客系统、论坛文章列表、静态数据查询

3. Memory

核心特性: 数据存储在内存中、读写极快、重启数据丢失。
存储方式: 内存存储,仅.frm文件存磁盘
索引类型: 哈希索引/B树索引
**适用场景:**会话数据、临时计算结果、缓存层

  • Archive引擎:采用高压缩比存储,仅支持插入和查询操作,适合日志归档和历史数据存储。
  • CSV引擎:数据以纯文本CSV格式存储,便于与其他系统进行数据交换。
  • Federated引擎:可以访问远程MySQL服务器上的表,实现分布式数据访问。

场景化选型指南:

实战案例解析:

  • 电商订单系统:必须使用InnoDB。订单创建、库存扣减、支付处理都需要事务保证一致性。行级锁能支持高并发秒杀场景。

  • 新闻资讯网站:适合MyISAM。文章发布后很少修改,但读取频率极高。MyISAM的COUNT(*)操作直接从元数据读取,比InnoDB扫描全表快得多。

  • 用户会话管理:Memory引擎是理想选择。会话数据生命周期短,重启丢失影响不大,但读写速度要求极高。

  • 系统操作日志:Archive引擎最合适。日志只增不删,需要高压缩比节省存储空间,查询频率极低。

性能优化与避坑指南:

InnoDB优化策略

**缓冲池配置:**innodb_buffer_pool_size设置为物理内存的50%-70%,这是最重要的性能参数。

**事务管理:**保持事务短小精悍,避免长事务占用锁资源。

**索引设计:**主键使用自增ID,避免聚簇索引页分裂;定期使用ANALYZE TABLE更新统计信息。

MyISAM维护技巧

定期优化: 频繁写入后会产生碎片,使用OPTIMIZE TABLE重建表:OPTIMIZE TABLE myisam_table;

**备份策略:**MyISAM不支持崩溃恢复,必须建立定期备份机制。

**并发控制:**设置low_priority_updates=ON降低写操作优先级,减少读阻塞。

Memory引擎注意事项

**内存限制:**表大小受max_heap_table_size控制(默认16MB),需根据数据量调整。

**数据类型:**不支持TEXT、BLOB等大字段,VARCHAR会转为CHAR浪费空间。

**持久化方案:**如需数据持久化,可配合InnoDB定期同步,或直接使用Redis。

试题2:说一下 MySQL 的事务?

ACID四大特性:

⚛️ 原子性 (Atomicity)

操作全成功或全失败。通过 Undo Log实现。当事务回滚时,MySQL会读取Undo Log中的记录,将数据恢复到事务开始前的状态。

🔗 一致性 (Consistency)

保证数据完整性。由原子性、持久性和隔离性共同保证,确保事务执行前后数据库状态合法。

🛡️ 隔离性 (Isolation)

事务间互不干扰。主要通过 MVCC(多版本并发控制)锁机制实现。MVCC允许读操作不阻塞写操作,而锁机制则用于处理写操作间的冲突。

💾 持久性 (Durability)

提交后永久保存。通过 Redo Log实现。采用WAL(Write-Ahead Logging)技术,先将修改记录到Redo Log,再写入内存。当系统崩溃时,重启后可通过Redo Log恢复已提交事务的数据。

事务隔离级别:

隔离级别越高,数据一致性越强,但并发性能可能越低。

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED 可能 可能 可能
READ COMMITTED 不可能 可能 可能
REPEATABLE READ 不可能 不可能 可能 (标准)
SERIALIZABLE 不可能 不可能 不可能

*注:MySQL InnoDB在REPEATABLE READ级别通过Next-Key Lock机制有效避免了幻读。

事务控制语句:

java 复制代码
-- 开启事务
START TRANSACTION;

-- 执行SQL操作
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- 提交事务
COMMIT;

-- 或回滚
ROLLBACK;

实战案例:银行转账

假设从用户A账户转账1000元到用户B账户,必须保证操作原子性。

sql 复制代码
START TRANSACTION;

-- 从A账户扣款
UPDATE accounts SET balance = balance - 1000 WHERE user_id = 'A';

-- 向B账户存款
UPDATE accounts SET balance = balance + 1000 WHERE user_id = 'B';

-- 记录日志
INSERT INTO transaction_log(from_user, to_user, amount, timestamp) VALUES('A', 'B', 1000, NOW());

-- 提交事务
COMMIT;

试题3:说说 MySQL锁机制?

在数据库高并发场景下,锁是保证数据一致性和事务隔离性的核心机制。MySQL的锁机制主要分为表锁和行锁,它们分别对应不同的并发控制粒度,直接影响着数据库的性能和并发能力。

表锁锁定整张表,实现简单但并发度低;行锁只锁定特定行,并发度高但开销大。理解这两种锁的机制、适用场景以及InnoDB引擎如何实现行锁,是优化数据库性能的关键。

表锁(Table Lock)

锁定整张表,实现简单,开销小,但并发性能差。MyISAM引擎默认使用表锁,InnoDB在特定情况下也会退化为表锁。

工作模式:

读锁(共享锁):多个事务可以同时读取同一张表,但会阻塞写操作

写锁(排他锁):一个事务获得写锁后,其他事务的读写操作都会被阻塞

行锁(Row Lock)

锁定单行或多行数据,并发度高但开销大,可能引发死锁。InnoDB引擎的核心特性,基于索引实现。它只锁定需要操作的行,而不是整张表,这大大提高了数据库的并发性能。

InnoDB行锁的三种类型:

记录锁(Record Lock)

锁定索引记录本身,是最基本的行锁类型。当一个事务对一条记录加了X型记录锁后,其他事务既不能对该记录加S型记录锁,也不能加X型记录锁。

间隙锁(Gap Lock)

锁定索引记录之间的间隙,防止幻读现象的发生。只存在于可重复读隔离级别,间隙锁之间是兼容的。

临键锁(Next-Key Lock)

Record Lock + Gap Lock的组合,锁定一个范围并且锁定记录本身。InnoDB在可重复读隔离级别下默认使用临键锁防止幻读。

sql 复制代码
-- 行锁使用示例
START TRANSACTION;
-- 对id=1的记录加排他锁(X锁)
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 其他事务无法修改或锁定这行数据
UPDATE users SET name = 'new_name' WHERE id = 1;
COMMIT;

意向锁:表锁与行锁的协调者

InnoDB支持多粒度锁定,允许事务在行级和表级上同时存在锁。为了实现行锁和表锁的共存,InnoDB设计了意向锁(Intention Lock)

锁类型 级别 作用 兼容性
意向共享锁(IS) 表级 表示事务打算给表中的某些行加共享锁 与IS、IX兼容
意向排他锁(IX) 表级 表示事务打算给表中的某些行加排他锁 与IS、IX兼容
表共享锁(S) 表级 锁定整张表为只读 与IS兼容,与IX不兼容
表排他锁(X) 表级 锁定整张表为读写 与IS、IX都不兼容

意向锁是表级锁,其作用是快速判断表中是否存在行级锁。如果没有意向锁,加"独占表锁"时需要遍历表中所有记录,查看是否有记录存在独占锁,效率会很慢。

意向锁之间是相互兼容的,但意向锁与表级读写锁之间大部分都是不兼容的。这种设计使得InnoDB能够高效地管理不同粒度的锁。

锁的加锁与释放机制:

InnoDB采用两阶段锁定协议(two-phase locking protocol):在事务执行过程中,随时都可以执行加锁操作,但是只有在事务执行COMMIT或者ROLLBACK的时候才会释放锁,并且所有的锁是在同一时刻被释放。

隐式锁定与显式锁定

隐式锁定:对于常见的DML语句(如UPDATE、DELETE和INSERT),InnoDB会自动给相应的记录行加写锁;对于DDL语句(如ALTER、CREATE等),会自动给相应的表加表级锁。

显式锁定:通过特定语句手动加锁,如SELECT ... FOR UPDATE加行级写锁,SELECT ... LOCK IN SHARE MODE加行级读锁。

死锁问题与解决方案:

死锁(Deadlock)指两个或多个事务互相等待对方持有的锁,导致永远等待。行锁由于粒度细,更容易发生死锁,而表锁由于锁定整个表,不会出现死锁情况。

🔗 死锁示例场景

sql 复制代码
--事务1:
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
UPDATE accounts SET balance = balance - 50 WHERE user_id = 2;

--事务2:
START TRANSACTION;
UPDATE accounts SET balance = balance - 50 WHERE user_id = 2;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;

两个事务以相反顺序访问相同资源,互相等待对方释放锁,形成死锁。

死锁解决方案:

  1. 死锁检测与自动回滚:InnoDB会自动检测死锁,并回滚其中一个事务。

  2. 固定顺序访问资源:所有事务按照相同顺序访问表或行,避免循环等待。

  3. 合理设置超时时间:通过innodb_lock_wait_timeout参数设置锁等待超时时间。

  4. 尽量使用短事务:减少事务持有锁的时间,降低死锁概率。

高并发优化策略:

在实际应用中,合理使用锁机制可以显著提升数据库性能。以下是一些优化策略:

  • 使用行锁代替表锁:在可能的情况下尽量使用行锁,提高并发性能。
  • 创建合适的索引:确保查询语句能够命中索引,避免行锁退化为表锁。
  • 尽量使用短事务:减少锁的持有时间,降低锁冲突概率。
  • 固定顺序访问资源:避免不同事务以不同顺序访问相同资源,减少死锁发生。
  • 合理使用MVCC:对于只读查询,使用MVCC快照读避免加锁。
sql 复制代码
-- 优化示例:为WHERE列创建索引,避免全表锁
CREATE INDEX idx_user_id ON account(user_id);

-- 使用行锁的安全转账示例
START TRANSACTION;
-- 使用FOR UPDATE锁定需要修改的行
SELECT balance FROM accounts WHERE user_id = 'A' FOR UPDATE;
-- 检查余额、执行转账操作
UPDATE accounts SET balance = balance - 100 WHERE user_id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE user_id = 'B';
COMMIT;

试题四:说说 MySQL 索引?

首先介绍一款可以帮助我们理解数据结构的网站

https://www.cs.usfca.edu/~galles/visualization/Algorithms.htmlhttps://www.cs.usfca.edu/~galles/visualization/Algorithms.html

一、数据结构

索引是一种特殊的数据结构。可以帮助我们快速查询到数据。

1、二叉树

二叉树是一种特殊的树,每个节点最多有两个子节点,值从左到右依次递增。

例如:当想查询值为11的数据时,如果没有索引要按顺序查找多次,有索引只查三次,加速了查询数据。

问:为什么索引的数据结构不用二叉树?

因为当值依次递增插入时,二叉树会退化成链表,对加快查询没有任何作用。

时间复杂度(代码执行的次数):O(N)

2、红黑树( 自平衡二叉查找树**)**

特点:

  1. 每个节点只能是红色或黑色。
  2. 跟节点必须是黑色。
  3. 红色的节点,它的叶节点只能是黑色。
  4. 从任一节点到其叶子节点的所有路径都包含相同数目的黑色节点。

当数据插入时,红黑树通过旋转和变色来达到平衡。这样就弥补了二叉树退化成链表的尴尬

问:为什么索引的数据结构不用红黑树?

因为当值依次递增插入时树的高度会变得特别高。就例如上图查询0007只用3次,那查询70000呢?几乎要用30000次。效率会变得特别低。

时间复杂度为:O(log2N)

3、B树( 多路平衡搜索树**)**

特点:

  1. 一个节点可以有多个元素
  2. 叶节点具有相同的深度,叶节点的指针为空
  3. 所有索引元素不重复
  4. 节点中的数据索引从左到右递增排列

问:为什么索引的数据结构不用B树?

虽然B树相对于红黑树,树的高度降低了,但是随着数据量的增多,树的高度还是会变得很高,效率会变得特别低。而且对范围查找也不方便。

4、B+树

特点:

  1. 非叶子节点不存储data,只存储索引(冗余),可以放多个索引。

  2. 叶子节点包含所有的索引字段

  3. 叶子节点用指针连接,提高区间访问性能(注意是单向指针。)

5、mysql B+树

mysql使用的是B+树,但是不完全是。对原B+树做了一些改造,例如:

  1. 叶子节点改成双向指针,提高范围查询效率。
  2. B树查询性能不稳定,有的直接在根节点就能查到,而有的在叶子节点才能查到。导致查询时快时慢。
6、hash

可以通过对某一值做hash运算,可以快速确定数据存放地址,查询到数据。但是缺点也很明显,不支持范围查询<>。几乎不用

问:为什么innodb表必须有主键?

因为innodb表的索引结构是B+树,而B+树是基于索引来存储数据的。所有的数据全部保存在B+树的叶子节点上。

问:如果没有主键会怎么样?

innodb引擎会查找并选择第一个没有null值的列,作为主键索引。如果没有,则会使用隐藏列作为主键。

问:为什么推荐使用整形自增主键而不用uuid?

优点:

节约空间

插入效率高(由于B+树遵循左小右大,所以自增插入数据总是在最右侧插入。而uuid则不一定,如果页16k已经写满了,那只能把页中的数据向后移,在空位中插入。频繁的移动分页会造成碎片,后续需要使用OPTIMIZE TABLE来进行碎片整理)

问:为什么非主键索引结构叶子节点存储的是主键值?

非主键索引存储主键值,是为了当数据变动时,不需要修改各非主键索引的值,只需修改主键索引叶子结点的数据即可。减少了重复操作,即提高性能。

二、Innodb页底层结构

分成三个部分

  1. **页头:**包含前后页的指针

  2. **页目录:**包含数据区(分组)的最小id值,方便通过指针查询到数据

  3. **数据区:**用于存放数据,指针依次向下。

为什么要引入页目录?

比如:我要查询id为4的数据,需要从上到下查询4次。把数据区的数据分组,页目录存储每组数据最小的id值。那么我只需要通过目录3,查询2次即可。提高了查询效率。

当一页存满之后,数据会存放到下一页,通过双向指针连接,如图:

**当数据越存越多,最终多个页组成了链表。**例如:我要查询1000,如果从第1页开始查询,一页一页遍历效率就太慢了(这就是全表扫描)

那怎么办?当然还是用类似页目录的方式了:如图:

到这里就可以大致看出来了,这个结构就是上面说的B+树。每一页即B+树的一个节点

三、主键索引底层结构

问:索引文件对应系统位置在哪?

在mysql目录的data目录下某个数据库名的文件夹下

myisam引擎:包含frm(结构文件)、MYD(data数据文件)、MYI(index索引文件)

innodb引擎包含frm(结构文件)、idb(索引+数据文件)

innodb索引文件和数据文件在一起的(聚集索引)

此处可以看出innodb比myisam引擎少一次磁盘io操作(不需要再去MYD文件取数据),可以说性能好一些。

四、组合索引结构

explain

主要关注type字段和row字段

**type字段值包括:**const、eq_ref、ref、range、index、all

row表示扫描的行数、尽量越少越好。关联查询时使用小表驱动大表的方式

索引失效

使用!=、is null、or关键字

使用like时%加在左边

组合索引没有满足最左匹配原则

in的数量太多,也可能导致索引失效

where条件后面=左边,也就是对字段进行算数运算或函数操作

相关推荐
上海云盾-小余2 小时前
应用层漏洞实战防护:SQL 注入、XSS、文件上传漏洞一站式加固方案
数据库·sql·xss
罗湖老棍子2 小时前
打鼹鼠_二维树状数组(信息学奥赛一本通- P1540)(二维树状数组模版题)
数据结构·算法·树状数组·二维树状数组
_日拱一卒2 小时前
LeetCode:盛最多水的容器
数据结构·算法·leetcode
鸽芷咕2 小时前
从语法兼容到语义一致:深度解析金仓如何“无感”承接MySQL复杂业务
数据库·mysql
新缸中之脑2 小时前
AI智能体评估指南
数据库·人工智能·oracle
计算机安禾2 小时前
【数据结构与算法】第2篇:C语言核心机制回顾(一):指针、数组与结构体
c语言·开发语言·数据结构·c++·算法·链表·visual studio
add45a2 小时前
Python类型提示(Type Hints)详解
jvm·数据库·python
曾阿伦2 小时前
SQL 用法详解:从基础操作到进阶实战的全场景指南
数据库·sql
ew452182 小时前
【SQL】DISTINCT 与 GROUP BY 核心区别及常见误区、问题全梳理
sql·mysql