试题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;
两个事务以相反顺序访问相同资源,互相等待对方释放锁,形成死锁。
死锁解决方案:
死锁检测与自动回滚:InnoDB会自动检测死锁,并回滚其中一个事务。
固定顺序访问资源:所有事务按照相同顺序访问表或行,避免循环等待。
合理设置超时时间:通过innodb_lock_wait_timeout参数设置锁等待超时时间。
尽量使用短事务:减少事务持有锁的时间,降低死锁概率。
高并发优化策略:
在实际应用中,合理使用锁机制可以显著提升数据库性能。以下是一些优化策略:
- 使用行锁代替表锁:在可能的情况下尽量使用行锁,提高并发性能。
- 创建合适的索引:确保查询语句能够命中索引,避免行锁退化为表锁。
- 尽量使用短事务:减少锁的持有时间,降低锁冲突概率。
- 固定顺序访问资源:避免不同事务以不同顺序访问相同资源,减少死锁发生。
- 合理使用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 索引?
首先介绍一款可以帮助我们理解数据结构的网站
一、数据结构
索引是一种特殊的数据结构。可以帮助我们快速查询到数据。
1、二叉树
二叉树是一种特殊的树,每个节点最多有两个子节点,值从左到右依次递增。

例如:当想查询值为11的数据时,如果没有索引要按顺序查找多次,有索引只查三次,加速了查询数据。
问:为什么索引的数据结构不用二叉树?
因为当值依次递增插入时,二叉树会退化成链表,对加快查询没有任何作用。

时间复杂度(代码执行的次数):O(N)
2、红黑树( 自平衡二叉查找树**)**
特点:
- 每个节点只能是红色或黑色。
- 跟节点必须是黑色。
- 红色的节点,它的叶节点只能是黑色。
- 从任一节点到其叶子节点的所有路径都包含相同数目的黑色节点。
当数据插入时,红黑树通过旋转和变色来达到平衡。这样就弥补了二叉树退化成链表的尴尬

问:为什么索引的数据结构不用红黑树?
因为当值依次递增插入时树的高度会变得特别高。就例如上图查询0007只用3次,那查询70000呢?几乎要用30000次。效率会变得特别低。
时间复杂度为:O(log2N)
3、B树( 多路平衡搜索树**)**
特点:
- 一个节点可以有多个元素
- 叶节点具有相同的深度,叶节点的指针为空
- 所有索引元素不重复
- 节点中的数据索引从左到右递增排列

问:为什么索引的数据结构不用B树?
虽然B树相对于红黑树,树的高度降低了,但是随着数据量的增多,树的高度还是会变得很高,效率会变得特别低。而且对范围查找也不方便。
4、B+树
特点:
非叶子节点不存储data,只存储索引(冗余),可以放多个索引。
叶子节点包含所有的索引字段
叶子节点用指针连接,提高区间访问性能(注意是单向指针。)

5、mysql B+树
mysql使用的是B+树,但是不完全是。对原B+树做了一些改造,例如:
- 叶子节点改成双向指针,提高范围查询效率。
- B树查询性能不稳定,有的直接在根节点就能查到,而有的在叶子节点才能查到。导致查询时快时慢。
6、hash
可以通过对某一值做hash运算,可以快速确定数据存放地址,查询到数据。但是缺点也很明显,不支持范围查询<>。几乎不用
问:为什么innodb表必须有主键?
因为innodb表的索引结构是B+树,而B+树是基于索引来存储数据的。所有的数据全部保存在B+树的叶子节点上。
问:如果没有主键会怎么样?
innodb引擎会查找并选择第一个没有null值的列,作为主键索引。如果没有,则会使用隐藏列作为主键。
问:为什么推荐使用整形自增主键而不用uuid?
优点:
节约空间
插入效率高(由于B+树遵循左小右大,所以自增插入数据总是在最右侧插入。而uuid则不一定,如果页16k已经写满了,那只能把页中的数据向后移,在空位中插入。频繁的移动分页会造成碎片,后续需要使用OPTIMIZE TABLE来进行碎片整理)
问:为什么非主键索引结构叶子节点存储的是主键值?
非主键索引存储主键值,是为了当数据变动时,不需要修改各非主键索引的值,只需修改主键索引叶子结点的数据即可。减少了重复操作,即提高性能。
二、Innodb页底层结构

分成三个部分
**页头:**包含前后页的指针
**页目录:**包含数据区(分组)的最小id值,方便通过指针查询到数据
**数据区:**用于存放数据,指针依次向下。
为什么要引入页目录?
比如:我要查询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条件后面=左边,也就是对字段进行算数运算或函数操作