带过不少新人和应届生发现很多人对“会 MySQL”的定义就是能跑通几个 select、insert一旦被问到表结构为什么这样设计、查询为什么慢、事务和索引到底做了什么就明显露怯。这篇《MySQL 数据库基础》就是写给处于这个阶段的人——不是让你背几条命令就完事而是把 MySQL 里最核心、最常用、也最容易被忽略的那套基础逻辑一次性理顺。你不需要有任何数据库背景只要会开电脑能跟着操作就可以把它当作第一份入门材料。我会尽量用“做过项目的人”的口吻来讲而不是教科书式地罗列知识点。每讲一个概念我都会告诉你它解决什么问题、实际开发里会怎么用、踩过哪些坑。内容量大建议你打开电脑边看边操作效果比干读好得多。1. 值得先花十分钟搞懂的数据库基本概念1.1 数据库到底解决了什么问题很多人说数据库就是“存数据的地方”这话对但太笼统了。往深一层想你用 Excel 也能存数据为什么企业级应用几乎清一色选 MySQL、PostgreSQL 这类关系型数据库原因有四条持久化、并发控制、数据安全和高效检索。持久化进程结束、机器重启数据不会丢。并发控制几百个用户同时读写同一条记录数据库能保证数据仍然一致。数据安全通过账号体系、权限控制、事务机制避免误操作把数据搞坏。高效检索几百万行数据里查一条有索引的情况下能毫秒级返回。这四点是文件系统、内存存储很难同时做到的。MySQL 本质上就是一个替你把这些脏活累活全部包掉的服务进程。1.2 关系型数据库的“关系”二字从哪来关系型数据库的核心模型是“二维表”也就是行和列组成的表格。每一行是一条记录每一列是一个字段表与表之间通过主键和外键产生关联——这就是“关系”二字的来源。举个例子后面我们要做的“学生-课程-成绩”系统学生表里存学生的学号、姓名、性别课程表里存课程编号、课程名、学分成绩表里存学号、课程编号、分数通过学号和课程编号关联另外两张表。这样设计的好处是数据不重复学生的姓名只存在于学生表课程名只存在于课程表成绩表只存两个编号加一个分数。这种“不重复存、按需关联”的思路就是数据库设计里的范式思想。1.3 SQL 的四大分类别混着学SQLStructured Query Language结构化查询语言可以分为四类分类全称典型命令作用DDLData Definition LanguageCREATE、ALTER、DROP定义数据库、表的结构DMLData Manipulation LanguageINSERT、UPDATE、DELETE操作表里的数据DQLData Query LanguageSELECT查询数据日常刷得最多的DCLData Control LanguageGRANT、REVOKE控制权限很多初学者一上来就背各种语句背得混乱就是因为不知道眼前这条命令属于哪一类。一旦脑子里有这四个分类你会清楚地知道改表结构看 DDL改数据看 DML查数据看 DQL授权看 DCL。1.4 版本和存储引擎为什么 8.0 成了默认答案MySQL 的主流版本去年还是 5.7现在基本已经是 8.0 的天下了。8.0 相比 5.7 多了不少实用特性比如窗口函数、公共表表达式 CTE、默认字符集改为 utf8mb4性能也有提升。如果你是新学直接装 8.0不要犹豫更不要为了“兼容老项目”特意去装 5.7——等到你真正需要兼容老项目时你再回头研究也来得及。存储引擎方面你只需要重点关注 InnoDB。它是 MySQL 5.5 之后的默认引擎支持事务、行级锁、外键、崩溃恢复。而 MyISAM 不支持事务只支持表级锁写并发一高就卡。注意面试时经常有人问两种引擎的区别。你只要抓住“事务 行锁 外键”这三个关键词就抓住了命门。2. 安装、启动和连上数据库新手从入门到放弃的三道坎2.1 版本选择别下载一堆乱七八糟的版本很多人在 MySQL 官网下载页看到一堆名词就懵了MySQL Community Server、MySQL Cluster、MySQL Enterprise。你只需要选MySQL Community Server这是免费的社区版个人学习和绝大多数公司生产环境用的都是它。下载时注意操作系统和位数Windows 用户通常选 ZIP Archive 或 MSI Installer。MSI 是图形化安装适合鼠标党ZIP 是免安装版解压后手动初始化适合喜欢掌控每一步的人。macOS 用户可以直接下载 DMG 安装包或通过 Homebrew 安装。Linux 用户以 CentOS/Ubuntu 为例最省心的方式是直接用系统包管理器或 Docker。docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD123456 mysql:8.0一条命令就能拉起来一个测试实例非常适合本地练习。2.2 初始化、启动服务、设置环境变量如果你选了免安装版 ZIP拿到手之后不是解压就能用必须手动初始化# 进入解压后的 bin 目录 mysqld --initialize-insecure--initialize-insecure的意思是初始化数据目录并且 root 账号的初始密码为空。如果不用这个参数MySQL 会生成一个随机密码新手常常找不到后面我们单独讲。初始化完成后启动服务# 前端方式启动适合调试关掉终端服务就停 mysqld --console # 注册为 Windows 服务 mysqld --install net start mysqlLinux 下常见的是 systemd 方式systemctl start mysqld systemctl enable mysqld启动成功后再把bin目录加入环境变量这样你在任意目录敲mysql都能进入命令行客户端不用每次写一长串路径。2.3 初始密码网上说得最多的“伪命题”很多人在搜索栏里敲过“mysql的初始密码是什么”——这里直接告诉你答案MySQL 根本没有统一初始密码密码取决于你安装和初始化的方式。官网 MSI 安装器会在安装过程中让你设置 root 密码免安装版用--initialize-insecureroot 密码为空直接回车即可登录Linux 用mysqld --initialize随机密码会写在日志文件里比如/var/log/mysqld.log用grep temporary password /var/log/mysqld.log找。如果 root 密码忘了怎么办本地测试环境下最常用的做法是“跳过授权表重启”# 先在配置文件中临时加一行 skip-grant-tables # 重启 MySQL # 然后免密登录重设密码 ALTER USER rootlocalhost IDENTIFIED BY 新密码;设置完之后一定要把skip-grant-tables删掉再重启否则你的 MySQL 等于裸奔任何人在本机都能免密登录。生产环境千万不要用这招。2.4 客户端连接Workbench、Navicat 和 JDBC 的三个小坑命令行登录没问题之后很多人喜欢用可视化工具。官方工具是 MySQL Workbench功能全Navicat 是很多人习惯用的商业工具界面友好连接配置基本一样主机填127.0.0.1端口3306用户名root密码填你设置过的密码。连接失败的常见原因有三个服务没启动。检查服务状态而不是反复检查密码。端口被占用或被防火墙拦截。netstat -ano | findstr 3306Windows或lsof -i:3306macOS/Linux查看。密码确实输错了。这个无解回到 2.3 节重置。如果你是用 Java 的 JDBC 连接会额外碰到一个 SSL 相关的问题。8.0 版本的连接串里建议显式加上jdbc:mysql://localhost:3306/test?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse表示本地开发环境不用 SSL 加密allowPublicKeyRetrievaltrue是配合 MySQL 8.0 默认的 caching_sha2_password 认证方式用的。不加后者会报Public Key Retrieval is not allowed很多新手卡在这里。注意生产环境如果有条件SSL 该开还是开这里说的是本地开发调试的便捷配置。3. 建库建表以“学生-课程-成绩”为例的实体设计3.1 字符集和排序规则别再用 utf8 了建库时最容易被忽略的是字符集。MySQL 里的utf8实际上是“阉割版”最多只支持 3 个字节存不了 emoji 和一些生僻汉字。真正的完整 UTF-8 是utf8mb44 个字节MySQL 8.0 默认字符集已经是它。建库时建议明确写上CREATE DATABASE school DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;utf8mb4_unicode_ci是排序规则_ci表示 case insensitive也就是排序比较时不区分大小写。日常业务用这一套就够稳。3.2 三张表的设计实战基于“学生-课程-成绩”场景我们建三张表。学生表CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT DEFAULT 0 COMMENT 0未知 1男 2女, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;课程表CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 课程编号, course_name VARCHAR(100) NOT NULL COMMENT 课程名, credit DECIMAL(3, 1) NOT NULL DEFAULT 0.0 COMMENT 学分 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;成绩表CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5, 2) COMMENT 分数, KEY idx_student (student_id), KEY idx_course (course_id), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student (id), CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里用了两个外键把成绩表跟学生表、课程表关联起来。外键能保证数据的完整性——你不可能往成绩表里插入一个不存在的学生学号。生产环境有些团队为了性能会删掉外键、在应用层做校验但作为学习者把外键用明白是非常重要的基础。3.3 字段类型和默认值最容易埋雷的地方建表时类型选错后期改表的痛苦远超你的想象。我的经验是记住这几个最常见的就够了整数TINYINT小范围比如性别、INT常规主键、BIGINT雪花 ID、大表主键。小数DECIMAL存钱和分数必须用它因为它不会出现浮点误差。热词里有个“mysql中int5”说的是整数列的算术运算INT直接加数字没问题但如果是金额字段一定不要用FLOAT/DOUBLE业务上算错一分钱都是事故。字符串定长的用CHAR变长的用VARCHAR。VARCHAR 里写的是字符数上限不是字节数。时间DATETIME存年月日时分秒DATE只存日期。不要用字符串存时间否则排序、范围查询都会很难受。默认值方面“mysql设置默认值为0”是一个高频搜索词写法很简单status TINYINT NOT NULL DEFAULT 0我建议所有能用默认值的字段都显式指定默认值并且在建表时把NOT NULL想清楚。倒不是说每一列都不能为空而是“能不能为空”这件事必须是你明确设计的而不是放任自流。否则查询时到处是NULL统计结果常常对不上。3.4 表设计阶段最常见的三种错误不设主键。InnoDB 的表没有显式主键时性能会受影响而且复制、分表都麻烦。每张表都应该有主键。字段类型乱用。比如用VARCHAR(1000)存状态码用TEXT存手机号。类型过大占用更多内存和磁盘类型过小存不下都是隐患。没有统一命名规范。表和字段命名不一致后来人维护时非常痛苦。我习惯库名用小写下划线表名单数字段名可读性优先比如create_time而不是ct。4. 增删改查先把高频 SQL 写对再谈优化4.1 INSERT批量插入比逐条插入更现实插入三条学生的数据INSERT INTO student (name, gender) VALUES (张三, 1), (李四, 2), (王五, 1);一次插入多条记录而不是一条条执行能显著减少客户端和数据库的交互次数。在数据量大的批量初始化场景这个习惯能省出几倍的时间。4.2 UPDATE没写 WHERE 就是事故更新语句很简单但最常见的致命错误就是忘写 WHERE-- 危险会把所有学生的性别都改成 1 UPDATE student SET gender 1; -- 正确做法 UPDATE student SET gender 1 WHERE id 2;很多新手在测试库里没体会因为数据量小、删错了也能重建。可就因为这样才更要在练习阶段就养成“先 SELECT 确认范围再 UPDATE”的习惯-- 先查出来看一眼 SELECT * FROM student WHERE id 2; -- 确认无误再更新 UPDATE student SET gender 1 WHERE id 2;热词里有个“mysql中更新子查询”说的是 UPDATE 和子查询结合的场景。MySQL 不允许“一边查同一张表一边更新同一张表”所以像下面这种写法会报错-- 报错You cant specify target table for update in FROM clause UPDATE student SET name 赵六 WHERE id IN (SELECT student_id FROM score WHERE course_id 1);解决办法是包一层临时表UPDATE student SET name 赵六 WHERE id IN ( SELECT * FROM ( SELECT student_id FROM score WHERE course_id 1 ) AS tmp );这个问题在业务开发里很常见值得记住。4.3 DELETE 和 TRUNCATE同样是删命运完全不同DELETE是逐行删除可以加 WHERE操作会记录到 binlog理论上可以通过事务回滚TRUNCATE是直接把整张表重建速度快但不能回滚。DELETE FROM student WHERE id 100; TRUNCATE TABLE student;注意如果你只是想清空一张表并让自增 ID 重新从 1 开始用 TRUNCATE如果是删掉部分数据用 DELETE。两者混用的后果是“你以为删干净了自增 ID 却还从上次的位置往后走”。4.4 SELECTWHERE、ORDER BY、LIMIT 的黄金组合查询是基本功中的基本功。一张成绩表想查“课程编号为 1 的所有记录按分数从高到低排”SELECT student_id, course_id, score FROM score WHERE course_id 1 ORDER BY score DESC LIMIT 10;ORDER BY score DESC是降序升序用ASC默认就是升序可以省略。LIMIT 10表示只取前 10 条分页查询时你会频繁用到它。记住这个组合已经能覆盖开发中相当大比例的查询需求。4.5 JOIN连表查询的理解比背语法更重要JOIN 相关的搜索一直很热很多人被 left join、right join、inner join 绕晕。直接说结论INNER JOIN只保留两边都能匹配上的记录。LEFT JOIN左表全部保留右表能匹配就匹配匹配不上补 NULL。RIGHT JOIN右表全部保留左表匹配不上补 NULL。FULL OUTER JOIN两边都保留MySQL 不直接支持但可以用LEFT JOIN UNION RIGHT JOIN模拟。套到我们的例子查“每个学生的选课成绩包括没选课的学生”SELECT s.name, c.course_name, sc.score FROM student s LEFT JOIN score sc ON s.id sc.student_id LEFT JOIN course c ON sc.course_id c.id;用LEFT JOIN是因为我们关心“左侧学生表”的全部数据——即使某个学生没有成绩记录也要显示出来。面试时还会追一句JOIN 和子查询怎么选一般经验是JOIN 的可读性更好MySQL 优化器对 JOIN 的优化也比较成熟子查询在部分场景会出现性能陷阱。能直接用 JOIN 表达的关联需求优先 JOIN。4.6 聚合查询GROUP BY 和 HAVING 的配合统计“每个学生的平均分并且只显示平均分大于 60 的学生”SELECT student_id, AVG(score) AS avg_score FROM score GROUP BY student_id HAVING avg_score 60;注意 WHERE 和 HAVING 的区别WHERE 是在分组前过滤原始行HAVING 是在分组聚合后过滤聚合结果。想过滤“平均分大于 60”这种条件必须用 HAVING因为 AVG 是在分组阶段才计算出来的。4.7 一条查询的真实执行顺序这一点面试极爱考也是理解 SQL 的钥匙。你写的 SQL 是“先 SELECT 后 FROM”但数据库实际执行顺序不是这样FROM - WHERE - GROUP BY - HAVING - SELECT - ORDER BY - LIMIT也就是说数据库先确定从哪张表取数据再过滤行再分组再过滤分组然后才轮到 SELECT 挑选列最后排序和限制条数。明白这个顺序之后很多奇怪问题就说得通了——比如为什么 WHERE 里不能直接用 SELECT 里起的别名因为别名的生成在 WHERE 之后。5. 索引、事务和存储过程基础阶段最值得提前了解的进阶点5.1 索引为什么快B 树的通俗类比索引本质上是一种“以空间换时间”的数据结构。你可以把它想象成书的目录没有目录时你想找某个词只能一页页翻有目录后先定位到章节再缩小范围找。MySQL InnoDB 里最常用的是 B Tree 索引。B 树的叶子节点存储数据本身非叶子节点只存键值和指针。相比二分查找、哈希索引B 树的优势是“矮胖”——三层左右的树就能撑起几百万条数据。创建索引很简单CREATE INDEX idx_score_student ON score(student_id);但索引不是越多越好。每个索引都要占磁盘每次插入、更新都要额外维护索引结构。核心原则是低频查询的字段、区分度低的字段比如性别不适合建索引高频查询条件和 JOIN 关联字段才适合。5.2 EXPLAIN看一条查询到底怎么跑想判断 SQL 有没有走索引最直接的办法是用 EXPLAINEXPLAIN SELECT * FROM score WHERE student_id 1;看输出里的几个关键列type从好到坏大致是constrefrangeindexALL看到ALL说明全表扫描要警惕。key实际使用的索引名。rows预估扫描的行数越小越好。我见过很多项目上线后突然慢查询一查就是某个查询没走索引导致几百万行全表扫描。所以不要等出问题再排查写复杂查询时随手EXPLAIN一下能发现大量隐患。5.3 索引失效的几种常见写法建了索引不代表一定走索引。以下写法在大多数情况下都会让索引失效在索引列上做运算WHERE score 1 60应改成WHERE score 59。对索引列隐式类型转换字段是字符串条件却写数字。用前置模糊匹配WHERE name LIKE %张%前缀的%会破坏 B 树的按序查找。联合索引不满足最左前缀原则。这些不需要死记只要记住一个底层逻辑索引能加速的本质是“有序”一旦你的写法让 MySQL 无法利用这个“有序”索引就废了。5.4 事务ACID 到底在保护什么事务是数据库的并发安全机制。一个转账操作——从 A 账户扣钱、加到 B 账户——必须作为一个整体要么都成功要么都失败不能只成功一半。ACID 四个性质性质含义通俗理解A 原子性事务里的操作不可分割要么全做要么全不做C 一致性事务前后数据状态合法转账前后总金额不变I 隔离性并发事务互不干扰我改我的你查你的D 持久性提交后修改永久保存重启不丢MySQL 中事务的用法START TRANSACTION; UPDATE account SET balance balance - 100 WHERE id 1; UPDATE account SET balance balance 100 WHERE id 2; COMMIT;如果中间某条语句出错用ROLLBACK回滚到事务开始前的状态。隔离级别方面默认的REPEATABLE READ在绝大多数业务场景都够用初学者不需要一开始就死磕四种隔离级别的底层实现但要知道“隔离级别越高并发性能越差”。5.5 存储过程声明语法和适用场景存储过程就是“把一段 SQL 逻辑预先写好存到数据库里调用时直接执行”。基础阶段了解语法即可DELIMITER $$ CREATE PROCEDURE GetStudentScore(IN student_id_param INT) BEGIN SELECT s.name, c.course_name, sc.score FROM score sc JOIN student s ON sc.student_id s.id JOIN course c ON sc.course_id c.id WHERE s.id student_id_param; END$$ DELIMITER ;调用CALL GetStudentScore(1);DELIMITER的作用是把默认的分号结束符临时改成$$因为存储过程内部有多条 SQL每条都以分号结尾如果不改结束符MySQL 会在第一个分号处认为语句已结束。实际开发中存储过程用得越来越少因为复杂业务逻辑放在应用层更容易调试、测试和版本管理。但你要能看懂别人写的存储过程而且面试时它也是个高频考点。6. 基础阶段最值得练的几个排查动作6.1 锁表了怎么办show full processlist 的正确用法“mysql show full processlist”是热词说明很多人遇到过数据库突然卡住、请求全部堆积的问题。当你怀疑锁表时第一反应是执行SHOW FULL PROCESSLIST;这个命令会列出当前 MySQL 的所有连接和正在执行的语句。重点关注Command和State列如果有很多Query长时间卡在Waiting for table metadata lock或Updating基本可以判断有锁等待问题。锁一般来自长事务或未提交的更新语句。找到对应的Id后可以杀掉卡死的会话KILL 12345;不过 KILL 是治标根本解法是排查业务代码里是否存在“开启事务后长时间不提交”的逻辑。常见的坑是在事务里调外部接口、写文件、干耗时间把数据库连接攥在手里不放导致其他会话全部排队。6.2 连接不上数据库从报错关键词倒推原因新媒体上网一搜能看到一堆“Navicat 连接不上 MySQL”“PDO 报错”的帖子其实排查思路大同小异。以常见的 PHP PDO 报错为例SQLSTATE[HY000] [2002] Connection refused这类错误的关键词是Connection refused基本指向网络层或服务层而不是密码错误。按顺序检查MySQL 服务是否在运行。端口是否能访问telnet 127.0.0.1 3306。用户是否有远程连接权限MySQL 默认的 root 用户通常只允许 localhost 连接远程连接需要单独授权CREATE USER app% IDENTIFIED BY password; GRANT ALL PRIVILEGES ON school.* TO app%; FLUSH PRIVILEGES;防火墙和云平台安全组是否放行 3306。如果是密码类错误报错里通常会写Access denied for user。这类错误先不要反复试回到第 2 节的重置密码方案处理。6.3 性能调优从哪个方向下手“mysql性能调优”是长热词但基础阶段没必要一上来就啃几百个配置参数。我的建议是按这个顺序排查看慢查询日志找出超过 1 秒的 SQL用 EXPLAIN 分析这些 SQL 有没有走索引检查连接数是否被打满应用侧是否合理使用连接池表数据量过大时考虑分页优化、归档、分区最后才轮到调整innodb_buffer_pool_size这类内存参数。初学者最容易犯的错误是硬件参数调半天结果发现罪魁祸首是一条 SQL 没走索引。SQL 层面的问题不解决配置调得再多也没用。6.4 怎么检验自己学到了什么基础学完最好的检验不是做卷子而是做一个完整的小项目。比如把我们例子里的“学生-课程-成绩”系统用一个最简单的 JavaWeb 或 Python 脚本包起来实现录入成绩、按学生查成绩单、按课程算平均分、统计不及格人数。过程中你自然会用到数据库连接池对应热词里的“mysql的数据库连接池”事务控制成绩批量录入时多条 insert 要么全成功要么全失败索引优化按学生查成绩单时student_id有没有建索引联表查询成绩单要 join 学生表和课程表。这套流程走完你对 MySQL 的掌握就不是“背了几条命令”而是“能独立处理一个真实场景”。面试中也能很自信地把表设计、索引、事务、排查链路讲成完整的项目经验。我在实际教学和带人过程中反复看到学数据库基础最容易犯的错误不是学不会某个语法而是没有把“为什么这样设计”想清楚。建表时多深思一层字段类型写查询时多看一眼执行计划出问题时先查锁再杀进程——这些小习惯才是从入门走向熟练的真正分水岭。先把这篇文章里的内容动手练熟再去看优化器和集群相关知识你会发现后面的一切都建立在今天这些基础上。
