mysql面试题
什么是SQL?
结构化查询语言 (Structured Query Language
) 简称 SQL
,是一种数据库查询语言。
作用:用于存取数据、查询、更新和管理关系数据库系统。
MySQL三大范式
第一范式(1NF)
要求数据库表中的每一列都是不可分割的拆分的原子数据项.即表中的每个字段值都是不可再分的最小数据单位。这一范式的核心目的是确保数据的原子性,避免数据冗余和复杂的数据结构,从而提高数据的一致性和完整性。
- 数据原子性:在1NF中,每个字段只能包含单一值,不能包含多个值。例如,在一个学生信息表中,学生的姓名、年龄、性别等字段都应是独立的列,不能将多个学生的姓名存储在同一列中。
- 消除重复组:1NF要求消除表中的重复组,即将具有相同属性的数据拆分为多个独立的列。例如,在一个订单表中,不能将多个商品信息存储在同一列中,而应将每个商品信息拆分为单独的列,如商品名称、商品数量、商品价格等。
- 数据完整性:1NF要求表中的每个字段都有明确的语义和数据类型,确保数据的完整性和准确性。例如,在一个员工信息表中,员工的身份证号字段应为固定长度的数字类型,不能包含其他非数字字符。
第二范式(2NF)
要求表中的非主属性完全依赖于主键,而不能存在部分依赖。2NF的主要目的是消除数据冗余和异常操作,提高数据的更新效率和一致性。
- 完全函数依赖:在2NF中,表中的每个非主属性必须完全依赖于主键,而不能仅依赖于主键的一部分。例如,在一个学生选课表中,主键为(学号,课程号),学生的姓名、性别等属性应完全依赖于学号,而不能仅依赖于课程号。
- 消除部分依赖:2NF要求消除表中的部分依赖关系,即将具有部分依赖的非主属性分离到新的表中。例如,在一个订单表中,如果订单号和商品号共同组成主键,而商品名称、商品价格等属性仅依赖于商品号,那么应将这些属性分离到一个新的商品表中,以消除部分依赖。
- 数据冗余减少:通过消除部分依赖,2NF能够有效减少数据冗余。例如,在一个学生选课表中,如果每个学生选修多门课程,而学生的姓名、性别等信息在表中重复存储,那么通过将学生信息分离到一个新的学生表中,可以减少数据冗余,提高数据存储效率。
第三范式(3NF)
要求表中的非主属性不仅完全依赖于主键,而且不能存在传递依赖。3NF的主要目的是消除数据的传递依赖,进一步减少数据冗余和异常操作,提高数据的更新效率和一致性。
- 传递函数依赖:在3NF中,表中的每个非主属性不能存在传递依赖关系,即不能通过一个非主属性来确定另一个非主属性。例如,在一个学生信息表中,学生的系名和系主任姓名存在传递依赖关系,因为系名可以确定系主任姓名,而系主任姓名不能直接依赖于学生学号。
- 消除传递依赖:3NF要求消除表中的传递依赖关系,即将具有传递依赖的非主属性分离到新的表中。例如,在一个学生信息表中,如果学生的系名和系主任姓名存在传递依赖,那么应将系名和系主任姓名分离到一个新的系表中,以消除传递依赖。
- 数据冗余最小化:通过消除传递依赖,3NF能够进一步减少数据冗余。例如,在一个学生信息表中,如果每个学生的系名和系主任姓名在表中重复存储,那么通过将系信息分离到一个新的系表中,可以最小化数据冗余,提高数据存储效率。
MyISAM 与 InnoDB 的区别
特性 | MyISAM | InnoDB |
---|---|---|
事务 | ❌ | ✅ |
外键 | ❌ | ✅ |
并发写入 | 表锁 | 行锁 |
锁机制 | 仅支持表级锁,同一时间只能有一个线程操作表,并发写入时容易出现锁等待。 | 支持行级锁,允许多个事务同时操作同一表的不同行,并发性能更高。 |
全文索引 | 传统支持,但性能不如InnoDB | 5.6+支持 |
存储结构 | .firm (表结构)、.MYD (数据)、.MYI (索引) |
.ibd 、ibdata ,根据innodb_file_per_table 参数决定 |
崩溃恢复 | ❌ | 通过重做日志(redo log )和回滚日志(undo log )实现崩溃回复,确保数据一致性 |
适用场景 | 读密写少(日志分析、静态内容)、不需要事务和外键、数据量小且恢复能力要求不高 | 写密集型(电商、社交)、需要事务、高并发 |
性能 | 简单查询可能略快(索引、数据分离、缓存命中率高) | 写性能显著优于MyISAM |
MySQL锁
按照锁粒度
表级锁(Table Lock
)
- 特点:锁定整张表,开销小,但并发度低
- 适用场景:MyISAM默认使用,适合读多写少的场景
1 | -- 手动锁定表(读锁) |
行级锁(Row Lock
)
- 特点:锁定单行或多行,开销大,但并发度高
- 适用场景:InnoDB默认使用,适合写密集型场景
1 | -- 在事务中使用行级锁(SELECT ... FOR UPDATE) |
按锁类型分类
共享锁(Shared Lock
, S 锁)
- 特点: 允许其他事务读取锁定的数据,但禁止写入
1 | START TRANSACTION; |
排他锁(Exclusive Lock
, X 锁)
- 特点:禁止其他事务读取或写入锁定的数据
1 | START TRANSACTION; |
意向锁(Intention Locks
)
- 作用:表示表的某个部分被锁定(行锁),用于表级锁和行级锁的兼容判断。
1 | -- 当执行SELECT ... FOR UPDATE时,InnoDB会自动添加意向排他锁(IX) |
间隙锁(Gap Locks
)
- 作用: 锁定索引记录之间的间隙,防止幻读(
Phantom Read
)
1 | -- 假设表中有id=1和id=3的记录 |
临键锁(Next-Key Locks
)
- 作用: 记录锁 + 间隙锁,锁定记录及其前面的间隙。
1 | -- 假设表中有id=5和id=10的记录 |
死锁示例
- 场景: 两个事务互相等待对方释放锁
1 | -- 事务1 |
解决方法:
- 按相同顺序访问资源。
- 设置超时参数(
innodb_lock_wait_timeout
) - 使用
SHOW ENGINE INNODB STATUS
分析死锁日志。
乐观锁与悲观锁
- 乐观锁(
Optimistic Locking
)
- 特点: 假设冲突很少发生,通过版本号控制
1
2
3
4
5
6
7-- 表结构:products(id, stock, version)
-- 事务1
SELECT stock, version FROM products WHERE id = 1; -- version=1
UPDATE products SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 1; -- 检查版本号是否变化
-- 事务2若同时更新,会因版本号不匹配而失败
- 悲观锁(
Pessimistic Locking
)
- 特点: 假设冲突一定会发生,直接加锁。
1
2
3
4START TRANSACTION;
SELECT stock FROM products WHERE id = 1 FOR UPDATE; -- 悲观锁
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;
查看锁状态
1 | -- 查看当前事务的锁 |
锁类型 | 粒度 | 适用场景 | 存储引擎 |
---|---|---|---|
表级锁 | 表 | 读多写少,MyISAM | MyISAM, InnoDB |
行级锁 | 行 | 写密集,InnoDB | InnoDB |
共享锁 | 行 / 表 | 并发读取 | InnoDB |
排他锁 | 行 / 表 | 写入操作 | InnoDB |
间隙锁 | 索引间隙 | 防止幻读 | InnoDB |
乐观锁 | 应用层 | 冲突少的场景 | 通用 |
事务
什么是数据库事务
数据库事务(Database Transaction
)是数据库管理系统(DBMS
)中一个不可分割的工作单元,它由一系列数据库操作(如查询、插入、更新、删除等)组成,这些操作要么全部成功执行,要么全部失败回滚,以保证数据的一致性和完整性。
事务的和新特性(ACID)
事务必须满足以下四个基本特性,即ACID 特性,这是事务可靠性的基石:
- 原子性:(
Atomicity
)- 定义: 事务中的所有操作被视为一个整体,要么全部完成,要么全部不执行。如果事务中途失败,已执行的操作会被回滚(
Rollback
) 到事务开始前的状态,仿佛从未执行过。 - 示例: 银行转账时,”从 A 账户扣钱 “和 “给 B 账户加钱” 是一个事务。若扣钱后系统崩溃,扣钱操作会回滚,确保 A、B 账户余额不变。
- 定义: 事务中的所有操作被视为一个整体,要么全部完成,要么全部不执行。如果事务中途失败,已执行的操作会被回滚(
- 一致性:(
Consistency
)- 定义: 事务执行前后,数据库的状态必须从一个合法状态转变为另一个合法状态,即数据需满足预设的约束(如主键唯一、外键关联、字段校验等)。
- 示例:转账前 A 有 1000 元、B 有 500 元,总额 1500 元;转账后 A 有 800 元、B 有 700 元,总额仍为 1500 元,符合 “总额不变” 的业务约束。
- 隔离性:(
Isolation
)- 定义:多个并发事务同时执行时,每个事务的操作应与其他事务相互隔离,互不干扰。即一个事务未完成的中间状态,对其他事务是不可见的。
- 作用:避免并发操作导致的数据不一致(如脏读、不可重复读、幻读等问题)。
- 示例:两个用户同时给同一账户转账,隔离性确保各自的转账操作不会互相干扰,最终结果正确。
- 持久性:(
Durability
)- 定义:一旦事务成功提交(
COMMIT
),其对数据的修改将永久保存在数据库中,即使后续发生系统崩溃、断电等故障,修改也不会丢失。 - 实现:DBMS 通常通过写入日志(如
InnoDB
的redo log
)并同步到磁盘来保证持久性。 - 示例:订单提交后,即使数据库服务器突然断电,重启后订单数据仍能恢复。
- 定义:一旦事务成功提交(
事务的生命周期
一个完整的事务通常包含以下阶段:
- 开始事务(
Start Transaction
):通过命令(如BEGIN
或START TRANSACTION
)标记事务的起始点。 - 执行操作:执行一系列 SQL 语句(如
INSERT
、UPDATE
、DELETE
等)。 - 提交事务(
Commit
):若所有操作成功,通过COMMIT
命令确认修改,使其永久生效。 - 回滚事务(
Rollback
):若中途发生错误,通过ROLLBACK
命令撤销所有已执行的操作,恢复到事务开始前的状态。
示例:
1 | -- 开始事务 |
事务的隔离级别
为了平衡隔离性和并发性能,数据库定义了不同的隔离级别(由低到高),MySQL
的 InnoDB
引擎支持以下四种:
隔离级别 | 说明 | 解决的问题 | 并发性能 |
---|---|---|---|
读未提交(Read Uncommitted ) |
一个事务可读取另一个未提交事务的修改。 | 无 | 最高 |
读已提交(Read Committed ) |
一个事务只能读取另一个已提交事务的修改。 | 脏读 | 较高 |
可重复读(Repeatable Read ) |
事务中多次读取同一数据,结果始终一致(MySQL InnoDB 默认级别) |
脏读、不可重复读 | 中等 |
串行化(Serializable ) |
事务串行执行(加表锁),完全隔离。 | 脏读、不可重复读、欢读 | 最低 |
注:隔离级别越高,数据一致性越好,但并发性能越低,需根据业务场景选择。
隔离级别与锁的关系
- 在 Read Uncommitted 级别下,读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突
- 在 Read Committed 级别下,读操作需要加共享锁,但是在语句执行完以后释放共享锁;
- 在 Repeatable Read 级别下,读操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁。
- SERIALIZABLE 是限制性最强的隔离级别,因为该级别锁定整个范围的键,并一直持有锁,直到事务完成。
事务的适用场景
事务适用于需要保证操作完整性的场景,例如:
- 金融交易(转账、支付):确保扣款和到账操作同时成功或失败。
- 订单处理:创建订单时,需同时扣减库存、生成订单记录,任何一步失败都需回滚。
- 数据同步:多表关联更新时,保证关联数据的一致性。
数据库事务通过 ACID 特性,解决了并发操作下的数据一致性问题,是保证业务可靠性的核心机制。理解事务的特性、生命周期和隔离级别,有助于设计更健壮的数据库操作逻辑,避免数据异常。
索引
什么是索引
- 索引是一种特殊的文件 (
InnoDB
数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。 - 索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用 B树及其变种 B+树。
- 更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。
索引的优缺点
- 索引的优点:可以大大加快数据的检索速度,这也是创建索引的最主要的原因。通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
- 索引的缺点:时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增 / 改 / 删的执行效率;空间方面:索引需要占物理空间。
索引有哪几种类型
- 主键索引:数据列不允许重复,不允许为 NULL,一个表只能有一个主键。
- 唯一索引:数据列不允许重复,允许为 NULL 值,一个表允许多个列创建唯一索引。可以通过
ALTER TABLE table_name ADD UNIQUE (column);
创建唯一索引可以通过ALTER TABLE table_name ADD UNIQUE (column1,column2);
创建唯一组合索引 - 普通索引:基本的索引类型,没有唯一性的限制,允许为 NULL 值。可以通过
ALTER TABLE table_name ADD INDEX index_name (column);
创建普通索引可以通过ALTER TABLE table_name ADD INDEX index_name (column1, column2, column3);
创建组合索引。 - 全文索引: 是目前搜索引擎使用的一种关键技术。可以通过 ALTER TABLE table_name ADD FULLTEXT (column); 创建全文索引
索引的数据结构
索引的数据结构和具体存储引擎的实现有关,在 MySQL
中使用较多的索引有 Hash
索引,B + 树索引等,而我们经常使用的 InnoDB
存储引擎的默认索引实现为:B + 树索引。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择 BTree 索引。
B 树索引
MySQL
通过存储引擎取数据,基本上 90% 的人用的就是InnoDB
了,按照实现方式分,InnoDB
的索引类型目前只有两种:BTREE(B 树)索引和 HASH 索引。B 树索引是MySQL
数据库中使用最频繁的索引类型,基本所有存储引擎都支持 BTree 索引。通常我们说的索引不出意外指的就是(B 树)索引(实际是用 B + 树实现的,因为在查看表索引时,MySQL
一律打印 BTREE,所以简称为 B 树索引)B+tree 性质
n 棵子 tree 的节点包含 n 个关键字,不用来保存数据而是保存数据的索引。
所有的叶子结点中包含了全部关键字的信息,即指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
所有的非终端结点可以看成是索引部分,结点中仅含其子数中的最大(或最小)关键字。
B+ 树中,数据对象的插入和删除仅在叶节点上进行。
B + 树有 2 个头指针,一个是树的根节点,一个是最小关键码的叶节点。哈希索引
简要说下,类似于数据结构中简单实现的HASH
表(散列表)一样,当我们在MySQL
中用哈希索引时,主要就是通过Hash
算法(常见的Hash
算法有直接定址法、平方取中法、折叠法、除数取余法、随机数法),将数据库字段数据转换成定长的Hash
值,与这条数据的行指针一并存入Hash
表的对应位置;如果发生Hash
碰撞(两个不同关键字的Hash
值相同),则在对应Hash
键下以链表形式存储。当然这只是简略模拟图。
索引常见算法
索引算法有 BTree 算法和 Hash 算法
- BTree算法
BTree
是最常用的 mysql 数据库索引算法,也是 mysql 默认的算法。因为它不仅可以被用在=
,>
,>=
,<
,<=
和between
这些比较操作符上,而且还可以用于like
操作符,只要它的查询条件是一个不以通配符开头的常量。 - Hash算法
Hash 索引只能用于对等比较,例如=
,<=>
(相当于 =)操作符。由于是一次定位数据,不像 BTree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次 IO 访问,所以检索效率远高于 BTree 索引。
索引设计的原则
- 适合索引的列是出现在 where 子句中的列,或者连接子句中指定的列
原理:索引的目的是快速定位数据行,因此应优先为查询条件(如WHERE、JOIN、ORDER BY)中的列创建索引
1 | -- 查询:筛选年龄>18且性别为女的用户,并按注册时间排序 |
注意:遵循最左前缀匹配原则,索引顺序应与查询条件一致(如WHERE a=1 AND b=2,索引应为(a,b))。
- 基数较小的类,索引效果较差,没有必要在此列建立索引
原理:基数(Cardinality
):列中不同值的数量。基数越低(如性别、状态码),索引效果越差
1 | -- 性别列只有'M'/'F'两个值,基数极低 |
- 使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间
原理:对长字符串(如VARCHAR(255)
)创建前缀索引,可显著减少索引体积,提高查询速度
1 | -- 对email列前10个字符创建索引 |
适用场景:URL、Email、长文本等字段
- 不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可
- 代价
- 索引占用磁盘空间,尤其对大表影响显著
- 写操作(
INSERT
/UPDATE
/DELETE
)时需更新所有相关索引,导致性能下降。
- 优化建议
- 合并索引:将多个单列索引合并为复合索引( 如(a)、(b)合并为(a,b) )
- 删除未使用的索引(通过
SHOW INDEX USAGE
或慢查询日志分析) - 覆盖索引: 仅包含查询所需列的索引,避免回表查询(如
SELECT a,b FROM t WHERE a=1
索引(a,b))
- 索引设计技巧
- 索引顺序优化
- 将高选择性列(如用户 ID、订单号)放在前面。
- 将范围查询列(如>、BETWEEN)放在后面
1
2-- 查询:WHERE user_id=123 AND order_date > '2023-01-01'
CREATE INDEX idx_user_date ON orders (user_id, order_date); -- 正确顺序 - 索引与排序
- 当查询包含
ORDER BY
时,确保索引顺序与排序顺序一致,避免文件排序(filesort
)
1
2-- 查询:WHERE category='book' ORDER BY price DESC
CREATE INDEX idx_category_price ON products (category, price DESC); - 当查询包含
- 索引与聚簇索引
- InnoDB 主键即聚簇索引,辅助索引会包含主键值,因此主键应尽量短小(如自增 ID)
- 避免使用长字符串作为主键,否则会导致辅助索引体积膨胀。
- 索引顺序优化
- 索引设计工具
- EXPLAIN 语句:分析查询执行计划,检查索引是否被使用。
1
EXPLAIN SELECT * FROM users WHERE age > 18;
- 索引监控工具
- MySQL Performance Schema:记录索引使用情况
- pt-query-digest:分析慢查询日志,识别索引缺失
主从分离
实现方式
- 主从复制原理
MySQL 通过 二进制日志(binlog
) 实现主从复制:- 主库(
Master
): 记录所有写操作到binlog
- 从库(
Slave
): 通过 I/O 线程读取主库binlog
, 并写入本地中继日志(relay log
) - 从库
SQL
线程: 执行中继日志中的SQL
语句 , 实现数据同步
- 主库(
- 配置步骤
主库配置:
1 | # my.cnf |
重启主库后,创建用户复制的用户
1 | CREATE USER 'repl_user'@'%' IDENTIFIED BY 'password'; |
从库配置:
1 | # my.cnf |
重启从库后,连接主库
1 | CHANGE MASTER TO |
作用
- 读写分离
- 主库: 处理写操作(
INSERT
/UPDATE
/DELETE
) - 从库: 处理读操作(
SELECT
) - 应用: 通过中间件(如
MyCat
、ShardingSphere
)或代码层实现读写路由
- 主库: 处理写操作(
- 负载均衡
- 将读请求分散到多个从库,减轻主库压力
- 适用于读多写少的场景(如电商商品页、社交平台信息流)
- 高可用性与灾备
- 从库可作为主库的热备,主库故障时可快速切换
- 通过异地部署从库,实现跨区域容灾
- 数据备份与分析
- 从库可用于离线备份,避免影响主库性能
- 从库可用于复杂查询(如报表统计),避免拖慢主库
优缺点
- 优点
- 提升性能:读写分离减少了锁竞争,提高并发处理能力
- 水平扩展:通过增加从库数量,轻松扩展读负载
- 高可用性:主库故障时可快速切换到从库,保障服务连续性
- 成本优化:可使用低配服务器作为从库,降低硬件成本
- 缺点
- 数据延迟:主从复制存在延迟(尤其在高并发写场景),可能导致读操作读到旧数据
- 复杂性增加:
- 架构维护复杂,需监控主从同步状态
- 事务跨主从时可能产生一致性问题
- 写入瓶颈:所有写操作仍集中在主库,无法通过增加从库解决写压力
- 复制风暴:当主库宕机,多个从库可能同时连接新主库,导致网络风暴
适用场景与优化建议
适用场景
- 读多写少的业务(如内容平台、论坛、报表系统)
- 需要高可用性的业务(如电商、支付系统)
- 数据分层处理(如主库处理实时交易,从库处理数据分析)
优化建议
- 减少延迟
- 主从服务器尽量部署在同意机房,降低网络延迟
- 优化主库性能,减少binlog生成延迟
- 适用半同步复制(
semi-sync replication
)确保事务提交后至少有一个从库接收binlog
- 读写路由策略
- 对实时性要求高的读操作(如订单详情)直接访问主库
- 对实时性要求低的读操作(如商品列表)访问从库
- 监控与报警
- 监控主从延迟(
Seconds_Behind_Master
) - 监控从库复制状态(
SHOW SLAVE STATUS
)
- 监控主从延迟(
- 高可用方案
- 结合
Keepalived
或 MHA(Master High Availability
)实现主从自动切换
- 结合
典型架构图
1 | +--------+ |
主从分离是
MySQL
架构优化的重要手段,通过读写分离和负载均衡显著提升性能,但引入了数据延迟和架构复杂度。在实际应用中,需根据业务场景权衡利弊,并结合高可用方案确保服务稳定性