折腾了一上午,终于把第34节课的内容吃透了:创建数据库,然后把各种SQL语句挨个跑了一遍。说实话,这节课在教程里排得不算靠后,但对于刚装好MySQL准备练基本功的人来说,恰恰是最容易“卡壳”的地方——不是语法背不下来,而是你不知道为什么这样写、换了环境怎么调、报错该从哪里排查。这篇笔记把我从“建库”到“各种SQL都能跑通”的完整过程记录下来,适合刚装完MySQL、准备跟着教程敲命令的同学,也适合想系统性复盘一遍建库、建表、增删改查、索引事务的老手。
这篇笔记里没有花哨的东西,全是命令行里实测过的语句,以及我在第34节课上踩过的坑。你跟着敲一遍,基本就能把MySQL的日常操作串起来了。
1. 环境准备:装好MySQL,才能谈创建数据库
1.1 安装版本选型与安装方式实测
先说版本。现在新入门我建议直接装MySQL 8.0系列,包括最新的8.0.44等小版本,而不是还在网上大量流传的5.7.26。原因不复杂:8.0自带utf8mb4默认字符集、窗口函数、公用表表达式(CTE)这些现代SQL能力,而且默认认证插件是caching_sha2_password,整体更安全。5.7不是不能用,但官方已经逐步走向寿命末期,新项目再踩那个坑没必要。
安装方式上,Windows环境直接下载MySQL Installer的exe(你搜到的mysql 50616版本exe、windows安装mysql 8都是这类),一路Next选Developer Default,顺手把MySQL Workbench装上。要注意安装完有一个很关键的细节:MySQL 8.0初始化时会自动给root生成一个临时密码,放在数据目录下的xxx.err文件里,第一次登录必须用这个临时密码,登录后立刻改掉。很多人卡在第一步就是没去找这个单次临时密码。
Linux环境用rpm安装mysql是比较常规的操作,我用CentOS实测过,流程大致是:下载对应系统的rpm包,然后rpm -ivh安装,再用mysqld --initialize初始化数据目录。初始化完成后同样会生成临时密码,别急着启动服务,先grep 'temporary password' /var/log/mysqld.log看一眼,再systemctl start mysqld。
装完验证一下,命令行执行mysql --version,能输出版本号就说明安装成功。这一步虽然简单,但我见过不少同学装完直接双击图标发现“服务未启动”,所以建议第一个命令永远先确认服务状态。
1.2 命令行连接与图形化工具的选择
装好后第一件事就是连上去。命令行最直接:
mysql -u root -p输入密码后进入mysql>提示符,这时候一个完整的服务器端交互环境就起来了。如果提示ERROR 1045 (28000): Access denied for user 'root'@'localhost',多半是密码不对,或者你的root账户只允许本机连接,这个后面第5章详细说。
图形化工具方面,很多人习惯用Navicat for MySQL,我也承认它界面确实顺手,但正版要钱,破解版咱不谈。我个人的建议是:学习阶段能用命令行就命令行,必要看图的时候装一个DBeaver Community或者VS Code的Database插件,一样能跑SQL、看表结构。命令行的好处是让你真正理解“SQL在服务器上执行”这件事,而不是在表格框里点点点。
连接时还有一个高频问题:mysql ssl连接错误。8.0默认开了SSL认证,有些老客户端或者特殊网络环境下会握手失败,报SSL connection error。排查思路是先确认服务器端SSL配置,再检查客户端连接参数是否需要调整(比如某些驱动要求显式关闭或指定SSL模式)。这里我不建议一上来就禁SSL,先看服务端是否正常开启,再看证书路径对不对。
1.3 先把概念分清楚:实例、库、表
第34节课一上来,老师就强调了概念层级:一个MySQL服务器可以同时运行多个数据库实例(通常我们说的实例就是mysqld进程),一个实例下可以创建多个数据库(Database),一个数据库里可以建多张表,表里才是真正按行和列组织的数据。
我用个生活化的类比:数据库实例就像一栋写字楼,数据库是一个个公司租的楼层,表是楼层里的工位区,行就是工位上的一个人,列就是工位牌上的各项属性。创建数据库,本质上是在这栋楼里给新公司划一块地盘。
这个类比能帮你理顺后面所有操作:CREATE DATABASE在“楼层”层面创建空间,CREATE TABLE在“楼层”里摆工位。我们常说的“cmd导出sql”其实也是围绕这个概念走的——用mysqldump把整个“楼层”的结构和数据打包成一份sql脚本,到另一栋楼里还原。
2. 创建数据库:从CREATE DATABASE开始
2.1 CREATE DATABASE完整语法与字符集决策
创建数据库的SQL本身很简单,但里面藏着两个最容易出错的决定。标准语法长这样:
CREATE DATABASE IF NOT EXISTS school DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;IF NOT EXISTS是容错保护:如果库已经存在,MySQL不会报错,而是给一条warning。我第一次写的时候没加,第二次重复执行直接ERROR 1007 (HY000): Can't create database 'school'; database exists,所以现在我的习惯是凡是建库建表都带上这句。
真正的关键决策是字符集。MySQL里的utf8其实只是utf8mb3,只能存3字节以内的UTF-8字符,这意味着emoji表情和部分生僻字会直接变成乱码甚至报错。所以新库一律用utf8mb4,这个才是完整的四字节UTF-8。我见过太多次“客户端显示正常、服务器端乱码”的怪问题,追到最后全是建库时图省事用了默认latin1或者老utf8。
排序规则COLLATE作为配套也要选对。8.0默认的utf8mb4_0900_ai_ci就够用,大小写不敏感、口音不敏感,适合绝大多数中文和英文场景。如果是5.7环境,常见选择是utf8mb4_general_ci。排序规则决定了字符串比较和ORDER BY的最终顺序,选错不会报错,但排序结果可能跟你预期不一致。
建完库顺手做三件事:SHOW DATABASES;看库列表,USE school;切进去,SELECT DATABASE();确认当前选的库。这三条命令我每节课都至少跑一遍,已经成肌肉记忆了。
2.2 创建表:数据类型、主键与自增
建库只是划地盘,建表才真正定义数据格式。第34节课的示例表我敲了好几遍,最后定稿是这样:
CREATE TABLE IF NOT EXISTS student ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', name VARCHAR(50) NOT NULL COMMENT '姓名', email VARCHAR(100) DEFAULT NULL COMMENT '邮箱', score DECIMAL(5,2) DEFAULT 0.00 COMMENT '成绩', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (id), UNIQUE KEY uk_email (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生表';几个设计要点我逐个说:
INT UNSIGNED配合AUTO_INCREMENT做主键,是MySQL最经典的ID方案。UNSIGNED把负数空间让给正数,理论上限翻倍。VARCHAR是变长字符串,必须给长度。name VARCHAR(50)表示最多存50个字符,但实际占用按内容长度走,而不是固定50字节。这是和CHAR最大的区别。score DECIMAL(5,2)表示总长5位小数占2位,最大999.99。涉及金额、分数这种精确数值,绝对不用FLOAT或DOUBLE,二进制浮点会有精度丢失。created_at DATETIME DEFAULT CURRENT_TIMESTAMP让数据库自动填当前时间,应用层不用管。这是管理后台系统里利用率极高的默认值写法。UNIQUE KEY uk_email (email)给邮箱加了唯一索引。注意唯一索引会让重复值插入直接报错,你把字段当业务唯一标识时才能这么干。
建完表用DESC student;看结构,用SHOW CREATE TABLE student\G看完整定义。后者会把你建表语句原样返回,还能看到MySQL自动补充的默认值,是排查“为什么表结构和我想的不一样”的利器。
2.3 修改表结构:ALTER、TRUNCATE与DROP的边界
表建完之后经常要改。ALTER TABLE的常用场景:
ALTER TABLE student ADD COLUMN phone VARCHAR(20) AFTER email; ALTER TABLE student MODIFY COLUMN name VARCHAR(100) NOT NULL; ALTER TABLE student DROP COLUMN phone; ALTER TABLE student RENAME TO student_info;ADD COLUMN加列,MODIFY COLUMN改类型或约束,DROP COLUMN删列,RENAME TO改表名。里面细节不少:MODIFY会覆盖列的完整定义,你写的时候必须把NOT NULL、DEFAULT这些约束重新声明一遍,否则就被重置了。我刚学时经常只改一个类型,结果其他约束丢了。
清理表数据时你会面对三个选择:DROP TABLE是连表带定义一起删除,TRUNCATE TABLE是清空数据但保留表结构,DELETE FROM student是逐行删除且可以配合WHERE。TRUNCATE不能回滚,它通过重建表实现清理,速度极快;DELETE逐行删,可以走事务回滚。
表操作删错是灾难级的,我的习惯是先SHOW TABLES;列出所有表名,确认目标后再执行删除类语句。DROP DATABASE的操作更是要反复确认库名,生产环境里一条DROP DATABASE误执行就可能让整个业务的底层数据清零。
3. 运行各类SQL:DML与查询实操
3.1 增删改:INSERT、UPDATE、DELETE的避坑要点
先说插入。单行插入最直白:
INSERT INTO student (name, email, score) VALUES ('张三', 'zhangsan@example.com', 88.5);多行插入就用逗号把每一行VALUES连接起来,我实测过,一次插几百行比一条条执行快一个量级。还有一种进阶写法INSERT INTO ... SELECT,把查询结果直接灌进另一张表,做数据备份或临时表很方便:
INSERT INTO student_bak (id, name, score) SELECT id, name, score FROM student WHERE score >= 60;更新和删除最大的坑都是同一个:忘了加WHERE。UPDATE student SET score = 100;会改全表所有行,这在学习环境里无所谓,生产环境就是事故。我现在养成的习惯是:任何UPDATE或DELETE先写WHERE条件,再回头检查一遍条件字段有没有索引、是不是真的只想动这几行。还可以先用SELECT跑一遍同样的条件,确认命中的行数符合预期,再改成UPDATE或DELETE执行。
如果只想清空表且重置自增ID,TRUNCATE TABLE student;比DELETE FROM student更彻底。但注意它不能加WHERE,是整表清空,也别指望事务回滚。
3.2 查询核心三件套:过滤、排序、去重
查询是所有SQL操作里出镜率最高的,第34节课花了大量篇幅在这里。最基础的骨架是:
SELECT column1, column2 FROM table WHERE condition;先说WHERE过滤。支持=、!=、>、<、>=、<=,也支持逻辑组合AND和OR。记得加括号控制优先级,否则AND和OR混在一起很容易查错范围。
排序用ORDER BY,对应热搜词里的mysql排序。语法是:
SELECT name, score FROM student ORDER BY score DESC, name ASC;先按成绩降序,成绩相同时再按姓名升序。默认是ASC升序,想从高到低就显式写DESC。排序字段最好有索引覆盖,否则数据量大时会拖慢查询。
去重用DISTINCT,对应热搜里的sql语句去重:
SELECT DISTINCT name FROM student;这里有个新手经常误会的点:DISTINCT作用于后面所有选中列的组合,不是只作用于第一列。SELECT DISTINCT name, score FROM student得到的是name + score组合不重复的结果,如果两行name相同但score不同,两条都会被显示。想真正对单列去重,只能用子查询或GROUP BY。
分页查询是实际项目里几乎每天都用的:LIMIT关键字。LIMIT 10取前10行,LIMIT 10, 20表示跳过10行取20行,等价于LIMIT 20 OFFSET 10。分页的坑在深翻页,偏移量越大越慢,后面慢SQL优化那节再展开。
3.3 进阶过滤:空值、模糊匹配与范围查询
三大高频过滤场景,我一个个说。
空值处理对应热搜里的sql去除空值。SQL里NULL代表“未知”,它不是空字符串'',也不是数字0。判断空值必须用IS NULL或IS NOT NULL,写WHERE email = NULL永远查不到数据,因为NULL = NULL的结果也是NULL,条件不成立。
SELECT * FROM student WHERE email IS NOT NULL; SELECT * FROM student WHERE COALESCE(email, '无邮箱') = '无邮箱';COALESCE函数把NULL替换成默认值,是个很实用的小技巧,在SELECT结果里展示时不至于满屏NULL。
模糊匹配用LIKE,配合%和_两个通配符。%代表任意长度的任意字符,_代表单个任意字符:
SELECT * FROM student WHERE name LIKE '张%'; -- 张三、张伟、张无忌 SELECT * FROM student WHERE name LIKE '张_'; -- 只匹配两个字的名字,比如张三注意一点:%开头(比如'%张%')会导致索引失效,全表扫描。数据量小无所谓,大了就慢。真到了那一步,可以考虑全文索引或者搜索引擎方案。
范围查询用BETWEEN AND和IN:
SELECT * FROM student WHERE score BETWEEN 60 AND 90; SELECT * FROM student WHERE name IN ('张三', '李四');BETWEEN AND是闭区间,包含两端值。IN后面跟的是一个列表,匹配其中任意一个即可。这两招写起来简洁,可读性也比一堆OR好很多。
3.4 聚合与分组:COUNT、SUM、GROUP BY与HAVING
聚合函数不处理具体行,它们把多行压成一个结果。最常用的是:
SELECT COUNT(*) FROM student; -- 总行数 SELECT AVG(score), MAX(score), MIN(score) FROM student; SELECT SUM(score) FROM student;COUNT(*)数所有行,COUNT(1)没有本质区别,而COUNT(email)只数非NULL的行。如果你想统计“有多少人填了邮箱”,必须用后者,否则NULL会被忽略这个语义就体现不出来。
分组统计是数据分析的基础操作:
SELECT name, COUNT(*), AVG(score) FROM student GROUP BY name;GROUP BY把相同字段值的行合成一组,然后对每组做聚合。这里最容易踩的坑是ONLY_FULL_GROUP_BY模式:MySQL 5.7以后默认开启,SELECT里出现的非聚合列必须出现在GROUP BY里,否则直接报错。也就是说SELECT id, name, COUNT(*) ... GROUP BY name里如果带上id,而id不在GROUP BY里,就会报错。
分组后的条件过滤不能用WHERE,得用HAVING:WHERE过滤原始行,HAVING过滤分组后的结果。
SELECT name, AVG(score) AS avg_score FROM student GROUP BY name HAVING AVG(score) >= 80;这个场景我第34节课犯的错就是拿WHERE avg_score >= 80去跑,结果报错说找不到avg_score列——因为别名在WHERE阶段还没生成。
4. 运行SQL的进阶玩法:索引、事务与存储过程
4.1 索引原理与EXPLAIN使用入门
索引这块内容课上可能不会一步讲透,但你要是只学建库建表而不懂索引,后面遇到慢sql优化(热搜词之一)必然会懵。我先给个通俗解释:没有索引的表,MySQL查一行数据只能从头到尾一行行扫,像在一本没有目录的书里找一句话,这叫全表扫描;有了索引,就相当于书末尾多了个按拼音排序的关键词目录,查起来直接翻页。
创建索引常用两种方式:
CREATE INDEX idx_score ON student(score); ALTER TABLE student ADD INDEX idx_name_score (name, score);第二种是联合索引,两个字段按顺序排列。联合索引有“最左前缀原则”:查询条件必须从索引最左列开始,跳过后面的列索引用不上。比如idx_name_score能加速WHERE name = '张三',但如果只写WHERE score = 90,这个联合索引基本白建。
如何判断一条SQL有没有用上索引?用EXPLAIN:
EXPLAIN SELECT * FROM student WHERE name = '张三';看结果里的type列:const或ref说明走了索引,非常快;ALL就代表全表扫描,需要优化。还要看rows列,MySQL预估扫描了多少行,数字越小越好。我刚学的时候对所有可疑SQL都过一遍EXPLAIN,写慢查询分析报告很快就能上手。
索引不是越多越好。每个索引都要占存储空间,写入时还要同步维护,写多读少的表加一堆索引反而拖慢INSERT和UPDATE。
4.2 事务:ACID与转账场景实操
事务是MySQL中保证数据一致性的核心机制,对应热搜里的mysql事务处理。简单说,事务把多条SQL打包成一个“要么全成功、要么全失败”的整体。
标准操作是:
START TRANSACTION; UPDATE account SET balance = balance - 100 WHERE id = 1; UPDATE account SET balance = balance + 100 WHERE id = 2; COMMIT;如果第二步执行出错,执行ROLLBACK;就把第一步的扣款撤销,两边账户都不会出现“钱扣了但对方没到账”的问题。
事务有四个特性ACID:原子性(Atomicity)对应上面的“全成或全败”,一致性(Consistency)保证数据约束不被破坏,隔离性(Isolation)让并发事务互不干扰,持久性(Durability)保证提交后数据不丢。MySQL默认autocommit=1,也就是你单独执行一条INSERT或UPDATE会自动提交,只有当显式START TRANSACTION后,多步操作才真正在一个事务里。
有一点必须注意:CREATE DATABASE、DROP DATABASE、ALTER TABLE这些DDL语句会在执行时隐式提交当前事务,想靠ROLLBACK救回DDL是不可能的。这也是为什么删表删库操作要格外谨慎。
4.3 存储过程与连接池:从单条SQL到工程化
存储过程是把一段逻辑固化在数据库里,比如:
DELIMITER // CREATE PROCEDURE get_student_count(OUT cnt INT) BEGIN SELECT COUNT(*) INTO cnt FROM student; END// DELIMITER ; CALL get_student_count(@c); SELECT @c;定义存储过程时用DELIMITER //临时把分隔符改成//,因为存储过程内部包含多个分号,不改的话MySQL会在第一个分号处就截断,语法直接报错。存储过程适合高度稳定的、不想让业务层接触底层表的统计类逻辑,但过度使用也会让业务逻辑下沉到数据库,后期维护很痛苦。
连接池则是工程向的一个重要概念,搜索热词里的mysql的数据库连接池就是这么回事。JavaWeb项目(对应热搜javaweb项目完整案例mysql)里应用启动时会提前创建一批到MySQL的连接放池子里,谁要用谁取,用完归还,而不是每次请求都重新握手建立连接。建立数据库连接的握手开销很大,池化是标配。常见的连接池有HikariCP、Druid,面试基本必问,理解了“连接复用”四个字就不会慌。
C++、Java这些语言后续要连MySQL,本质也是走一套客户端协议。C++可以用MySQL Connector/C++,Java用JDBC,但上层都会覆盖一层连接池,避免频繁建连。第34节课能跑通命令行SQL,后面接编程语言的思路其实是同一套:建立连接、执行语句、处理结果、释放连接。
5. 学习笔记里的常见问题速查
5.1 连接与认证类问题
我在第34节课前后积累了一批高频报错,第一个就是ERROR 1045 Access denied。这个报错原因有三种:用户不存在、密码错误、该用户不允许从当前IP连接。排查顺序:先试mysql -u root -p是不是本地密码错,再看用户表:
SELECT user, host FROM mysql.user;host是localhost表示只能本机连,远程连接需要单独建'user'@'%'这样带通配符的账户并授权。
第二个高频问题是SSL连接报错,对应热搜里的mysql ssl连接错误。MySQL 8.0默认启用SSL,客户端连接时如果服务端证书配置异常、或者客户端协议版本过老,就可能握手失败。不用一上来就禁SSL,先确认服务端SHOW VARIABLES LIKE '%ssl%'状态正常,再排查客户端驱动版本。
第三个是Windows服务启动失败,比如错误码e0434352。这类.NET运行时异常通常和系统运行库、服务权限有关。排查方向:确认MySQL服务账户有数据目录读写权限,检查Windows事件查看器里的具体异常堆栈,必要时重装对应VC++运行库。别一上来就重装MySQL,先看日志。
密码过期也是个典型的坑:只要开启了default_password_lifetime,用户密码到期后客户端连接会报ERROR 1862 ... password has expired。处理办法:
ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码';或者直接修改全局生命周期配置。这也是热搜里sql server 2012密码到期类似场景的MySQL版本,原理相同:认证过期后必须重设。
5.2 执行SQL时的报错排查与乱码问题
SQL执行时报错最经典的一种是提示字段不存在,类似Unknown column 'test_url' in 'field list',跟搜索热词里那条SQLite报错“no such column: test_url”是同一个套路。原因一般有三个:表结构里真没这个字段、字段名拼错、或者这张表和你以为的表不是同一个。排查的顺序是:先DESC 表名;确认字段存在,再检查SQL里的字段名是否完全一致,最后确认当前连接的库用SELECT DATABASE();有没有选对库。
语法错误的报错是ERROR 1064 (42000),它会提示出错的SQL位置,但位置不一定就是真正问题所在。我的排查技巧是:把一大段SQL拆成小片段逐段执行,先去掉所有WHERE条件,再一层层加回来,定位到第一个让MySQL报错的片段。第34节课上有一次我只漏了个逗号,找了好几分钟,就是因为整段执行没分段。
乱码问题也是高频现场。确认三处字符集一致:数据库字符集、连接字符集、客户端字符集。命令行里可以执行:
SET NAMES utf8mb4;把当前会话的character_set_client、character_set_connection、character_set_results一次设好。如果数据已经乱码,通常是写入时客户端字符集不对,先检查是不是从一开始就统一用了utf8mb4,再用ALTER DATABASE或ALTER TABLE统一库表和列字符集。字符集底子不好,后面全是泪。
5.3 慢SQL优化思路与自检清单
搜索热词里有一条是慢sql优化,这里给一套我在学习阶段总结的自检清单。第一步,打开慢查询日志:
SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1;凡是执行超过1秒的SQL都会被记录下来,Linux下日志默认在数据目录的-slow.log文件里。第二步,拿到慢SQL后立刻EXPLAIN,重点看type列和rows列。type是ALL基本就是在全表扫,rows估算值越大越危险。
优化方向按性价比排序:优先看是不是缺索引,WHERE和ORDER BY字段要不要建索引;再看是不是频繁使用%关键字%这种前置模糊搜索;再看有没有SELECT *,按理只取需要的列能减少网络传输和临时表开销。深分页问题在数据量大时尤其明显,LIMIT 1000000, 10会扫描前面100万行再丢掉,优化手段一般是改写成基于上次最大ID的“键集分页”(keyset pagination),类似WHERE id > 1000000 ORDER BY id LIMIT 10。
学习阶段能把上面这套自查跑完,慢SQL优化就算入了门。以后接触更为复杂的场景,比如用Flink把MySQL同步到ClickHouse之类的实时数仓方案,本质依然是对MySQL日志和数据的深度利用,前提还是对基础SQL烂熟于心。
我个人上完这节课最大的体会是:SQL这玩意儿,看十遍不如手敲一遍。今天建库时我就吃过一次字符集的亏,表结构设计也返工了两轮,但这些错早犯比晚犯好。建议你课后自己建一个练习库,把今天涉及的所有语句都跑一遍,再去试试修改表结构、跑聚合分组,顺便用EXPLAIN看看自己写的查询性能如何。真到了能默写常用SQL的程度,再看索引和事务会豁然开朗。最后一个小技巧:建库建表语句一定要存成sql脚本文件放在项目里,别只在命令行敲完就关,后面迁移环境时这就是你的救命稻草。