简介:这份1000行MySQL学习笔记面向数据库初学者与想系统提升的开发者,从Windows服务启动、客户端连接管理,到库表创建与修改、存储引擎选择,再到视图、触发器、存储过程、事务控制、索引优化等高级特性,内容编排由浅入深,可作日常查阅的速查手册。资源共1个docx文档,压缩包约49KB,体积轻量却涵盖常用SQL语法、字段约束、表选项及分区等细节,适合离线阅读或打印对照。目前已有480人学习下载,笔记中通过大量命令示例清晰展示CREATE TABLE字段约束、ALTER TABLE修改结构、SHOW ENGINES查看引擎等具体操作,并对比InnoDB与MyISAM的适用场景,帮助读者理解不同存储引擎在事务与读取性能上的取舍。无论是准备数据库面试、快速复习,还是需要一份完整命令清单,这份笔记都能提供扎实参考价值。
1. “史上最全” MySQL 学习笔记到底在记什么:从建库到跑批的完整闭环
把一份 1000 行的 MySQL 学习笔记(无论是 .docx 还是 Markdown)拿在手里,第一反应通常是“先收藏再说”。但我看到太多人收藏完就再也不打开,真到写生产 SQL 时,连 utf8mb4 和 utf8 的区别都说不清。这份笔记真正的价值不在于“1000 行”这个数量,而在于它能不能覆盖一条完整链路:建库建表、写增删改查、跑事务、设计索引、排查线上故障。如果你是要准备面试、接手老项目或者从零搭一套业务库,这个方向确实值得投入。本文就按这条链路把它们拆开讲,每一章都落到能复现的命令和参数上。
2. 建库建表与字符集:最容易被忽略的“地基”决定后续 90% 的坑
2.1 字符集与排序规则:utf8mb4 不是选完就完事
在 MySQL 8.0 里,默认字符集已经是 utf8mb4,但 5.7 及更早版本默认还是 latin1。很多人迁移老库时只改了character_set_server,却忘了改已有表的存储字符集,结果就是中文写入报错Incorrect string value,或者读取时出现一串问号。我一般会在建库前把三层设置一次做完:
-- 建库时明确指定字符集与排序规则 CREATE DATABASE `user_center` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;逻辑说明:CREATE DATABASE后面的DEFAULT CHARACTER SET决定了这个库下新建表的默认字符集,COLLATE决定排序和比较规则。utf8mb4_0900_ai_ci里的0900对应 Unicode 9.0 标准,ai表示口音不敏感,ci表示大小写不敏感。如果业务需要大小写敏感的比较,比如用户名登录校验,就得换成utf8mb4_0900_bin。参数说明:utf8mb4_general_ci在 MySQL 5.7 里很常见但性能略差,8.0 直接用0900_ai_ci就行。另一个提醒是连接层也要设字符集,否则表是 utf8mb4、客户端却是 latin1,依然会乱码。连接串里建议显式加characterEncoding=utf8(JDBC)或SET NAMES utf8mb4(命令行)。
2.2 建表约束与自增主键:整数类型选错会让你后悔半年
很多新手建表时喜欢无脑INT AUTO_INCREMENT,但业务量一旦上去,INT的上限 21 亿并不算宽裕。日志表、流水表这类高频插入的表,我个人会直接上BIGINT,避免两年后迁移主键的惨剧。另一个常见问题是不加NOT NULL和DEFAULT,导致应用层读到的空值无法被 MyBatis 映射,引发空指针。下面这个例子是把约束写全的典型建表语句:
CREATE TABLE `login_log` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', `user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID', `login_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '登录时间', `ip` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '来源IP', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1成功 0失败', PRIMARY KEY (`id`), KEY `idx_user_time` (`user_id`, `login_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='登录日志表';逻辑说明:BIGINT UNSIGNED把负数区间去掉,可用上限翻倍到 1844 亿;CURRENT_TIMESTAMP作为DEFAULT可以让插入时不传login_time也能自动带时间,省掉一层应用代码。参数说明:TINYINT用来存状态位足够,不要用INT去扛一个只有 0 和 1 的字段;VARCHAR(64)存储 IP 够用,IPv6 最长 45 字符,别留 255。索引idx_user_time是典型联合索引,后面第四章会细讲为什么把user_id放前面。
2.3 一条 SQL 把表结构看清:SHOW CREATE TABLE 的读法
接手老项目最怕的是没人告诉你哪张表有坑。与其翻文档,不如直接执行SHOW CREATE TABLE,它会把真实的建表语句、字符集、约束、索引全部打出来,这是排查问题的第一现场。我每次排查慢查询都先跑这条语句确认表结构有没有被改过,再看索引。字段注释里往往藏着业务逻辑的线索——比如status字段注释写“1待支付 2已支付 3已退款”,你才知道查询条件该怎么写。这条命令不需要任何权限,是 DBA 之外的开发最该熟悉的排查工具。
3. SQL 高频操作与事务隔离:让笔记里的语句真正能跑进生产
3.1 DML 的三种写入姿势与“ON DUPLICATE KEY UPDATE”
笔记里最容易被抄错的是 INSERT 的可选子句。三种写入姿势分别是普通插入、INSERT IGNORE、INSERT ... ON DUPLICATE KEY UPDATE。普通插入遇到唯一键冲突直接报错,INSERT IGNORE会静默跳过冲突行,而带ON DUPLICATE KEY UPDATE的写法可以把冲突变成一次更新。示例:
-- 按 user_id 维度写入评分,存在则更新分数 INSERT INTO user_score (user_id, score, update_time) VALUES (10001, 95, NOW()) ON DUPLICATE KEY UPDATE score = VALUES(score), update_time = VALUES(update_time);逻辑说明:ON DUPLICATE KEY UPDATE的触发条件是唯一索引或主键冲突。VALUES(score)在 MySQL 8.0.20 之前表示引用 INSERT 里准备写入的值,8.0.20 之后官方推荐用别名写法,避免歧义。参数说明:这个语句的代价比普通 INSERT 略高,因为 InnoDB 要先尝试插入、再走唯一索引检测冲突,高并发秒杀场景要慎用。批量写入时一条语句带几百组值即可,不建议一次塞几万行,max_allowed_packet默认 64MB 很容易被打满。
3.2 事务与隔离级别:脏读、不可重复读、幻读的复现实验
MySQL 的事务核心是 InnoDB 的 MVCC 和锁机制,默认隔离级别REPEATABLE READ下你已经不太容易踩到脏读和不可重复读。但“不太容易”不等于“不会”,很多人在笔记里记了四个隔离级别却不知道边界在哪。最简单的复现方式是用两个客户端跑一个转账场景:A 会话开启事务更新余额但不提交,B 会话在READ COMMITTED下读到的还是旧值,在READ UNCOMMITTED下会读到未提交的脏数据。
-- 客户端1 START TRANSACTION; UPDATE account SET balance = balance - 100 WHERE id = 1; -- 不写 COMMIT,停在原地 -- 客户端2 SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT balance FROM account WHERE id = 1;逻辑说明:START TRANSACTION开启事务后,未提交的修改对其他会话是否可见,取决于对方会话的隔离级别。READ UNCOMMITTED会读到未提交数据,这在财务场景里是灾难。参数说明:生产库一般坚持默认的REPEATABLE READ,需要更高并发时才考虑改READ COMMITTED。改隔离级别用SET SESSION只影响当前连接,SET GLOBAL会影响所有新连接但不会重置已有的。另外注意START TRANSACTION之后如果执行了 DDL,MySQL 会隐式提交当前事务,笔记里把这个记成“事务中不能跑 DDL”就是从这个机制来的。
3.3 排序与去重:ORDER BY 的隐式排序陷阱和 DISTINCT 的误区
排序的坑往往藏在“结果看起来是对的”里。用ORDER BY对中文按拼音排序时,如果字符集排序规则是utf8mb4_0900_ai_ci,结果会按拼音排;如果是utf8mb4_bin,就会按编码排,顺序完全不同。另外ORDER BY里混用字段别名时,MySQL 8.0 会强制要求别名不能出现在WHERE中,否则直接报错。去重方面,DISTINCT作用于所有查询列的组合,不是只作用于第一列。很多人写SELECT DISTINCT user_id, status以为是在对 user_id 去重,实际是对(user_id, status)组合去重,结果出现重复 user_id。要真正拿到去重后的 user_id,应该用GROUP BY user_id配合聚合函数。
4. 索引设计:三类索引失效场景与 EXPLAIN 验证
4.1 隐式转换、前导模糊查询、函数包裹列:索引失效的三个常见场景
索引失效的典型场景可以背,但要理解为什么。第一类是隐式转换,比如字段类型是VARCHAR但查询条件传数字,MySQL 会把列转成数字再比较,索引就失效了。第二类是前导模糊查询,LIKE '%abc'因为不确定匹配开头是什么字符,走不了 B+ 树的有序查找。第三类是函数包裹列,WHERE DATE(create_time) = '2024-01-01'相当于对索引列做完函数运算才比较,索引天然失效。下面这条 SQL 是反面教材:
-- 错误示范:DATE() 包裹 create_time,导致 idx_create_time 失效 SELECT * FROM order_info WHERE DATE(create_time) = '2024-01-01';逻辑说明:对索引列使用函数后,MySQL 无法直接利用 B+ 树的叶子节点顺序做范围匹配,只能全表扫描。参数说明:正确的写法是把条件改成范围查询WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02',这样能走索引范围扫描range。另一个案例是SELECT * FROM user WHERE phone = 13800138000,如果phone是VARCHAR,这个查询会触发隐式转换。解决方式是写字符串字面量WHERE phone = '13800138000'。
4.2 联合索引的最左前缀原则:字段顺序决定生死
联合索引(user_id, login_time)遵循最左前缀原则:查询条件里只有出现user_id时才能用这个索引,只给login_time是不行的。很多人建索引时随手把区分度高的字段放后面,结果查询根本走不上。生产经验是先放等值查询字段,再放范围查询字段。下面演示如何验证:
-- 建立联合索引 ALTER TABLE login_log ADD INDEX idx_user_time (user_id, login_time); -- 能用上索引的查询:user_id 等值 + login_time 范围 EXPLAIN SELECT * FROM login_log WHERE user_id = 10001 AND login_time >= '2024-01-01'; -- 用不上索引的查询:只查 login_time EXPLAIN SELECT * FROM login_log WHERE login_time >= '2024-01-01';逻辑说明:第一条EXPLAIN应该能看到type为range或ref,key列出现idx_user_time;第二条的type大概率是ALL,表示全表扫描。参数说明:当user_id的过滤效果好时,联合索引把同用户的多行登录记录在叶子节点上连续排列,范围查询只需要定位一次再顺序扫。如果业务还有大量只按login_time的查询,就得单独补一个login_time的单列索引,不要指望联合索引反过来生效。
4.3 用 EXPLAIN 读执行计划:type、key、rows 是最该看的三个字段
EXPLAIN的输出有很多列,很多初学者被possible_keys和key搞混。possible_keys列出可能被用到的索引,key是实际用到的索引。光看key不够,还要看type:从好到差依次是system、const、eq_ref、ref、range、index、ALL。ALL就是全表扫描,index表示扫描整棵索引树,也不一定快。rows是估算要扫的行数,数量级在万以上就要警惕。示例:
EXPLAIN SELECT id, user_id FROM login_log WHERE user_id = 10001\G逻辑说明:\G让 MySQL 把结果按纵向输出,看type: ref、key: idx_user_time、rows: 12就说明命中了索引且扫描行数可控。参数说明:如果看到type: ALL且rows上千万,立刻考虑加索引。另一个小技巧是在EXPLAIN后面加FORMAT=JSON,能看到cost_info里的代价估算,方便做两块执行计划的横向对比。笔记里如果只记了 explain 的列名含义,没有记读法顺序,等于白记。
5. 线上 MySQL 故障排查:从启动失败到锁等待的 5 类现场
5.1 服务无法启动:net start mysql 报错怎么定位
Windows 上net start mysql报“服务无法启动”是高频问题,真正的原因通常在错误日志里而不是服务窗口。MySQL 5.7 和 8.0 的错误日志默认在数据目录下,文件名可能是hostname.err。报错[ERROR] [MY-014060] ... invalid mysql server upgrade通常意味着数据目录里的系统表和二进制版本不匹配,常见于跨大版本升级后直接复用旧数据目录。排查步骤是先看日志,再确认目录权限。Linux 上用systemctl status mysqld加日志路径更快,CentOS 下默认日志在/var/log/mysqld.log。修复方式是把数据目录备份后重新初始化,但初始化前一定要确认原来的ibdata1和ib_logfile*没有残留,否则又是一个新坑。
5.2 SSL 连接报错:sql 连接串里 ssl-mode 的取舍
MySQL 8.0 默认开启 SSL 要求,JDBC 连接串如果不带ssl-mode,可能直接报Public Key Retrieval is not allowed。本地开发和内网环境常见做法是显式关闭 SSL:?useSSL=false&allowPublicKeyRetrieval=true。但生产环境不建议因为怕麻烦就全关,至少用ssl-mode=PREFERRED开启协商。这类报错的本质是客户端和服务端在握手阶段协商加密方式失败,和字符集问题一样,属于连接层问题。参数说明:allowPublicKeyRetrieval=true允许客户端从服务端拉取公钥,仅用于caching_sha2_password认证,内网开发可以开,公网环境开了会有中间人风险。
5.3 锁等待与死锁:从 information_schema 定位持锁事务
Lock wait timeout exceeded是并发写入时的经典报错,大部分情况不是真的死锁,而是一个事务持锁不释放,把别的会话卡住了。排查方式是通过information_schema.innodb_trx找到长时间未提交的事务,然后结合sys.innodb_lock_waits看谁阻塞了谁:
-- 查看当前所有运行中的事务和耗时 SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id FROM information_schema.innodb_trx WHERE trx_state = 'RUNNING'; -- 查看锁等待关系 SELECT * FROM sys.innodb_lock_waits\G逻辑说明:innodb_trx能看事务启动时间和状态,超过几十秒还在RUNNING的事务多半是忘了提交;sys.innodb_lock_waits直接列出阻塞者和被阻塞者的线程 ID。查到后可以用KILL <trx_mysql_thread_id>结束持锁会话,但前提是确认它不是核心业务事务。参数说明:innodb_lock_wait_timeout默认 50 秒,等不及可以改小,但治标不治本;真正解法是缩短事务体,把更新操作批量合并,避免事务里穿插网络调用。
5.4 误更新数据:undo log 与 binlog 的两种后悔药
笔记里如果只写了 DELETE 和 UPDATE 的语法,没写“如何还原”,实用性少一半。MySQL 提供两种常见恢复路径:如果事务还没提交,直接ROLLBACK用 undo log 回滚;如果已经提交,则需要靠备份 + binlog 做时间点恢复。第二种路径的代价在于你得提前开了binlog,并且知道大致误操作时间。日常生产中我的习惯是:任何 UPDATE 都先SELECT同样的WHERE条件看影响行数;重要表每次改动前用CREATE TABLE tmp AS SELECT * FROM target WHERE ...做临时备份。参数说明:binlog_format建议用ROW,因为STATEMENT格式在回放时可能因为函数、时间等环境变量产生和原来不一致的结果。误操作后的恢复步骤是把备份恢复到临时实例,再用mysqlbinlog从误操作前的时间点增量回放。
5.5 Docker 部署 MySQL:时区、编码、文件挂载三大参数
自建环境里用 Docker 跑 MySQL 已经非常普遍,但不少人docker run起来后就发现中文乱码、时间差 8 小时、容器重启数据丢失。乱码是因为容器内默认字符集不是 utf8mb4,时区是因为容器默认 UTC。下面是一个我常用的最小化部署命令:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=your_password \ -e TZ=Asia/Shanghai \ -v /data/mysql:/var/lib/mysql \ mysql:8.0逻辑说明:-e TZ=Asia/Shanghai设置容器时区;-v /data/mysql:/var/lib/mysql把数据目录挂到宿主机,避免容器重建后数据丢失。参数说明:还可以加--character-set-server=utf8mb4 --collation-server=utf8mb4_0900_ai_ci显式指定字符集。Docker 部署失败大多不是因为镜像本身,而是挂载目录权限:宿主机目录属主不是 MySQL 的 uid,容器启动时会因为目录不可写直接退出。排错时不要只看docker logs,先确认/data/mysql的属主是否允许容器内进程写入。另外docker pull mysql报错failed to decode referrers index多半是镜像源解析问题,换一个 registry 镜像即可,不要把时间花在排查容器本身上。
6. 把笔记内化成能力:用 mysqldump 做一次完整的恢复演练
“看过 1000 行笔记”和“能处理一次真实故障”之间隔着一场演练。我建议你选一个周末的下午,在自己的测试库上做一次完整备份与恢复:第一步用mysqldump导出全库,第二步删掉一张表,第三步把备份导回去。命令就三条,但做完你会发现很多笔记里没写的细节,比如导出时--single-transaction的作用:
# 导出全部数据,带单事务快照,不影响线上写入 mysqldump -uroot -p --single-transaction --set-gtid-purged=OFF \ --databases user_center > /backup/user_center.sql # 恢复前先看备份文件前几行,确认字符集和 CREATE DATABASE 语句 head -50 /backup/user_center.sql # 恢复 mysql -uroot -p < /backup/user_center.sql逻辑说明:--single-transaction在 InnoDB 下导出时开启一个一致性快照事务,不会被 DML 干扰,也不会锁表;--set-gtid-purged=OFF是为了避免 GTID 信息在普通恢复时干扰全局事务编号。恢复时用<重定向,相当于把文件当作 SQL 批处理执行。参数说明:如果只导出某张表,用mysqldump 库名 表名;如果只要结构不要数据,加--no-data。这个流程跑完之后还有一件事值得做:把导出的 SQL 文件在临时实例上执行一遍,确认数据行数和原库对得上,不要只看到Import finished就以为万事大吉。
我自己的习惯是每个月挑一天做恢复演练,时间从首次的 40 分钟压到现在的 10 分钟。只有真正把恢复流程跑顺了,笔记里的命令才从“读过”变成“会救火”。这个方向是否值得投入,我说一个判断标准:如果你发现自己面对锁等待、索引失效、字符集乱码时能只看日志就定位到具体章节的知识点,那这份笔记就没有白读。希望帮到你。
本文还有配套的精品资源,点击获取