☰
MySQL数据库零基础入门:安装、建表与增删改查全攻略
2026/10/11 21:37:57 网站建设 项目流程

把时间拨回我刚学数据库那会儿,带我的前辈只甩了一张网页链接和一个任务:把 MySQL 装好,然后把用户表建出来。结果那个下午我在 “Access denied” 和 “命令不存在” 之间反复横跳了整整三个小时。后来我自己带人,发现几乎每个新人都要经历一遍这个阶段——不是 MySQL 难,是没人先把 “数据库到底是用来干什么的” 这件事讲明白。

所以这篇基础篇(一),我想拉你到跟当年那个菜鸟不一样的位置:先搞懂原理,再动手实践,最后再回来看报错,你会发现那些红字其实都是提示,不是惩罚。这篇文章覆盖四块内容:数据库和 MySQL 的基本概念、安装环境准备、用命令行完成第一次连接、建库建表以及最核心的增删改查。适合完全零基础、刚准备学后端开发或数据分析的朋友,也适合那些装了 MySQL 却一直没搞懂自己在敲什么的人。

1. 数据库到底是个什么东西:先搞懂存储和查数

1.1 文件和数据库,为什么我们需要后者

很多人最开始对数据库的理解是一堆能存数据的文件。这个理解方向没错,但差别在于:你自己用 Excel 存一份用户名单,那是文件;让一百个人同时往这份名单里写数据,还能保证不错不乱,那就是数据库要干的事。

先打个生活化的比方。数据库好比一个大型仓库,里面不只有货架(表),还有一套严格的入库出库流程和保管制度。仓库里的每个货架有编号,每件货物有标签,保管员能按任何条件快速找到你要的那一批货——比如“找到所有大于 18 岁的用户”。而 Excel 或者普通文件更像你书桌上的文件夹,自己翻没问题,一旦开始多人共用、数据量变大、查询条件越来越复杂,就全乱了。

具体来说,文件方案会在三个场景里败下阵来。第一是并发写入:两个用户同时下单,都往同一个文件里写记录,后写的人很容易把先写的覆盖掉,或者文件直接锁死。第二是查询效率:几百万行数据的 CSV,你想筛出某个时间段的记录,用程序遍历一遍可能要好几秒甚至更久,而数据库通过索引几毫秒就能返回。第三是数据完整性:文件里没有约束机制,你可以随手在年龄列里写一个“abc”,但数据库在建表时就能规定只能填数字,填错了直接报错拦下。

所以数据库本质上解决的是三件事:可靠地存、高效地查、严格地管。这也是为什么几乎所有后端系统、移动 App 的背后都站着一个数据库。

1.2 关系型数据库的“关系”指的是什么

MySQL 属于关系型数据库。“关系”这个词听着抽象,翻译成人话就是:数据以表格形式存放,表与表之间可以通过公共字段产生关联。

一张表可以看成是一个二维表格,行(Row)是一条完整记录,列(Column)是一个字段属性。比如一张用户表,一行就是“张三”的全部信息,列分别是 id、用户名、邮箱、注册时间。最常见的关联例子是用户表和订单表:订单表里存一个 user_id 字段,指向用户表的某一行,于是我们就能查出“张三到底买了哪些东西”。这种通过字段把多张表连起来的能力,就是“关系”二字的由来。

那为什么这种模型能统治数据库世界几十年?因为它符合人类的直觉。你把所有业务数据拆成一张张整齐的表,就像把公司资料分门别类放进不同的文件夹,每个文件夹之间用标签互相引用,找资料时顺着标签走就行。这种模型学习成本低、表达能力强,而且有严格的理论基础——关系代数。所以哪怕后来出现了各种非关系型数据库,关系型数据库仍然是企业级应用的默认选择。

1.3 入门选 MySQL 而不是 MongoDB 的理由

新手经常问:NoSQL 不也火得很吗,我为什么不直接学 MongoDB?我的回答是:入门可以学,但打基础首选还得是 MySQL。原因有三。

第一,MySQL 免费开源、生态成熟,随手一搜就是海量中文资料,遇到问题几乎都能找到现成的解决方案。第二,SQL 是一门通用语言,你在 MySQL 上学会的 SELECT、INSERT、UPDATE,换到 PostgreSQL、Oracle、SQL Server 上一样能用,最多就是少量函数和语法有区别。而 MongoDB 的查询语法跟 SQL 完全不同,学完很难平移到其他数据库。第三,从就业和实际项目角度看,传统企业、金融、电商、内容管理系统的核心存储基本都是关系型数据库,MySQL 的岗位需求量大,用到它的场景几乎无处不在。

我不是说 NoSQL 没用,而是建议你先啃下 MySQL,建立对数据模型、索引、事务的基本认知后,再去看 Redis 或者 MongoDB 会轻松得多。数据库的核心思想永远是相通的,框架可以换,底层逻辑不会变。

2. 安装不是最难的一步,但坑比想象中多

2.1 版本选择:8.0 是底线

安装之前先解决版本问题。现在网上还有不少老教程让你装 MySQL 5.7,如果你是在维护老项目那没得选,但作为新入门选手,请直接安装 8.0 及以上的版本。

原因很实在:8.0 在性能、安全、功能上都比 5.7 强一大截。8.0 默认字符集是 utf8mb4,存中文和 emoji 不会出现莫名其妙的乱码;8.0 的默认加密插件是 caching_sha2_password,比老版本的 mysql_native_password 更安全;8.0 还加入了窗口函数、通用表表达式等现代 SQL 特性,后续进阶学习也用得上。更关键的是,MySQL 8.0 已经是当前社区的主力版本,官方对 5.7 的维护支持也已停止。如果你从 5.7 学起,后面工作真实环境大概率还要再适应一遍 8.0,没必要走这条弯路。

怎么确认自己装的是哪个版本?装完以后终端执行mysql --version就能看到。如果显示mysql Ver 8.x.x,那就没问题。

2.2 Windows 安装:Server only 就够了

Windows 上安装 MySQL 有两种主流方式,一是用安装包,二是用免安装版解压。新手建议直接选安装包。去官网下载 MySQL Installer for Windows,选那个体积大一些的完整安装包,不要选 Web 版,省得安装过程中还要联网下载组件,慢且容易失败。

安装的时候有一个步骤会问你要装哪些组件,这时候只选 MySQL Server 就够了,其它像 Workbench、Router、Shell 之类的,入门阶段用不上,后面需要再补装。组件选择界面向导会让你设 root 用户的密码,请记住这个密码,并且不要用太简单的组合。建议至少 8 位以上,包含字母数字和特殊字符。后面那步“Windows Service”配置保持默认即可,它会自动把 MySQL 注册为系统服务并开机启动。

装完有个很多人会忽略的步骤:配置环境变量。否则你每次打开命令行都得跑 C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe。手动把 bin 目录加到系统 PATH 里以后,直接在任意路径下敲mysql -uroot -p就能连接,体验会舒服很多。配好环境变量之后记得重开一个终端再试,不然缓存的旧 PATH 不生效。

2.3 Mac / Linux 安装:一条命令的事

Mac 用户强烈推荐用 Homebrew 安装,比官网 dmg 安装包清爽得多。终端执行:

brew install mysql

装完以后执行brew services start mysql启动服务。Homebrew 默认安装的 MySQL 没有密码,直接执行mysql -uroot就能进去,所以我一般建议再设置一下 root 密码:

ALTER USER 'root'@'localhost' IDENTIFIED BY '你的密码';

Linux 这边,以 Ubuntu 或 Debian 为例:

sudo apt update sudo apt install mysql-server sudo systemctl start mysql sudo systemctl enable mysql

CentOS 或 RHEL 则用:

sudo yum install mysql-server sudo systemctl start mysqld

有一点要提前说明:Linux 各发行版包管理器自带的 MySQL 版本各不相同,有些比较旧,有些甚至默认装的是 MariaDB——一个高度兼容 MySQL 的分支。如果你的包管理器里只有 MariaDB,作为入门学习问题不大,但如果你严格遵守教程里的 MySQL 8.0 特性来学习,可能部分功能会不一致。这种情况下建议去官网下载官方仓库源安装,确保拿到的是标准 MySQL。

2.4 装完先看这里:服务状态与第一个报错

安装完成不是终点,能稳定连接才是终点。不管哪个系统,装完以后先确认服务状态。Windows 可以用services.msc打开服务列表,找到 MySQL 一栏查看状态;Mac 用brew services list;Linux 用systemctl status mysql。

如果服务起不来,最常见的两个原因:一个是端口 3306 被占用,另一个是数据目录权限不对。Windows 下可以用命令行查端口占用:

netstat -ano | findstr 3306

如果看到某个进程占用了 3306,那八成是你机器上已经装过别的 MySQL 实例,或者有别的程序占了这个端口。要么停掉占用进程,要么给 MySQL 换一个端口,但新手阶段建议优先解决端口冲突,别改端口,否则后面所有教程里的命令都会因为端口对不上而报错。

还有一个被问烂的报错是 “Can't connect to MySQL server on 'localhost' ”,大概率就是服务没启动,先回去看服务状态,不要急着检查别的。学会看错误日志也是一项基本能力。Windows 安装版默认把日志放在安装目录的 data 文件夹下,Linux 一般在 /var/log/mysql/ 下,Mac 在 /usr/local/var/mysql/ 下。日志文件的扩展名是 .err,出了任何起不来的怪问题,先打开它看最后几十行,比到处问人效率高十倍。

3. 用命令行完成第一次连接

3.1 连接命令的完整形态

环境准备就绪后,第一件事就是通过命令行连上数据库。打开终端,输入:

mysql -u root -p

回车后系统会提示输入密码,这里是不会显示输入的字符的,别以为键盘坏了,直接盲打密码敲回车就行。-u指定用户名,-p表示需要密码登录。

如果你本机只有一个 root 用户,这句就够了。但如果 MySQL 端口不是默认的 3306,或者你打算连接远端的数据库,就需要补上端口和主机参数:

mysql -u root -p -h 192.168.1.100 -P 3306

-h是 host,-P是大写字母 P,指定端口。这里有个小坑:端口参数是大写的 P,密码参数是小写的 p,很多人第一次都记反,被报错折磨半天。记法很简单:password 是单词所以要小写 p,Port 是专业术语习惯用大写 P。

连接成功以后你会看到类似这样的界面:

Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 8 Server version: 8.x.x

看到这个就说明你成功进入 MySQL 的交互环境了。在这里,所有 SQL 命令都要以分号结尾,否则 MySQL 会一直傻等,然后给你一个像->这样的续行提示符。如果不小心没打分号,就多按几次回车、补上分号,或者按\c取消当前输入。

3.2 三个比“hello world”更实在的命令

进入交互模式以后,先别急着建表,在系统里逛一圈,熟悉一下环境。有一个原则贯穿始终:不要背命令,要理解命令是用来回答什么问题的。

第一个命令是SHOW DATABASES;。它回答的问题是“这台数据库服务器上现在有哪些库”。新手刚安装完通常会看到这样几个系统自带库:information_schema、mysql、performance_schema、sys。这些是数据库用来管理自身的系统库,比如 mysql 库里存了用户和权限信息。不要去动它们,看一眼知道有这些东西就行。

第二个命令是USE mysql;。它回答的问题是“我要切换到哪个库去工作”。注意:MySQL 没有一个“双击文件夹进入”的操作,就是靠USE语句切换。切换成功后命令行提示符会变成mysql>,告诉你当前正处在哪个库的上下文里。

第三个命令是SHOW TABLES;。它回答的问题是“当前这个库里有哪些表”。在系统库 mysql 里执行它,你会看到一堆和用户、权限相关的表,比如 user、db、tables_priv。如果你好奇,可以再看一下表结构——执行下面这句:

DESC user;

DESC也就是 describe 的缩写,用来查看表结构。你会发现这张系统用户表里字段非常多,很多涉及权限的字段值都是 Y/N。到这里,你就已经跟数据库建立起了一个基本的“对话链路”:找库、切库、看表、看表结构。后面所有操作都是在这个链路的基础上展开的。

3.3 创建一个属于自己的数据库

在系统库里逛完一圈,可以开始创建自己的第一个数据库了。语法很简单:

CREATE DATABASE student_demo DEFAULT CHARACTER SET utf8mb4;

这条语句做了三件事:创建一个名为 student_demo 的数据库;指定默认字符集为 utf8mb4;最后这个 utf8mb4 是重点——它是完整版的 UTF-8 编码,支持中文、日文以及 emoji 表情。如果你建库时不指定字符集,MySQL 8.0 虽然默认就是 utf8mb4,但旧版本或者部分云数据库实例可能默认还是 latin1,中文一存进去就乱码。建议不管用什么版本,建库时都显式加上这一句,养成好习惯。

创建完以后,先验证一下:

SHOW DATABASES;

应该能看到 student_demo 已经在列表里了。再进入这个库看看:

USE student_demo;

执行SELECT DATABASE();可以确认当前使用的库名。这个命令在写脚本调试时非常有用,它能直接告诉你“我现在到底在哪个库里”,避免操作错对象。

还有一条命令叫SHOW CREATE DATABASE student_demo;,它可以回看你建库时的完整定义,包括字符集和排序规则。如果你建的库字符集不对,不需要删掉重建,可以直接修改:

ALTER DATABASE student_demo DEFAULT CHARACTER SET utf8mb4;

另外,新手一定要把DROP DATABASE student_demo;这句牢记在心,但永远不要出于好奇去执行它。因为执行完数据库连带着里面所有表数据都会被立即删除,没有回收站、没有二次确认,删了就没了。我见过太多好奇心过剩的同学,刚学两天就把自己练习库删了,对着空空的SHOW DATABASES;结果欲哭无泪。

4. 建表:把数据放进格子间之前,先想清楚规则

4.1 CREATE TABLE 长什么样

数据库建好了,接下来先想清楚要往里放什么数据。建表就是在定义“格子间”的规则——每个格子叫什么名字、允许放什么类型的数据、是否允许为空、有没有默认值。语法长这样:

CREATE TABLE 表名 ( 字段名 数据类型 [约束条件], 字段名 数据类型 [约束条件], ... );

给你一个最直观的类比:表就像一张 Excel 表头,你在第一行定义列名和格式,之后每一行都是按这个格式填入的数据。如果中途想改格式,可以用ALTER TABLE修改表结构,但初学者不建议频繁改来改去。

举个例子,一张学生表,最基础的需求是存学号、姓名、年龄、注册时间:

CREATE TABLE students ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL, age TINYINT UNSIGNED DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这一小段代码涉及好几个关键点,展开说一下:id 是整数且不允许为空,AUTO_INCREMENT表示自增,每次插入数据会自动加一,省得手工维护编号;name 定为 50 个字符,中文按字符数计算,够用;age 用 TINYINT UNSIGNED 表示年龄够用且省空间;created_at 默认值CURRENT_TIMESTAMP表示插入时自动取当前时间,不需要你在 INSERT 里手动写时间;ENGINE=InnoDB使用 InnoDB 存储引擎,支持事务,这是 MySQL 的默认引擎,日常业务表建议全部用它;DEFAULT CHARSET=utf8mb4指定表字符集,保证中文不乱码。

4.2 数据类型:选错了后面会很难受

新手建表时最常见的错误不是语法,而是随意选类型。类型选不对,要么浪费存储空间,要么存不进去,要么计算时出现诡异错误。

整数类型从小到大依次是 TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT。TINYINT 范围是 -128 到 127(无符号是 0 到 255),适合存年龄、状态码;INT 范围到 21 亿,适合大多数业务表的主键;BIGINT 范围更大,适合存储量极大的场景。一般规则是能用小的就别用大的,但新手也不用太纠结,拿不准就选 INT,总不会错。

小数是个大坑。存金额、单价这类数据,千万不要用 FLOAT 或 DOUBLE。原因是浮点数在计算机中用近似值存储,0.1 + 0.2 的结果可能不是 0.3,在做金额计算时会产生非常隐蔽的偏差。正确做法是用 DECIMAL 或 NUMERIC,需要指定总位数和小数位。比如DECIMAL(10,2)表示总共 10 位,其中小数占 2 位,整数最多 8 位,99999999.99 这个量级对绝大多数场景都足够了。

字符串类型最常用的是 VARCHAR 和 CHAR。VARCHAR 是可变长度,按实际内容存储,适合用户名、标题这种长度不定的文本;CHAR 是固定长度,适合身份证号、手机号这类长度固定的字段。需要注意:VARCHAR(50) 里的 50 是字符数上限,不是字节数。中文一个字算一个字符,所以 VARCHAR(50) 能存 50 个汉字,不按字节算。

日期类型有 DATE、DATETIME、TIMESTAMP 三种。DATE 存年月日;DATETIME 存年月日时分秒,范围更大;TIMESTAMP 也存年月日时分秒,范围比 DATETIME 小,但会受时区影响。业务系统里存“创建时间”,DATETIME 用得最多,配上DEFAULT CURRENT_TIMESTAMP几乎零成本维护。

4.3 主键、自增、默认值:新手最容易忽略的三件套

主键是什么?一句话:表中每条记录的唯一身份证。主键字段不允许重复,不允许为空。有了主键,数据库才能精确定位一行数据。最常见的主键是自增数字 id:id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY。这一行里 NOT NULL 表示不允许为空,AUTO_INCREMENT 表示自动递增,PRIMARY KEY 表示设为主键。

很多新手会问:不用自增主键行不行?用身份证号、手机号做主键行不行?技术上允许,但强烈不建议。原因在于:业务字段做主键意味着一旦业务变化,比如手机号可以改号或注销,主键就失去了稳定性。而自增无意义 id 是纯粹的数字编号,稳定、省空间、性能好。这条经验在真实项目里被验证过无数次,能省很多麻烦。

自增还有一个值得注意的小细节:删除最后几条数据后,自增计数不会回退。比如你删了 id 为 5、6 的记录,下一条插入的数据 id 是 7,而不是 5。这个行为是正常的,新手第一次看到时经常以为自己操作出了问题,其实数据库就是故意这么设计的,避免主键在被删除后立刻复用引发数据错乱。

DEFAULT默认值也很重要。它解决的问题是:插入数据时如果没有提供某个字段的值,数据库用默认值来填充。比如年龄默认 0,创建时间默认当前时间。这样应用层就不用每次显式传递所有字段。配合NOT NULL使用,还能防止脏数据进入表。NOT NULL表示这个字段必须填;DEFAULT表示没填时落在某个值上。两者组合能极大提高数据质量。

4.4 一张完整的用户表和文章表长什么样

光说不练假把式,下面给出一对完整示例:用户表和文章表,模拟一个最简单的博客系统。

CREATE TABLE users ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL, age TINYINT UNSIGNED DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_email (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='博客用户表';

users 表里UNIQUE KEY uk_email (email)这句话的意思是给 email 字段加唯一约束:同一封邮箱不能注册两次。如果你不了解唯一索引的直接效果,看示例就行:尝试插入两个相同 email 的用户,第二条会报错。

文章表就复杂一些,因为它要和用户表关联:

CREATE TABLE posts ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content TEXT NOT NULL, user_id INT UNSIGNED NOT NULL, status TINYINT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user (user_id), CONSTRAINT fk_posts_user FOREIGN KEY (user_id) REFERENCES users(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='博客文章表';

这里出现了两个新概念:KEY idx_user (user_id)是给 user_id 建索引,加快速按作者查询文章的速度;FOREIGN KEY是外键,它告诉数据库:posts 表里的 user_id 必须存在于 users 表的 id 中,否则插入会被拒绝。外键是维护数据完整性的利器,知道它是干什么的即可,详细操作和取舍以后专门写一篇讲。

我见过不少新手建完表就忘掉了表结构,所以再强调一次,想随时回顾表结构,执行DESC posts;或SHOW CREATE TABLE posts;都能看。DESC看字段清单,SHOW CREATE TABLE看完整的建表语句。后面一种还能直接复制出来发给别人复现你的表结构,非常实用。

5. 增删改查:以后 90% 的 SQL 都是这四件事

5.1 INSERT:把数据装进去

表建好了,往里面塞数据用 INSERT。标准语法:

INSERT INTO users (username, email, age) VALUES ('zhangsan', 'zhangsan@example.com', 22);

如果按表的所有字段顺序插入,可以省略字段名列表,直接:

INSERT INTO users VALUES (1, 'lisi', 'lisi@example.com', 25, NOW());

但省略字段名有一个风险:一旦表结构调整,字段顺序变了,你的插入语句就会悄悄插错列。入门阶段我强烈建议显式列出字段名,宁可多敲几个字,也换一个心安。

一次插入多行数据的写法要掌握,批量插入比循环单条插入高效得多:

INSERT INTO users (username, email, age) VALUES ('wangwu', 'wangwu@example.com', 20), ('zhaoliu', 'zhaoliu@example.com', 30), ('sunqi', 'sunqi@example.com', 27);

执行 INSERT 后,MySQL 会返回类似Query OK, 3 rows affected的结果。有经验的开发都会看一眼这个输出,确认影响行数符合预期。如果实际影响行数和预期不一样,说明插入逻辑里多半有意外,早发现比晚发现成本低得多。

插入时最常见的报错之一:Field 'email' doesn't have a default value。这是因为你的字段设了 NOT NULL 但没有默认值,插入时又没给它传值。解决办法就是插入语句里补上字段值,或者改表结构让它允许为空、给一个默认值。

5.2 SELECT:按条件把数据捞出来

SELECT 是 SQL 世界里出场率最高的语句,也是新手最需要花时间精雕细琢的部分。它的字面意思是“选取”,核心套路是先确定从哪张表取,再确定要哪些列,再追加筛选条件。

最简单的是全表查询:

SELECT * FROM users;

*表示所有列。但实际开发中尽量少用*,因为网络传输的数据量会变大,而且表结构一变,查出来的列就变了。入门阶段没问题,工作了以后要有意识地只查自己需要的列:

SELECT username, email FROM users;

条件筛选用 WHERE,这是 SELECT 的灵魂。下面几种写法覆盖了最常见的场景:

-- 单一条件 SELECT * FROM users WHERE age > 20; -- 多个条件,AND 和 OR SELECT * FROM users WHERE age > 20 AND age < 30; SELECT * FROM users WHERE age < 20 OR age > 60; -- 范围 SELECT * FROM users WHERE age BETWEEN 20 AND 30; -- 枚举多值 SELECT * FROM users WHERE age IN (20, 25, 30); -- 模糊匹配,% 代表任意多个字符 SELECT * FROM users WHERE username LIKE 'zhang%';

模糊查询 LIKE 是新手最容易出错的地方。LIKE 'zhang%'匹配的是以 zhang 开头的字符串,LIKE '%zhang%'匹配的是包含 zhang 的字符串。_下划线在 LIKE 里代表任意单个字符,LIKE 'zhang_'匹配 zhang 后面跟任意一个字符的记录,但匹配不到 zhang 开头的完整单词。入门阶段先记住%的通配含义就够了,下划线用到的时候再查。

排序和分页是另外两个高频需求。排序用 ORDER BY,默认升序,降序要加 DESC:

SELECT * FROM users ORDER BY created_at DESC;

分页用 LIMIT。有两种常见写法,含义不同,一定要分清楚:

-- 只取前 5 条 SELECT * FROM users LIMIT 5; -- 跳过 10 条,再取 5 条,这是真正的分页写法 SELECT * FROM users LIMIT 10, 5;

LIMIT 10, 5的语义是“从第 11 条开始取 5 条”,第一个数字是偏移量 offset,第二个数字是数量 count。很多新手的坑就在于把第一个数字当成数量,结果分页数据越翻越乱。这里有个好记的口诀:offset 是“前面要跳过多少条”,count 是“这次要拿多少条”。

WHERE 里的比较运算符还有一点需要提醒:判断某个字段是否为 NULL 时,不能用=空值,要用IS NULL或IS NOT NULL。比如查没有填邮箱的用户,正确写法是SELECT * FROM users WHERE email IS NULL;。写成WHERE email = NULL会查不出任何数据,因为 NULL 在 SQL 里代表“未知”,未知与未知比较的结果依然未知,不会被条件匹配到。

5.3 UPDATE 和 DELETE:动手之前先问自己一句“WHERE 加了吗”

UPDATE 和 DELETE 这两条语句是新手事故高发区,因为它们的杀伤力完全取决于 WHERE。先看正常写法:

UPDATE users SET age = 23 WHERE username = 'zhangsan'; DELETE FROM users WHERE username = 'zhaoliu';

UPDATE 的语法结构是“把哪张表里的哪些记录,把哪一列改成什么值,限定条件是哪些行”。SET 后面可以同时改多个字段:

UPDATE users SET age = 24, email = 'newzhangsan@example.com' WHERE id = 1;

DELETE 的语法更简单:从哪张表删哪些行。简单意味着危险也简单——如果你把 WHERE 忘写了,或者 WHERE 条件写得过于宽泛,那结果就是:

-- 这会把 users 表所有行全删掉 DELETE FROM users;

没有后悔药。MySQL 默认开了事务自动提交,一条 DELETE 执行完,数据立刻永久删除。所以我的习惯是:在写 UPDATE 或 DELETE 之前,先用相同 WHERE 条件跑一次 SELECT:

SELECT * FROM users WHERE username = 'zhaoliu';

确认查出来的行数正是你打算修改或删除的那些,再把 SELECT * 换成 UPDATE 或 DELETE。这个习惯能拦住 80% 的误操作事故。

另外补充一点:DELETE 删除数据不会重置自增主键计数;TRUNCATE 清空整张表并重置自增计数,速度也更快。但 TRUNCATE 是一条 DDL 语句,操作粒度非常粗,一旦执行无法按行回退,实战中要格外谨慎。入门阶段记住它和 DELETE 的区别就够了。

5.4 新手最容易翻车的几个语法细节

第一,分号问题。SQL 语句默认以分号结束。终端里输入多行 SQL,如果不打分号,MySQL 会一直显示续行符->等你继续输入。看起来像“卡住了”,其实只是没收到语句结束信号。按\c可以取消当前输入。

第二,字符串引号问题。SQL 里字符串用单引号,不是双引号。虽然部分模式双引号也能用,但会带来歧义,建议统一单引号。字符串里的中文、英文标点都要注意,如果内容本身包含单引号,需要写两个单引号转义,比如:

INSERT INTO posts (title) VALUES ('It''s a good day');

第三,关键字冲突。有些单词既是 SQL 保留字,又是你想起的表名或字段名。比如 order、group、desc、select 在 SQL 里都有特殊含义。给表起名时尽量避开这些词。实在要用,可以加反引号包住:`order`。反引号在 MySQL 里专门用来转义标识符。

第四,中英文标点。新手经常在 SQL 里混入中文括号、中文分号、中文逗号,MySQL 不会识别中文标点,直接报语法错误,而且报错位置有时看起来很奇怪。遇到syntax error,先检查引号和标点是不是英文的,比仔细研读报错信息更快解决问题。

第五,字段名拼写。SQL 报错Unknown column 'username' in 'field list'说明字段打错了;报错Table 'xxx' doesn't exist说明表名打错了。这两个报错非常直白,照着错误信息改就行。注意字段名是区分大小写的——在 MySQL 里表名的大小写行为跟操作系统有关,字段名则严格区分大小写,所以写的时候保持和自己建表时完全一致。

6. 跟着走一遍:从 0 到 1 搭一个迷你博客数据库

6.1 建库建表全过程(含预期输出)

前面讲了一堆理论和零散命令,这里把所有东西串起来,完整跑一遍。打开终端,从连接到建库建表,一条都不用跳过。

mysql -u root -p

输入密码进入交互环境,首先建一个练习库:

CREATE DATABASE IF NOT EXISTS blog_demo DEFAULT CHARACTER SET utf8mb4; USE blog_demo;

这里出现了IF NOT EXISTS,意思是“如果库不存在才创建”。加上它之后,重复执行建库语句不会报错。然后执行前面设计好的两张表:

CREATE TABLE IF NOT EXISTS users ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL, age TINYINT UNSIGNED DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_email (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='博客用户表'; CREATE TABLE IF NOT EXISTS posts ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content TEXT NOT NULL, user_id INT UNSIGNED NOT NULL, status TINYINT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user (user_id), CONSTRAINT fk_posts_user FOREIGN KEY (user_id) REFERENCES users(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='博客文章表';

每一条 CREATE TABLE 执行成功后都会返回Query OK和影响行数 0。然后验证一下建表结果:

SHOW TABLES;

预期输出是两张表:

+---------------------+ | Tables_in_blog_demo | +---------------------+ | posts | | users | +---------------------+

看到这个结果,你手上的练习环境就算正式搭起来了。

6.2 写入几条数据并查询

有了表和表结构,先给 users 表插入 3 位用户:

INSERT INTO users (username, email, age) VALUES ('zhangsan', 'zhangsan@example.com', 22), ('lisi', 'lisi@example.com', 25), ('wangwu', 'wangwu@example.com', 20);

执行后返回3 rows affected。再往 posts 表插 2 篇文章,user_id 对应刚才的用户 id:

INSERT INTO posts (title, content, user_id) VALUES ('我的MySQL学习笔记', '这是第一篇文章的内容。', 1), ('数据库安装踩坑记', '这是第二篇文章的内容。', 2);

这里注意,我没有手动指定 id,数据库自动生成;user_id 填的是 users 表里的真实自增 id。如果你想看看自己插入的数据长什么样,执行:

SELECT * FROM users; SELECT * FROM posts;

预期输出是两张以表格形式展示的二维数据。你会发现 users 的 id 自动从 1 开始递增,created_at 自动生成了当前时间。这两点如果不到位,说明建表语句或者数据库模式有问题,回去检查字符集和默认值设置。

再来几个组合查询。先找出年龄大于 21 的用户:

SELECT username, age FROM users WHERE age > 21 ORDER BY age;

再按创建时间倒序看看最早注册的是谁:

SELECT username, created_at FROM users ORDER BY created_at DESC LIMIT 1;

一个稍微复杂一点的需求:查用户名里包含 “lisi” 的那篇文章标题和作者。这需要两张表进行关联,关联条件就是 posts.user_id 等于 users.id:

SELECT u.username, p.title FROM users u JOIN posts p ON p.user_id = u.id WHERE u.username = 'lisi';

这里出现了 JOIN 连接和表别名 u、p。入门阶段先把概念记住:JOIN 就是“把两张表在某个共同字段上拼起来”,u、p 是表的短名字,起别名后写起来省事。多表连接用不用得溜,直接决定你后面能处理多复杂的数据需求,这篇文章先留个印象,之后专门展开讲。

6.3 更新与删除的演练

模拟一个非常真实的需求:zhangsan 把邮箱改了,同时想把年龄改成 23。正确动作是先查到这个用户,再执行更新:

SELECT id, username, email FROM users WHERE username = 'zhangsan'; UPDATE users SET email = 'newzhangsan@example.com', age = 23 WHERE username = 'zhangsan';

执行后返回1 row affected。注意,如果把 zhangsan 的 username 改成空字符串,UPDATE 会执行成功但是数据变得没有意义,因为 username 字段 NOT NULL 约束只挡 NULL,不挡空字符串。这是新手经常忽略的细节:数据库层面的约束只能保证“有值”,不能保证“值合理”。

再模拟一条删除操作。假设 wangwu 注销了账号,需要把用户和文章一起处理,但这里有个需要小心的点:文章表里如果有 wangwu 的文章,外键约束会拦住删除,因为 posts 表还引用着他的 user_id。所以真实业务里一般先删关联文章,再删用户:

DELETE FROM posts WHERE user_id = 3; DELETE FROM users WHERE username = 'wangwu';

如果文章的 user_id 字段建表时没有外键,直接删用户会导致文章变成“无主数据”,后续关联查询会查不到作者信息。这种数据孤儿问题就是为什么需要外键约束的原因之一。入门阶段不需要掌握复杂的级联删除配置,但要建立起一个意识:删除主表数据之前,先想想还有没有别的表引用着它。

6.4 一个小检查:这张表目前到底存了些什么

实战的最后一步,用一条组合查询整体检查练习库的状态。需求是:统计每个用户的文章数量。这个需求用到了 GROUP BY 分组、COUNT 聚合函数、JOIN 连接,是入门阶段非常经典的一道练习题:

SELECT u.username, COUNT(p.id) AS post_count FROM users u LEFT JOIN posts p ON p.user_id = u.id GROUP BY u.id, u.username;

解释一下这条语句的每个环节:LEFT JOIN 保证即使某个用户一篇文章都没写,他也会出现在结果里,文章数为 0;COUNT(p.id) 统计每个分组下的文章数量;GROUP BY 把同一用户的多篇文章合并到一行;AS 给聚合结果列起一个别名 post_count。

执行后你大概会看到类似这样的输出:

+----------+------------+ | username | post_count | +----------+------------+ | zhangsan | 1 | | lisi | 1 | +----------+------------+

到这里,你已经跑通了从建库、建表、插入、查询、更新、删除到分组聚合的一整条链路。这条链路里出现的每条命令,在真实工作中每天都在被人反复使用。把这套流程在终端里亲手敲三遍以上,比看十篇教程都管用。

7. 常见问题速查:入门期遇到过的坑我替你踩完了

7.1 登录相关问题

登录阶段遇到最多的是ERROR 1045 (28000): Access denied for user 'root'@'localhost'。报错意思很直接:密码不对,或者这个用户没有本地登录权限。解决思路是先确认密码有没有记错,注意密码是区分大小写的,输入的时候 Caps Lock 有没有打开也检查一下。如果确定密码忘了,需要进入“重置密码”流程,最通用的做法是启动 MySQL 时临时跳过权限验证。具体步骤是:停掉 MySQL 服务,启动时加--skip-grant-tables参数,然后免密登录,修改密码,再重启服务恢复。这个方法入门阶段可以用,但生产环境千万别这么玩,等于把大门敞开了。

还有一类报错是ERROR 2003 (HY000): Can't connect to MySQL server on 'localhost:3306'。这说明服务根本没起来,或者端口不对。先确认服务状态,再看端口占用,不要一上来就怀疑防火墙。

ERROR 1049 (42000): Unknown database 'blog_demo'也很常见,说明你 USE 了一个不存在的库。如果你确定库存在,那可能是大小写问题;MySQL 在 Linux 上表名区分大小写,而数据库名和表名是否区分在某些配置下表现不同,保险起见统一用小写。

7.2 中文字符集乱码问题

插入中文后发现存进去的是乱码,或者查询结果里中文变成了一串问号“???”,这就是典型的字符集问题。排查思路分三步。

先看数据库和表的字符集设置:

SHOW CREATE DATABASE blog_demo; SHOW CREATE TABLE users;

这两条命令的输出里都有 CHARSET= 的值。如果是 utf8mb4,没问题。接着看客户端和数据库连接层的字符集,登录后执行:

SHOW VARIABLES LIKE 'character_set%';

重点关注 character_set_client、character_set_connection、character_set_results 三个值。它们应该一致。如果不一致,最简单粗暴的修复方法是先执行:

SET NAMES utf8mb4;

它会同时把上面三个字符集都设成 utf8mb4。但注意,这个设置只对当前会话有效,关掉终端重连就没了。要永久生效,应该在 MySQL 配置文件里配好默认字符集,Windows 一般在安装目录下的 my.ini,Linux 在 /etc/mysql/my.cnf。往 [mysqld] 段和 [client] 段分别加上 default-character-set=utf8mb4 或 character-set-server=utf8mb4,然后重启服务。

最后一个隐藏坑:你要连接的客户端工具本身也影响显示。终端编码如果不是 UTF-8,那数据库没问题、连接没问题,你看到的依然是乱码。Windows 的默认命令行对 UTF-8 支持比较差,这是客户端工具的问题,不是 MySQL 的问题。解决办法是用chcp 65001切换终端代码页,或者直接用图形化客户端工具连数据库。

7.3 服务起不来、端口被占

Windows 下 MySQL 服务无法启动,先打开事件查看器,或者直接看 MySQL 安装目录 data 下的 .err 日志文件。常见原因有三个:3306 端口被某个旧进程占用;data 目录权限不对导致无法读写;上次异常关机导致 InnoDB 日志文件出现损坏标记。

端口占用排查命令已经提过:

netstat -ano | findstr 3306

看到了占用进程后,可以打开任务管理器,按 PID 找到对应进程。如果确认那个进程是废进程,结束它再启动 MySQL。如果不知道是谁占用的,先查 PID 对应的进程名称,不要盲杀,免得把别的服务干崩了。

Linux 的排查思路一致,命令换成:

ss -tlnp | grep 3306 sudo systemctl status mysql journalctl -u mysql --no-pager | tail -50

日志永远是最可靠的排查入口。记住顺序:看日志、看端口、看配置。比到处问人先试三招,大部分问题都能自己解决。

7.4 其他让人头皮发麻的报错

  • ERROR 1064 (42000): You have an error in your SQL syntax:语法错误。最常见的原因是标点符号用了中文、单引号没写全、关键词写错。去检查语句本身,不要试图修复报错位置以外的内容。

  • ERROR 1054 (42S22): Unknown column 'xxx' in 'field list':字段名不存在。多半是拼写错误或者表里确实没有这一列。执行DESC 表名;看看真实字段名。

  • ERROR 1146 (42S02): Table 'xxx' doesn't exist:表不存在。确认下你有没有先 USE 正确的库,以及表名大小写是否一致。

  • ERROR 1366 (HY000): Incorrect string value:插入的中文或特殊字符在当前字符集下无法存储。优先排查表字符集是不是 utf8mb4。

  • ERROR 1364 (HY000): Field 'xxx' doesn't have a default value:某字段设为 NOT NULL 且没有默认值,但插入时没给它传值。补上值,或者修改表结构让它可空、加默认值。

  • ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails:外键约束被触发。你插入的关联字段在父表里找不到对应记录,或者删除时还有子表引用着它。先查父表数据,对应关系理顺了再操作。

把这些报错和对应解法复制保存到自己的笔记里。我敢保证,以后你写 SQL 的第一次报错,大概率就在这个清单里。

结尾:几句掏心窝的话

这套入门基础篇写到这里,其实把新手最常卡住的几道坎都过了一遍:数据库到底在解决什么问题、MySQL 怎么装、命令行怎么连、表怎么建、数据怎么增删改查、报错怎么排查。我个人在实际带新人时有一个很深的体会:凡是能把这篇文章里的命令从头到尾手动敲五遍,还自己动手记了笔记的人,后面学索引、学事务、学多表连接,基本都是一路顺风。数据库这门技术没有太多需要死记硬背的,真正的门槛是熟练度和对数据模型的直觉。

最后再分享一个小技巧:学 MySQL 别只盯着操作,试着给自己设计一个“问题清单”,比如“我如何查出最近一周注册的用户”“如果同一邮箱出现两条数据怎么找出并删除重复”这类问题,然后逼自己写 SQL 去解决。带着问题去学,比从头到尾背语法效率高得多。下一篇我们准备聊索引和事务——如果你已经能把建库、建表、增删改查跑顺,那就准备好往更深处钻吧。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询