一、业务场景驱动表设计:电商订单系统案例
场景背景
某电商平台需设计用户、商品、订单模块,要求支持以下操作:
- 用户高频查询最新订单
- 按商品分类+价格区间筛选商品
- 统计每日订单金额
建表实战
-- 用户表(反范式设计存储常用信息)
CREATE TABLE users (
user_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(32) NOT NULL COMMENT '登录账号',
hashed_password CHAR(64) NOT NULL COMMENT 'SHA256加密',
realname VARCHAR(20) NOT NULL,
age TINYINT UNSIGNED DEFAULT 0,
last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_username(username) -- 高频登录字段索引
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 商品表(大字段分离优化)
CREATE TABLE products (
product_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
category_id SMALLINT UNSIGNED NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock MEDIUMINT UNSIGNED DEFAULT 0,
detail TEXT, -- 单独存储到product_details表更佳
INDEX idx_category_price(category_id, price) -- 联合索引
);
-- 订单表(垂直分表设计)
CREATE TABLE orders (
order_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
total_amount DECIMAL(12,2) NOT NULL,
status ENUM('pending','paid','shipped','completed') NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_status(user_id, status), -- 覆盖索引
INDEX idx_created(created_at)
);
-- 订单明细表(水平分表预备)
CREATE TABLE order_items (
item_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT UNSIGNED NOT NULL,
product_id INT UNSIGNED NOT NULL,
quantity SMALLINT UNSIGNED NOT NULL,
INDEX idx_order_product(order_id, product_id)
);
设计要点
- 数值类型优化:
- TINYINT存储年龄(0-255)
- DECIMAL精确存储金额
- BIGINT应对订单号的增长需求
- 字段长度控制:
- 手机号使用CHAR(11)固定长度
- 地址信息使用VARCHAR(255)动态分配
- 时间类型选择:
- TIMESTAMP记录最后登录时间(自动时区转换)
- DATETIME存储订单创建时间(范围更大)
二、索引设计的艺术:速度与空间的平衡
索引创建原则
-- 联合索引最左匹配
ALTER TABLE products ADD INDEX idx_category_price_status(category_id, price, stock);
-- 前缀索引优化长文本
ALTER TABLE products ADD INDEX idx_title_prefix(title(20));
-- 函数索引(MySQL 8.0+)
CREATE INDEX idx_month ON orders((MONTH(created_at)));
索引优化场景
- 订单查询优化
-- 低效查询
SELECT * FROM orders WHERE user_id = 1001 ORDER BY created_at DESC LIMIT 10;
-- 优化方案:建立覆盖索引
ALTER TABLE orders ADD INDEX idx_user_created(user_id, created_at);
- 商品筛选优化
-- 联合索引避免回表
SELECT product_id, price FROM products
WHERE category_id = 5 AND price BETWEEN 100 AND 500;
索引避坑指南
- 避免在status等低区分度字段单独建索引
- LIKE '%keyword%'导致索引失效时,考虑全文索引
- 定期使用ANALYZE TABLE更新统计信息
三、高频面试题深度解析
基础篇
- CHAR与VARCHAR的核心区别?
- CHAR定长适合存储固定内容(如MD5值)
- VARCHAR变长节省空间但需要额外长度标识
- 索引失效的典型场景?
- 隐式类型转换:WHERE user_id = '123'(user_id为INT)
- 索引列参与运算:WHERE YEAR(create_time) = 2023
- 最左前缀缺失:已有索引(a,b,c),查询条件无a
进阶篇
3. 如何优化深度分页查询?
-- 传统分页
SELECT * FROM orders ORDER BY id LIMIT 100000, 20;
-- 优化方案
SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 20;
- 事务隔离级别如何选择?
- 读未提交:可能导致脏读
- 读已提交:Oracle默认级别
- 可重复读:MySQL默认,解决不可重复读
- 串行化:完全隔离但性能差
设计篇
5. 大表优化策略
- 垂直拆分:将text/blob字段分离到扩展表
- 水平拆分:按时间或哈希分表
- 归档历史数据:将6个月前订单转移到history库
- 如何设计一个点赞系统?
- 使用Redis做计数器缓存
- 异步落库防止高频写压力
- 合并写入减少磁盘IO
- 以下是一些 MySQL 常见面试题及答案:
基础概念
- 什么是 MySQL?
- 答案:MySQL 是一个开源的关系型数据库管理系统,它使用 SQL(结构化查询语言)作为查询语言。具有高性能、易用、安全、可扩展、支持多种存储引擎、支持事务处理等特点4。
- ACID 事务特性是什么? 4
- 原子性(Atomicity) :事务中的所有操作要么全部成功,要么全部失败。
- 一致性(Consistency) :事务执行前后,数据库从一个一致性状态转换为另一个一致性状态。
- 隔离性(Isolation) :并发执行的事务之间互不干扰,一个事务的中间状态对其他事务不可见。
- 持久性(Durability) :事务成功提交后,其对数据库的更改是永久性的。
存储引擎
- MyISAM 和 InnoDB 存储引擎的区别?
- 事务支持:MyISAM 不支持事务;InnoDB 支持事务,支持 ACID 事务特性4。
- 锁机制:MyISAM 是表级锁,并发写入性能较低;InnoDB 是行级锁,支持高并发写入4。
- 外键约束:MyISAM 不支持外键约束;InnoDB 支持外键约束4。
- 索引结构:MyISAM 使用非聚簇索引,索引和数据分开存储;InnoDB 使用聚簇索引,数据行和索引放在一起存储,主键索引就是聚簇索引4。
- 适用场景:MyISAM 适用于读取密集型应用,如一些只读的报表系统;InnoDB 适用于需要高并发写入、对数据一致性要求高的应用,如电商系统、银行系统等4。
索引
- 什么是索引?有哪些常见类型?
- 答案:索引是数据库中用于加速查询操作的数据结构。常见的索引类型有 B - Tree 索引和 Hash 索引4。B - Tree 索引是平衡多路查找树,适用于范围查询和排序,优点是查询效率稳定,缺点是插入和删除操作相对较慢;Hash 索引是基于哈希表的索引,适用于等值查询,优点是查询速度快,缺点是不支持范围查询和排序4。
- 如何选择合适的索引列?
- 答案:通常选择在 WHERE 子句、连接条件、ORDER BY 子句和 GROUP BY 子句中经常使用的列作为索引列。对于高基数(列中不同值的数量较多)的列,索引效果通常更好,但也要注意避免过度索引,因为索引会占用额外的存储空间,并且在数据插入、更新和删除时会增加维护成本。
事务与锁
- MySQL 中的事务隔离级别有哪些?它们之间有什么区别? 4
- 读未提交(Read Uncommitted) :允许读取未提交的数据,可能导致脏读、不可重复读和幻读。
- 读已提交(Read Committed) :只允许读取已提交的数据,可以避免脏读,但可能导致不可重复读和幻读。
- 可重复读(Repeatable Read) :在同一个事务中多次读取同一数据结果一致,可以避免脏读和不可重复读,但可能导致幻读,是 MySQL 的默认隔离级别。
- 串行化(Serializable) :事务串行执行,可以避免脏读、不可重复读和幻读,但性能较差。
- 什么是悲观锁和乐观锁? 4
- 悲观锁:假设数据在并发操作时很可能发生冲突,因此在数据操作前加锁,适用于写操作较多的场景,在 MySQL 中,可以通过行级锁实现。
- 乐观锁:假设数据在并发操作时不太可能发生冲突,因此不加锁,而是在提交时检查数据是否发生冲突,适用于读操作较多的场景,在 MySQL 中,可以通过版本号或时间戳实现。
查询优化
- 如何优化 MySQL 查询性能? 4
- 为常用查询字段创建合适的索引。
- 使用 EXPLAIN 分析查询执行计划,找出性能瓶颈。
- 优化 SQL 语句,避免使用子查询和全表扫描。
- 使用连接池减少连接开销。
- 调整数据库配置参数,如缓冲区大小、最大连接数等。
- 使用主从复制和读写分离分担查询压力。
- EXPLAIN 命令的作用是什么?它返回的信息有哪些?
- 答案:EXPLAIN 命令用于分析 SQL 查询语句的执行计划,帮助开发者了解 MySQL 如何执行查询,从而找出潜在的性能问题。它返回的信息包括查询的执行顺序、使用的索引、扫描的行数、是否使用临时表、是否进行文件排序等。通过分析这些信息,可以判断查询是否高效,并针对性地进行优化。
备份与恢复
- MySQL 有哪些备份方式?
- 逻辑备份:使用 mysqldump 工具将数据库中的数据和结构导出为 SQL 语句,适用于对数据一致性要求较高、恢复速度要求相对较低的场景,优点是备份文件可读性好,可在不同的 MySQL 版本和平台上恢复,缺点是备份和恢复速度相对较慢。
- 物理备份:直接复制数据库的物理文件,如 InnoDB 的表空间文件和日志文件等,适用于需要快速备份和恢复大量数据的场景,优点是备份和恢复速度快,缺点是备份文件不可读,且恢复时可能需要停机操作,对数据库的一致性要求较高。
- 如何实现 MySQL 数据库的主从复制?
- 答案:主从复制的基本原理是主服务器将数据库的更改记录在二进制日志(binlog)中,从服务器通过 I/O 线程读取主服务器的 binlog,并将其记录到中继日志(relay log)中,然后由 SQL 执行线程执行 relay log 中的语句,从而实现主从数据的同步。具体步骤包括配置主服务器的 binlog 日志格式和相关参数,配置从服务器的连接信息和复制参数,启动主从复制线程等。在实际应用中,还需要考虑主从服务器的网络拓扑、数据一致性检查、故障切换等问题。
结语
MySQL优化是持续的过程,需结合业务特点进行:
- OLTP系统优先考虑索引覆盖
- 分析型系统适当使用反范式设计
- 定期使用pt-query-digest分析慢日志
- 新版本优先考虑MySQL 8.0(CTE、窗口函数等新特性)