数据库基本概念
什么是数据库
- 数据:描述事物的符号记录,是数据库存储的基本单位
- 数据库(DB):长期存储在计算机内、有组织、可共享的大量数据的集合
- 数据库管理系统(DBMS):负责数据定义、操纵、控制与维护的软件系统,如 MySQL、Oracle、PostgreSQL
- 数据库系统(DBS):由数据库、DBMS、应用程序与用户组成的整体,通常泛指以上概念的组合
关系模型核心概念
- 表(Relation):关系模型以二维表组织数据,一行一条记录、一列一个属性
- 行:表中的一条完整记录,又称元组(Tuple)
- 列:记录中的一个字段,又称属性(Attribute),列有唯一列名与数据类型
- 域:某列可取值的集合,即该属性的取值范围
- 主键:唯一标识一行记录的列或列组合,取值唯一且非空
- 候选键:能唯一标识记录的属性组,可能有多个,选其中之一作主键
- 外键:引用其他表主键的列,用于建立表间关联并约束数据完整性
- 复合键:由多列组合构成的键,常用于多条件联合唯一标识
- 关系表的三类完整性:实体完整性(主键非空且唯一)、参照完整性(外键必须存在或为 null)、用户定义完整性(列级业务规则)
- 冗余:同一数据在库中重复存储,破坏一致性且浪费空间,设计表结构的目标之一就是消除冗余
三大范式
范式(NF)是设计关系表结构、避免数据冗余与异常的原则规范,级别从低到高层层递进:满足高一级必然满足低一级。
第一范式(1NF)
- 核心要求:表中每个字段都不可再分,必须存储原子值
- 违反示例:地址存成"广东省广州市天河区"单个字段、爱好存成"篮球,足球"逗号拼接,都不是原子值
- 违反危害:无法按字段内的一部分查询、统计与排序
第二范式(2NF)
- 核心要求:在满足 1NF 的基础上,消除了非主属性对主键的部分依赖(部分依赖:非主键字段只依赖复合主键中的一部分)
- 违反示例:复合主键(学号, 课程号)下,学生姓名只依赖学号,就是部分依赖
- 违反危害:姓名重复存于多行导致冗余;改姓名需改多行;增删课程时还会引发修改异常、插入异常、删除异常
- 解决方式:拆成"学生表(学号, 姓名)"与"选课表(学号, 课程号, 成绩)"两张表
第三范式(3NF)
- 核心要求:在满足 2NF 的基础上,消除了非主属性对主键的传递依赖(传递依赖:字段 A 依赖主键,字段 B 又依赖 A,B 便通过 A 间接依赖主键)
- 违反示例:学生表(学号, 学院, 学院地址)中,学院地址依赖学院、学院依赖学号,即为传递依赖
- 违反危害:学院地址多行重复存储,改学院地址需同步多行
- 解决方式:拆成"学生表(学号, 学院 id)"与"学院表(学院 id, 学院, 学院地址)"两张表
补充(BCNF / 第 4、5 范式)
- BCNF(巴斯-科德范式):在 3NF 基础上要求每个决定因素都是候选键,消除主属性对候选键的部分依赖
- 第四范式(4NF):在 BCNF 基础上消除多值依赖,即表中不允许有多对多的属性组合
- 第五范式(5NF):消除连接依赖,把表拆到无法再无损分解为止,实践中极少用到
范式取舍
- 实际工程以满足 3NF 为主即可,BCNF 及以上会过度拆分表,反而增加 Join 成本
- 存在大量复杂查询的业务常反范式化:故意冗余部分字段(如订单表冗余商品名、价格快照),用空间换查询效率
- 面试答"范式"时先按 1NF → 2NF → 3NF 递进,补一句"BCNF 是 3NF 的增强",再谈反范式化
事务的 ACID 特性
- 事务:数据库执行的最小逻辑工作单元,由一条或多条 SQL 组成,要么全部成功要么全部失败
- 原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部不执行,不存在部分完成
- 一致性(Consistency):事务执行前后数据库都必须处于合法状态,不违反任何约束与业务规则
- 隔离性(Isolation):多个事务并发执行时互不干扰,每个事务看到的数据不受其他事务中间状态影响
- 持久性(Durability):事务一旦提交,对数据的修改就永久保存,即使系统崩溃也不会丢失
- 实现手段:原子性与持久性靠日志(Undo Log 回滚、Redo Log 重做)实现,隔离性靠锁 / MVCC 实现,一致性是最终目标
- 注意点:隔离级别越低性能越高但一致性风险越大,需根据业务权衡
并发控制与一致性问题
- 脏读:事务 A 读到事务 B 未提交的修改数据,B 回滚后 A 读到的就是错误数据
- 不可重复读:同一事务内两次读取同一行,因其他事务提交修改而读到不同值
- 幻读:同一事务内两次范围查询,因其他事务插入新行而返回不同数量的行
- 丢失更新:两个事务先后基于同一旧值修改,后提交者覆盖先提交者的修改
- 解决手段:加锁(悲观并发控制)或 MVCC 多版本并发控制(乐观并发控制)
隔离级别
- 读未提交(READ UNCOMMITTED):事务可读到其他事务未提交的数据,不解决任何一致性问题
- 读已提交(READ COMMITTED):只能读到已提交数据,解决脏读,但存在不可重复读,Oracle 默认
- 可重复读(REPEATABLE READ):同一事务内多次读取结果一致,解决脏读与不可重复读,但存在幻读,MySQL InnoDB 默认
- 可串行化(SERIALIZABLE):事务强制串行执行,解决全部一致性问题,但并发性能最差
- MySQL 的 InnoDB 在可重复读下通过间隙锁 / Next-Key Lock 机制解决了幻读
键与约束
PRIMARY KEY主键:唯一标识一行,约束非空且唯一,InnoDB 中也是聚簇索引的索引键UNIQUE唯一约束:保证列值不重复,允许一个 null(MySQL 中多个 null 不冲突)NOT NULL非空约束:禁止该列为空DEFAULT默认值约束:插入未指定时使用默认值CHECK检查约束:限制列值必须满足某表达式(MySQL 8.0.16 前被解析但不强制生效)FOREIGN KEY外键约束:保证参照完整性,但会牺牲写入性能,大厂常禁用外键改由应用层保证AUTO_INCREMENT自增:整数列自动递增,常用于主键
索引
- 索引:类似书的目录,加速查询的数据结构,以空间换时间,同时会拖慢写入
- 聚簇索引:叶子节点直接存整行数据,InnoDB 表只能有一个(主键索引)
- 非聚簇索引(二级索引):叶子节点存主键值,查到主键后再回表查数据
- 覆盖索引:查询所需字段都能从索引本身取到,无需回表,性能最优
- 联合索引:多列组合的索引,遵循最左前缀原则,如 (a, b, c) 可命中 a、a+b、a+b+c
- 唯一索引:索引列值不可重复,可用于快速判断存在性
- 索引失效场景:对索引列用函数或表达式、左模糊
like '%xx'、隐式类型转换、or链接非索引列、违反最左前缀 - 索引数据结构:B+ 树(MySQL 默认,树高矮、范围查询快)与哈希索引(等值查询极快但不支持范围)
日志
- Redo Log(重做日志):记录物理修改(页变化),用于崩溃后重放恢复,保证持久性
- Undo Log(回滚日志):记录数据旧版本,用于回滚事务与 MVCC 快照读取,保证原子性
- Binlog(二进制日志):记录逻辑 SQL,用于主从复制与数据恢复,属于 MySQL Server 层
- WAL(Write-Ahead Logging):先写日志、后落数据页的机制,将随机写变为顺序写,是保证崩溃安全性的核心
锁
- 共享锁(S 锁 / 读锁):多个事务可同时持有,加锁期间不允许其他事务写
- 排他锁(X 锁 / 写锁):只允许一个事务持有,期间其他事务不能读也不能写
- 悲观锁:先加锁再操作,靠数据库锁机制保证,适合写多读少、冲突频繁
- 乐观锁:不加锁,靠版本号 / CAS 先改后校验,冲突时重试,适合读多写少
- 表锁:直接锁整张表,开销小但并发度低
- 行锁:只锁涉及的行,并发度高但加锁开销大,InnoDB 支持
- 间隙锁:锁住范围但不锁记录本身,防止幻读
- 死锁:两个事务互相持有对方需要的锁,形成循环等待,数据库会检测并回滚其中一个事务
数据库架构与集群
- 主从复制:主库负责写、从库负责读,数据通过 Binlog 同步,实现读写分离与横向扩展
- 读写分离:写请求走主库、读请求走从库,降低主库压力,但存在主从延迟
- 分区:把一张大表按分区键拆成多个存储区(物理存储),查询时只扫描相关分区
- 分库分表:数据量大到单库单表无法支撑时,把一个库拆成多个库、一张表拆成多张表(垂直/水平拆分)
- 垂直拆分:按业务模块或字段冷热拆表,降低单表宽度
- 水平拆分:按分片键(如用户 id hash)把行分散到多张表,是最常用的扩展手段
- 中间件:分库分表场景下代理 SQL 路由的组件,如 ShardingSphere、MyCat
- 高可用:通过主从切换、集群、多副本保证数据库故障时服务不中断
缓存与数据库一致性
- 缓存:把热点数据放到内存(如 Redis),减少数据库查询压力,但会引入一致性问题
- Cache Aside(旁路缓存):读先查缓存、miss 再查库并回填;写先更新库、再删缓存,最常用策略
- 先删缓存再更新库:读并发的空隙可能把旧值写回缓存(缓存击穿变体),通常不推荐
- 双删策略:更新后延迟一段时间再删一次缓存,兜底并发下旧值回填问题
- 延迟双删:先删缓存 → 更新库 → 延迟(约 500ms)再删缓存,配合过期时间兜底
- 最终一致性:在无强一致要求时,允许短时间内数据不一致,最终通过过期、消息队列补偿等方式达成一致
扩展概念:视图、存储过程、触发器
- 视图:基于 SQL 查询结果创建的虚拟表,不存储数据,用于简化查询与权限控制
- 存储过程:预编译并保存在数据库中的一组 SQL,减少网络往返,但调试与迁移困难,现代项目较少使用
- 触发器:在表发生增删改时自动执行的 SQL 逻辑,常被业务代码隐式触发导致难以排查,也较少使用
- 游标:逐行遍历查询结果集的机制,适合需要逐行处理的场景(对大批量数据效率低,不推荐)
- SQL 注入:把用户输入拼进 SQL 导致结构被篡改的攻击,应使用预编译(参数化查询)规避
常用面试追问
以下内容属于进阶知识,通常面试时会被当作深挖点,此处仅列关键结论:
- MySQL 为什么用 B+ 树做索引而不是 B 树 / 红黑树:B+ 树矮(三层约可存两千万行)、叶子节点有序链表让范围查询快、非叶子节点只存键更省内存
- InnoDB 与 MyISAM 的区别:InnoDB 支持事务、行锁、外键、崩溃恢复;MyISAM 不支持事务只支持表锁,但查询略快
- 事务隔离级别与锁 / MVCC 的关系:读已提交与可重复读均依赖 MVCC,区别在于快照生成时机(前者每次读生成新快照,后者首次读生成快照)
- 为什么建议自增主键:B+ 树按主键顺序插入避免页分裂,随机主键(如 UUID)会引起大量页分裂、产生碎片
varchar与char的区别:varchar变长按实际长度存(省空间但 update 时可能页分裂),char定长(存取快,适合固定长度值)delete与truncate的区别:delete可回滚、逐行删、自增不重置;truncate不可回滚、直接重置表与自增- 左连接、右连接、内连接的区别:左连接以左表为基准补 null,右连接以右表为基准补 null,内连接只返回匹配行
group by与having的先后顺序:where先过滤原始行,group by再分组,having后过滤分组结果- 主从延迟的解决方案:半同步复制、并行复制(多线程回放 Binlog)、读写分离时读请求路由到主库、减少大事务
- 什么是慢查询:执行时间超过阈值(如
long_query_time默认 10s)的 SQL,通过EXPLAIN分析执行计划优化