
MySQL 和 SQL 这两个词几乎每个写代码的人都听过但真到了要上手的时候很多人第一反应是这俩到底是不是一回事为什么我装个 MySQL 还要敲命令行为什么别人写的 SQL 我能看懂自己一写就报错我在带新人或者帮朋友调课设的时候发现大部分困惑其实都集中在最基础的那几个概念上。这篇就当是 SQL 菜鸟教程的第一篇不扯高深理论就用实际能跑通的例子把数据库、表、SQL 语句这些最核心的东西讲明白。目标读者就是零基础想入门数据库的人或者学过一点但总觉得概念含糊的同学。1. 先把 MySQL 和 SQL 的关系掰扯清楚1.1 数据库、数据库软件、SQL 是三件不同的事很多初学者最混乱的一点就是把 MySQL 和 SQL 当成同一个东西。其实它们是三个完全不同层面的概念。数据库可以简单理解成“存数据的仓库”数据最终是以文件形式存在磁盘上的。你创建了一个数据库系统就会在数据目录下生成对应的文件结构。数据库管理系统DBMSMySQL 就是这一类软件它负责管理数据库文件提供增删改查的接口处理并发、权限、备份等一堆脏活累活。除了 MySQL常见的还有 PostgreSQL、Oracle、SQL Server 等。SQL全称 Structured Query Language结构化查询语言。它是你操作数据库的“语言”无论是查询还是修改数据都通过写 SQL 语句让数据库管理系统去执行。打个比方数据库是仓库里的货架MySQL 是仓库管理员而 SQL 就是你和管理员沟通用的普通话。你用 SQL 说“把第三排货架上的所有商品列出来”MySQL 这个管理员就去帮你翻货架、取数据再把结果告诉你。所以 SQL 本身不依赖 MySQL换一个数据库软件大部分 SQL 语法依然通用。也正因为这个关系教程里经常说“学 MySQL”其实是在同时学两件事一是 MySQL 这个软件怎么装、怎么配置、怎么连接二是 SQL 语言怎么写。前者有很强的工具性后者才是真正的核心技能。1.2 MySQL 的常见使用场景MySQL 之所以这么流行是因为它属于开源的关系型数据库免费、性能不错、生态极其成熟。小到个人博客大到互联网公司核心业务系统都能见到它的身影。主流的搭配就是 Linux 服务器上跑 MySQL后端用 Java、Go、Python 等语言通过驱动去连接它。如果你还在学习阶段没必要纠结“它和 Oracle 谁更牛”“要不要直接学 PostgreSQL”这类问题。先把 MySQL 用熟练关系型数据库的基本概念表、字段、索引、事务、SQL你就都掌握了。这些概念是所有关系型数据库共通的以后换其他数据库迁移成本很低。重要的是理解模型和思维而不是纠结具体品牌的按钮位置。1.3 关于版本和工具的一点建议安装 MySQL 时建议选择官方提供的社区版版本上 5.7 和 8.0 都有大量用户在使用。8.0 是长期支持版本功能更新默认字符集已经是 utf8mb4推荐直接用 8.0。网上有很多安装教程但要注意不同版本的安装过程细节有差异不要混用。连接数据库的工具有两类。一类是官方自带的命令行客户端重新打开一个终端窗口输入mysql -u root -p就能进入 MySQL 的命令行环境。另一类是图形化工具比如 Navicat、DataGrip、DBeaver它们用起来更直观适合查看数据和写复杂查询。我的建议是刚开始学的时候命令行一定要用熟因为它是所有环境通用的也是你排查问题时的最后手段。2. 搭建环境装好 MySQL 连上客户端2.1 安装方式的选择Windows 和 macOS 上安装 MySQL 相对简单下载官方安装包一路 Next 即可。安装过程中会让你设置 root 用户的密码还会问你是否配置为 Windows 服务建议勾选这样开机后 MySQL 会自动在后台运行省去手动启动的麻烦。Linux 服务器上的安装更建议用包管理器。比如 Ubuntu 系统执行sudo apt update sudo apt install mysql-server安装完成后MySQL 服务会默认启动。检查服务状态的命令是systemctl status mysql如果是 CentOS 或者 RHEL 系列用的是yum install mysql-server服务名称可能是 mysqld。这里的细节很容易坑到新手不同发行版的包名和服务名略有差异报错“找不到 mysql-server”或者“服务不存在”时先确认一下自己的系统版本和已安装的包名。2.2 连接数据库命令行和图形工具安装好 MySQL 后第一件事就是连接上去并验证能正常执行 SQL。命令行连接方式mysql -u root -p输入密码后进入 MySQL 交互式命令行此时提示符变成了mysql。你可以先执行一个最简单的 SQL 语句验证连通性SELECT 1;如果返回1说明一切正常。接下来可以查看当前 MySQL 的版本SELECT VERSION();图形化工具连接时填的信息本质上和命令行一样主机地址本地就是 127.0.0.1、端口默认 3306、用户名root、密码。中间任何一项填错都会导致连接失败。这个排查思路要建立起来连不上数据库时先确认服务有没有启动再确认端口是否放通最后检查账号密码是否正确。2.3 环境准备阶段常见问题第一次装 MySQL 的人一半以上的时间可能会花在环境问题上而不是写 SQL 上。最常见的几个忘记了 root 密码。MySQL 有一套跳过权限验证的启动参数但生产环境千万别随便用。学习阶段如果真忘了最简单的方式是卸载重装数据不重要时重新来一遍反而更快。端口被占用。错误提示里会出现Port 3306 is already in use用netstat -ano | findstr 3306查看是谁占用了端口关掉冲突进程即可。命令行输入中文报错。这是字符集问题在连接命令里加上--default-character-setutf8mb4可以解决。3. 建库建表数据落地的第一步3.1 数据库的创建与删除环境跑通之后先从建库开始。MySQL 中一个数据库就是一个命名空间里面可以放很多张表。创建数据库的 SQL 是CREATE DATABASE school;执行后可以用SHOW DATABASES;查看当前 MySQL 实例中有哪些数据库。删除数据库用DROP DATABASE school;在实际项目中删除库是一个高危操作会把里面所有表和数据一次性清空且默认没有回收站。所以这里要养成习惯执行DROP语句前一定先确认名字最好备份或注释掉代码。切换到指定数据库USE school;后续所有建表、查询操作就都针对这个库了。也可以不切换直接在表名前加上库前缀比如school.student这种全限定名的写法在写脚本时更严谨。3.2 表结构设计基础表是关系型数据库的核心存储单元。一张表由行和列组成列定义了数据的结构类型行是具体的一条条数据。用 SQL 建表时最关键的就是确定有哪些列每列用什么数据类型以及有哪些约束。以一张学生表为例CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, gender CHAR(1) DEFAULT 男, birthday DATE, score DECIMAL(5,2), created_at DATETIME DEFAULT CURRENT_TIMESTAMP );这种写法对应了日后工作中最常见的建表模式每张表都有一个自增主键id用VARCHAR存不固定长度的字符串用DATE或DATETIME存日期时间用DECIMAL存精确小数。这几个点可以直接抄走因为它们几乎能覆盖绝大多数开发场景。3.3 数据类型怎么选MySQL 的数据类型很多但菜鸟阶段先掌握最常用的几类就够了。整数类型里INT是最常见的一般用于主键、数量、计数等字段。如果数据范围很大比如订单编号或者雪花算法生成的 ID需要用到BIGINT。需要注意INT在 MySQL 8.0 中默认就是有符号的范围大概是正负二十多亿日常够用。小数类型值得单独强调一下。FLOAT和DOUBLE是浮点型存储的是近似值用于科学计算。而涉及到金额、分数这类要求精确计算的场景一定要用DECIMAL。比如上面的DECIMAL(5,2)表示总共 5 位数字小数点后保留 2 位最大能存 999.99。用浮点型存金额很容易出精度问题这个坑我见过不止一次。字符串类型里VARCHAR是动态长度的按实际字符数分配存储空间适合存用户名、地址、手机号等长度不固定的内容。CHAR是定长字符串适合存长度固定的值比如性别、状态码、身份证号。TEXT类型可以存大段文本但要记住TEXT 字段不能在创建时给默认值并且对 TEXT 字段的查询性能通常不如普通字符串所以大文本尽量单独放一张表或放在资源存储系统里不要让主业务表太臃肿。日期时间类型用DATE只存年月日、DATETIME年月日时分秒、TIMESTAMP时间戳就够用了。这里有个细节TIMESTAMP的范围上限是 2038 年虽然还很远但新设计表时更推荐DATETIME一步到位避免以后出问题。3.4 约束的意义建表时加的各种约束不是为了为难你而是从数据库层面保护数据完整性。最常见的有四类PRIMARY KEY主键约束唯一标识每一行。NOT NULL该列不允许为空防止关键业务字段缺失。UNIQUE唯一约束保证列值不重复比如用户名、手机号经常需要加唯一约束。FOREIGN KEY外键约束用于维护表与表之间的关联关系。实际开发中外键的使用存在争议。有些团队要求一律用外键由数据库保障引用完整性另一些团队在高并发场景下会禁用外键把关联关系完全交给应用层逻辑来控制以换取更好的扩展性和写入性能。对初学者来说需要理解外键是什么、约束规则是什么但在自己的练习项目里不必强行加外键重点是先掌握基础的表设计和 SQL 编写。4. 用 SELECT 把数据查出来查询语句的核心4.1 SELECT 的基本结构有一张表之后80% 的工作都是在做查询。SELECT 语句是 SQL 里最重要的语法结构可以一句话概括为从哪张表里取出哪些列在什么条件下怎么排序要多少条。SELECT name, score FROM student WHERE score 60 ORDER BY score DESC LIMIT 10;这个语句拆开看SELECT name, score指定要返回的列。如果是SELECT *就是返回所有列但在生产环境写代码时不建议用星号因为表结构一旦变更返回的字段就会跟着变代码里强依赖字段名的地方就会出问题。FROM student指定要查哪张表。WHERE score 60过滤条件只有满足条件的行才会被返回。ORDER BY score DESC按分数从高到低排序。LIMIT 10只返回前 10 行。执行顺序上MySQL 实际是先找到表再应用 WHERE 过滤然后排序最后取前 10 条。理解执行顺序很重要因为后面写复杂 SQL 时很多错误都是因为逻辑顺序没理清。4.2 WHERE 条件过滤WHERE 子句支持的操作符需要熟记、不等于、、、、、BETWEEN、LIKE、IN、IS NULL等。查询分数达到 90 分的学生SELECT * FROM student WHERE score 90;查询姓“张”的学生用模糊匹配SELECT * FROM student WHERE name LIKE 张%;这里的%是通配符表示任意多个字符。张%匹配以“张”开头的所有名字%张%匹配名字里任意位置含有“张”的人。需要注意LIKE在数据量大时没有办法高效利用索引所以这种模糊查询不适合高频执行。如果业务里经常要做模糊搜索应该考虑专业的全文检索方案或者搜索引擎而不是让 MySQL 硬扛。查询空值要特别小心。判断一个字段是不是 NULL不能用 NULL必须用IS NULL。这一点几乎是新手必踩的坑原因在于 SQL 中 NULL 代表“未知”未知与任何值比较的结果仍然是未知不会是真。所以SELECT * FROM student WHERE birthday IS NULL;千万不要写成birthday NULL那样查不出任何结果。4.3 排序 ORDER BY 与分页 LIMIT排序的语法很简单SELECT * FROM student ORDER BY score DESC, id ASC;表示先按分数降序分数一样时按 id 升序。排序在数据量小的时候毫无压力但大表上对未索引字段排序会非常慢因为数据库需要把结果集加载到内存里做完整的排序操作。这个优化话题现在先了解即可后面学到索引时会自然理解。分页是后端开发里最常见的需求。每页 20 条第一页和第二页分别这样写SELECT * FROM student ORDER BY id LIMIT 20 OFFSET 0; SELECT * FROM student ORDER BY id LIMIT 20 OFFSET 20;LIMIT 20表示取 20 条OFFSET 0表示跳过 0 条。简写也可以写成LIMIT 0, 20第一个数字是偏移量第二个是条数。分页必须配合明确的排序否则每次都按随机顺序取翻页会乱。4.4 列去重与别名查询结果中重复的数据可以用DISTINCT去重。比如想知道学生表里一共有哪些班级就可以SELECT DISTINCT class_id FROM student;这在数据分析场景下很常用但要注意DISTINCT是对后面所有列的组合去重不是只去第一列。如果写SELECT DISTINCT class_id, name那只有当这两列组合完全相同时才算是重复。列别名用AS定义方便程序代码里引用SELECT name AS student_name, score AS exam_score FROM student;别名同样可以用在表上比如SELECT s.name FROM student AS s。给表起别名在写多表关联查询时几乎是必须的因为多张表里可能都有同名列不区分就会产生歧义。5. 增删改让数据动起来查询是最常用的操作但数据本身需要靠增删改来维护。这部分统称 DML 语句包括 INSERT、UPDATE、DELETE。5.1 INSERT 插入数据往表里插数据有三种写法。最简单的是按列顺序插入INSERT INTO student (name, gender, birthday, score) VALUES (张三, 男, 2005-03-12, 88.5);不加列名直接写值也可以但强烈不建议。因为表结构一旦调整比如中间插入一列这种写法就会报错或数据错位。显式列出列名代码可读性和稳健性都会好很多。一次插入多行数据时可以这样写INSERT INTO student (name, gender, birthday, score) VALUES (李四, 女, 2005-07-01, 92.0), (王五, 男, 2004-11-23, 76.5);一条语句插多行比逐条执行 INSERT 性能高很多尤其是初始化数据或批量导入时差别非常明显。5.2 UPDATE 更新数据更新语句的格式是UPDATE student SET score 95.0 WHERE name 张三;UPDATE 更新的是行数据可以同时更新多个列UPDATE student SET score 95.0, birthday 2005-01-01 WHERE id 1;这里最核心的黄金法则就是UPDATE 之前先明确 WHERE 条件。数据库没有后悔药一旦 WHERE 写漏了更新的是整张表的所有行。我见过不止一次开发环境里执行UPDATE student SET score 0后才发现忘了加 WHERE结果全年级成绩清空。防止这种事故的方法是把 UPDATE 拆成两步走先 SELECT 用相同条件查一遍确认影响的行数符合预期再执行 UPDATE。另外如果更新操作需要用原字段值来参与计算直接引用字段名即可。典型的需求比如全员加 5 分UPDATE student SET score score 5 WHERE class_id 1;5.3 DELETE 删除数据DELETE 的写法和 UPDATE 有点类似DELETE FROM student WHERE id 1;没有 WHERE 条件会把表中所有数据删掉。需要区分的是DELETE 只是删除数据行表结构依然存在自增主键的计数器也不会自动重置。如果想要把表结构和数据一起还原到初始状态可以用TRUNCATE TABLE student。TRUNCATE 没有 WHERE 条件速度通常比 DELETE 快但清除后无法通过事务回滚。生产环境里TRUNCATE 和 DROP 一样被归为高危操作执行权限要严格收敛。在真实业务系统里发展到现在很多团队已经不太鼓励物理删除了。设计表的时候会加一个is_deleted字段用UPDATE is_deleted 1来标记删除这种逻辑删除方式可以保留完整的历史数据。具体用物理删除还是逻辑删除取决于业务要求。5.4 关于事务的提前认知INSERT、UPDATE、DELETE 这三类语句都会修改数据而多个修改操作放在一起时会出现“要么全部成功、要么全部失败”的需求。事务就是解决这个问题的。MySQL 的 InnoDB 引擎支持事务。典型结构是START TRANSACTION; UPDATE account SET balance balance - 100 WHERE user_id 1; UPDATE account SET balance balance 100 WHERE user_id 2; COMMIT;转账场景里如果第二条语句执行失败执行ROLLBACK;第一条扣款也会被回滚不会出现钱扣了对方没收到的尴尬情况。菜鸟阶段对事务至少要有概念知道它是一条安全带在后面学习隔离级别和并发问题时会遇到更多细节。6. 菜鸟阶段必踩的坑和建议6.1 大小写与保留字Windows 上 MySQL 对表名不敏感Linux 上则区分大小写。所以开发时统一用小写表名、下划线分隔单词是最稳妥的写法比如user_profile、order_detail。这样在本地和服务器之间同步数据库不会因为系统差异出现问题。另外表名和字段名不能和 SQL 保留字冲突。比如有一张表叫orderorder在 SQL 里是排序关键字直接执行SELECT * FROM order会报语法错误。正确做法是SELECT * FROM order;这里的反引号是 MySQL 用来标识“这是一个表名或列名”的特殊符号。设计表名时能避开保留字就避开比如用orders代替order省得后面所有 SQL 都要加反引号。6.2 NULL 导致的各种意外NULL 值带来的问题极其隐蔽。比如统计全班平均分SELECT AVG(score) FROM student;如果有的学生成绩没录入是 NULLAVG 统计时这些行会被直接忽略得到的结果可能比真实平均水平偏高。又比如查询成绩大于 60 分和成绩不大于 60 分的人数如果用SELECT COUNT(*) FROM student WHERE score 60; SELECT COUNT(*) FROM student WHERE score 60;这两条语句的结果相加不等于总人数。因为 NULL 既不满足大于也不满足不大于它被两条语句同时漏掉了。理解 NULL 的三值逻辑是写 SQL 不出错的重要前提。6.3 忘记 WHERE 条件的惨痛教训前面已经提过达沃 UPDATE 和 DELETE 必须有 WHERE这里再强调一遍操作纪律。我给新人的建议是在练习阶段可以把 UPDATE 和 DELETE 语句故意拆成两行写。先写 SELECT 版本SELECT * FROM student WHERE id 1;确认结果没问题再改成 UPDATE 或 DELETE。可以用 MySQL 提供一个--safe-updates参数启动命令行它会在执行 UPDATE 或 DELETE 时强制要求 WHERE 条件或 LIMIT没有的话会拒绝执行属于开发者的一道保险。6.4 后面可以学什么这一篇侧重于基础概念和 DDL、DML 的入门操作。下一篇可以沿着这个路径继续深入多表连接的 JOIN 查询理解内连接、左连接的区别聚合函数 GROUP BY 加 HAVING做汇总统计索引的原理和设计为什么加索引查询快、写入会变慢窗口函数比如 ROW_NUMBER、RANK做排名类需求很方便存储过程和视图的使用场景慢 SQL 的排查和优化思路第一篇文章的定位是让更多人“把手弄脏”真正在 MySQL 里执行一遍最简单的操作。SQL 是一门实践性极强的语言看十篇文章不如自己在命令行里敲十条语句。遇到报错不要慌报错信息虽然英文居多但仔细读大部分问题都能解决。下一步建议你动手建一张自己的学生表插入几行数据试着把它查出来排序、过滤、翻页都走一遍。数据库的乐趣就是在你亲手把一个仓库管理得井井有条时开始的。