markdown复制## 1. 数据库创建基础与SQL入门
刚接触MySQL时,最让人兴奋的莫过于亲手创建第一个数据库。记得我最初在终端输入`CREATE DATABASE`命令时,那种"无中生有"的创造感至今难忘。不同于直接使用现成数据库,从零开始构建能让你真正理解数据存储的底层逻辑。
### 1.1 数据库创建的核心语法
创建数据库的基础命令看似简单:
```sql
CREATE DATABASE school_management
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
这里有几个关键点需要注意:
utf8mb4字符集支持完整的Unicode字符(包括emoji)_unicode_ci排序规则实现不区分大小写的排序- 数据库名避免使用MySQL保留字(如
order,group)
踩坑提醒:早期项目我曾用
latin1字符集,结果用户提交中文全部变成问号。建议所有新项目直接使用utf8mb4。
1.2 可视化工具 vs 命令行
新手常纠结该用Navicat这类GUI工具还是纯命令行。我的建议是:
- 开发阶段:用MySQL Workbench的ER图工具设计表结构
- 生产环境:必须掌握命令行操作(尤其批量执行SQL文件时)
- 日常调试:DBeaver的智能补全能节省大量时间
这里演示命令行创建表的完整流程:
bash复制# 连接MySQL(注意空格和引号)
mysql -u root -p"your_password"
# 选择刚创建的数据库
USE school_management;
# 建表语句(注意ENGINE和COMMENT的用法)
CREATE TABLE students (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL COMMENT '学生姓名',
gender ENUM('M','F') DEFAULT NULL,
birth_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. SQL语句的实战分类解析
2.1 DDL:数据库定义语言
建表时最易忽略的是索引设计。比如学生表经常需要按姓名查询:
sql复制-- 单列索引
ALTER TABLE students ADD INDEX idx_name (name);
-- 组合索引(注意最左匹配原则)
ALTER TABLE students ADD INDEX idx_gender_birth (gender, birth_date);
经验之谈:
AUTO_INCREMENT的坑在于,删除记录后ID不会重用。曾经有个项目累计删了10万条记录,导致ID值突破百万,后来改用UUID解决。
2.2 DML:数据操作语言
插入数据时推荐使用批处理:
sql复制-- 单条插入(避免!)
INSERT INTO students VALUES(1,'张三','M','2005-08-14',NOW());
-- 批量插入(效率高10倍以上)
INSERT INTO students (name, gender, birth_date) VALUES
('李四','F','2006-03-21'),
('王五','M','2005-11-30'),
('赵六',NULL,'2007-01-05');
更新操作要特别注意WHERE条件:
sql复制-- 危险操作(会更新整张表!)
UPDATE students SET gender = 'M';
-- 安全做法
UPDATE students SET gender = 'F' WHERE name LIKE '李%';
2.3 DQL:数据查询的艺术
基础查询看似简单,但实际项目中最复杂:
sql复制-- 常用查询模板
SELECT
s.id, s.name,
TIMESTAMPDIFF(YEAR, s.birth_date, CURDATE()) AS age,
COUNT(c.score) AS course_count
FROM students s
LEFT JOIN courses c ON s.id = c.student_id
WHERE s.gender = 'M'
GROUP BY s.id
HAVING age > 15
ORDER BY course_count DESC
LIMIT 10;
几个高阶技巧:
EXPLAIN分析查询性能- 避免
SELECT *只查必要字段 - 大数据表使用分页查询(
LIMIT 10000, 20效率极低)
3. 实战中的避坑指南
3.1 字符集导致的乱码问题
遇到过最棘手的bug是Java程序插入的中文在数据库显示为"???"。解决方案链:
- 确认MySQL服务端字符集
sql复制SHOW VARIABLES LIKE 'character_set%'; - JDBC连接字符串加参数:
code复制jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=UTF-8 - 确保应用服务器和客户端统一编码
3.2 事务与锁的陷阱
一次支付系统故障让我深刻理解事务隔离级别:
sql复制-- 典型丢失更新问题
START TRANSACTION;
SELECT balance FROM accounts WHERE user_id = 1; -- 读到100
-- 此时另一个事务也读取并修改了余额
UPDATE accounts SET balance = 150 WHERE user_id = 1;
COMMIT;
解决方案:
sql复制-- 使用悲观锁
SELECT balance FROM accounts WHERE user_id = 1 FOR UPDATE;
-- 或乐观锁(加version字段)
UPDATE accounts SET balance = 150, version = version + 1
WHERE user_id = 1 AND version = 2;
3.3 索引失效的常见场景
通过慢查询日志发现这些"索引杀手":
- 对字段使用函数:
WHERE YEAR(create_time) = 2023 - 隐式类型转换:
WHERE user_id = '100'(user_id是INT) - 模糊查询通配符在前:
WHERE name LIKE '%张' - 不符合最左前缀的联合索引
4. 性能优化实战记录
4.1 EXPLAIN执行计划详解
分析这个查询:
sql复制EXPLAIN SELECT * FROM orders WHERE user_id = 100 AND status = 'PAID';
关键指标解读:
type:ALL(全表扫描)→ 需优化为range或refkey:实际使用的索引rows:预估扫描行数Extra:Using filesort/Using temporary最危险
4.2 分库分表实战策略
当单表超过500万行时考虑拆分:
- 水平拆分:按user_id哈希分到不同表
sql复制-- 分表查询示例 SELECT * FROM orders_1 WHERE user_id = 100 UNION ALL SELECT * FROM orders_2 WHERE user_id = 100; - 垂直拆分:将大字段拆到单独表
- 使用ShardingSphere等中间件
4.3 缓存与数据库一致性
双写问题解决方案对比:
- 先更新数据库再删缓存(推荐)
- 延迟双删(解决删除缓存失败)
- 订阅binlog(如Canal)
缓存击穿防护代码示例:
java复制public Student getStudent(Long id) {
// 1. 先查缓存
Student stu = cache.get(id);
if (stu == null) {
// 2. 获取分布式锁
if (lock.tryLock()) {
try {
// 3. 二次检查缓存
stu = cache.get(id);
if (stu == null) {
// 4. 查数据库
stu = db.query(id);
// 5. 回填缓存
cache.set(id, stu, 300);
}
} finally {
lock.unlock();
}
}
}
return stu;
}
5. 开发环境配置建议
5.1 Docker搭建MySQL集群
生产级开发环境配置:
yaml复制# docker-compose.yml
version: '3'
services:
mysql-master:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: masterpass
ports:
- "3306:3306"
volumes:
- ./master-data:/var/lib/mysql
mysql-slave:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: slavepass
ports:
- "3307:3306"
volumes:
- ./slave-data:/var/lib/mysql
depends_on:
- mysql-master
5.2 常用配置参数调优
my.cnf关键配置(8核16G服务器示例):
ini复制[mysqld]
innodb_buffer_pool_size = 12G # 总内存的70%
innodb_log_file_size = 2G
max_connections = 500
thread_cache_size = 100
table_open_cache = 4000
监控命令备忘:
sql复制-- 查看连接数
SHOW STATUS LIKE 'Threads%';
-- InnoDB状态
SHOW ENGINE INNODB STATUS;
-- 锁等待
SELECT * FROM performance_schema.events_waits_current;
6. 学习路径建议
根据我带新人的经验,推荐的学习顺序:
- 基础:CRUD → 事务 → 索引
- 进阶:执行计划 → 锁机制 → 分库分表
- 高阶:主从复制 → 集群部署 → 性能调优
必读资料:
- 《高性能MySQL》第4章索引、第6章查询优化
- MySQL官方手册的InnoDB引擎部分
- Percona博客的案例分析
最后分享一个排查慢查询的完整流程:
- 开启慢查询日志
- 用pt-query-digest分析
- EXPLAIN定位问题
- 添加适当索引
- 重写复杂查询
- 验证QPS提升效果
code复制