MySQL这门课我接触了十年,最早也是从“数据库和表的基本操作”入门的。说实话,刚开始觉得无非就是CREATE、DROP、ALTER那几条命令,后来在真实项目里踩了无数坑,才明白基础操作里藏着大量细节,比如字符集选错导致乱码、字段类型不合适导致慢查询、误删表后怎么救。这期内容我打算把MySQL里数据库和表的基本操作完整拆一遍,不只讲语法,还会把每个操作背后的原理、适用场景、常见误区讲清楚,适合刚学SQL的同学打基础,也适合写过一段时间但体系不牢的开发者查漏补缺。
这里要说明一点:我对MySQL的版本约定是5.7+,如果你用的是8.0及以上,个别语法和默认行为有差异,我会在文中标注出来。下面直接进入正题。
1. 动手之前的准备工作:客户端选型与基础认知
1.1 图形化工具和命令行到底选哪个
很多新手一上来就问用Navicat还是用MySQL Workbench,我的建议是:图形工具可以用,但命令行必须会。
原因很简单:图形工具点按钮生成的SQL,你看不到背后发生了什么,一旦遇到生产环境没有图形界面、只能SSH连接的情况,你就懵了。还有一点,命令行能让你对SQL语句本身更敏感,比如多写一个分号、漏了一个逗号,都能立刻感受到语法的严谨性。
我个人的习惯是:本地开发用图形工具做快速数据浏览,但所有涉及表结构的变更,都先在命令行里跑一遍,确认无误后再执行。这样既高效又稳妥。常用的命令行连接方式如下:
mysql -u root -p如果指定主机和端口:
mysql -h 127.0.0.1 -P 3306 -u root -p提示:-h 默认是localhost,-P 默认是3306,如果都用默认值,直接
mysql -u root -p就行,大写P是端口,小写p是密码,别搞混了。
1.2 连接MySQL时的几个关键参数
连接MySQL不只是输入用户名密码那么简单,有几个参数直接影响后续操作。
第一个是字符集参数。连接时如果不指定字符集,即使数据库建表时用了utf8mb4,客户端传输时也可能因为连接字符集不对导致乱码。所以在命令行里,建议连接后先执行:
SET NAMES utf8mb4;或者在启动连接时指定:
mysql --default-character-set=utf8mb4 -u root -p第二个是默认数据库参数。如果不想每次写SQL都带库名,可以用-D指定默认数据库:
mysql -u root -p -D test_db连接后,当前库就是test_db,直接写SELECT * FROM users就能查到test_db.users里的数据。
第三个是超时参数。命令行连接一般不会超时,但如果你写脚本连MySQL,要关注wait_timeout和interactive_timeout,这两个值默认是8小时,看起来很长,但如果是短连接频繁建立,反而容易把连接数打满。生产环境里我更倾向于用连接池,这个问题后面有机会再说。
1.3 库和表的逻辑关系用生活例子讲透
数据库和表的关系,我习惯用“仓库”和“货架”来理解。
一个MySQL实例就像一个大型仓库,仓库里可以划分出很多独立区域,每个区域就是“数据库”。MySQL里的数据库其实是一个命名空间,它把表、视图、存储过程、函数、触发器等对象归拢在一起,方便管理和权限控制。不同数据库之间的表是隔离的,但可以在同一个实例里互相访问,前提是用户有权限。
每个数据库里可以有好多张“货架”,也就是表。表本身是二维结构,由行和列组成。列定义了数据的类型和名称,行则是具体的一条条记录。比如一张users表,列可能是id、name、email,每一行就是一个真实用户的数据。
理解了这个关系,你就知道“切换到某个数据库”只是在告诉MySQL你接下来想操作哪个区域里的表。执行USE db_name就是切换仓库区域,不切的话,就得用db_name.table_name这种完整写法来引用表。
2. 数据库的基本操作:从创建到删除的完整闭环
2.1 创建数据库的语法和字符集选择
创建数据库是一个看似简单、实则暗藏玄机的操作。最简单的写法:
CREATE DATABASE school;但这样创建出来的库,会使用MySQL服务端的默认字符集和排序规则。如果你所在环境的默认字符集是latin1,那后续存中文就会出现乱码。所以我强烈建议,任何时候建库都显式指定字符集:
CREATE DATABASE school DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;这里有两个关键参数:
CHARACTER SET是字符集,utf8mb4是UTF-8的完整实现,支持4字节的emoji和生僻字。老项目里常见的utf8其实是utf8mb3,最多3字节,根本存不了emoji。从MySQL 8.0开始,默认字符集已经是utf8mb4了,但5.7及更早版本不是,所以建库时显式指定最保险。COLLATE是排序规则,utf8mb4_unicode_ci表示基于Unicode的、大小写不敏感的排序。如果你更在意中文排序准确性,可以用utf8mb4_zh_0900_as_cs(8.0支持),但一般业务场景用utf8mb4_unicode_ci就够了。
注意:排序规则里的
_ci表示 case insensitive,即大小写不敏感。如果需要区分大小写,用_cs或_bin。utf8mb4_bin是二进制排序,比较时严格区分大小写,适合存储区分大小写的编码场景。
创建数据库时还可以判断是否存在:
CREATE DATABASE IF NOT EXISTS school DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;加上IF NOT EXISTS后,如果库已存在,MySQL不会报错,只是给一个warning。这个写法适合在初始化脚本里使用,避免反复执行报错。
2.2 查看和切换数据库
创建完数据库,先看看当前实例里有哪些库:
SHOW DATABASES;这个命令会列出当前用户有权限看到的所有数据库。如果你想看某个库的创建语句,包括字符集定义:
SHOW CREATE DATABASE school;切换到某个库:
USE school;切换后,当前会话的“默认库”就变成了school,以后写表名不需要再带库名前缀。
还有一个命令我经常用,就是查看当前所在的库:
SELECT DATABASE();如果在没切库的状态下,它会返回NULL,提醒你还没有指定默认库。
2.3 修改数据库参数与删除数据库
数据库创建之后,有些参数是可以改的,比如默认字符集和排序规则:
ALTER DATABASE school DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;这个操作只影响默认值,已经存在的表不会自动改,只有新建的表才会继承新的默认值。所以如果是线上库需要改字符集,千万别只执行这一句,后续还要对已有表逐一修改。
删除数据库:
DROP DATABASE school;加上判断:
DROP DATABASE IF EXISTS school;这个操作有多危险?一个数据库里可能有几十张表,DROP DATABASE会连同表、视图、存储过程、函数、触发器、事件全部删除,而且不走回收站。所以我在生产环境从来不敢直接执行它,非删不可时,也会先mysqldump备份,再找非业务低峰期执行,且必须有二次确认。
2.4 实操心得:字符集与排序规则别等出问题再改
我在某项目上吃过一次亏:数据库建的时候没指定字符集,默认是latin1,软件上线后中文写入全部变成“???”,只能把库里的表全部改一遍,备份恢复折腾了整晚。
所以现在的习惯是,新项目初始化时,我会写一个标准建库脚本:
CREATE DATABASE IF NOT EXISTS project_core DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;同时把所有建表语句里的字段类型也统一用utf8mb4。在MySQL 8.0里,默认字符集已经是utf8mb4,但我依然会显式声明,原因很简单:团队里如果某个人本地或测试环境的MySQL版本是5.7,默认字符集还是latin1,不显式声明就会触发迁移陷阱。答应我,建库时多写几个字符,别偷这个懒。
3. 数据表的核心操作:创建、查看、修改、删除
3.1 建表前先梳理字段类型和约束
建表之前,最忌讳的是脑子一热直接开写。我一般会先拿出一张纸,列出业务需要哪些字段、每个字段的类型是什么、要不要允许NULL、有没有默认值、主键选什么、哪些字段需要唯一约束。
比如要建一张学生表,可能包含:学号、姓名、性别、年龄、入学时间、邮箱、手机号。建表前先思考:
- 学号:字符串类型,定长且唯一,适合做主键。
- 姓名:字符串,可变长度,用VARCHAR(50)足够。
- 性别:可以用CHAR(1)存'男'/'女'或'M'/'F',也可以用ENUM。
- 年龄:要不要存?如果只是展示当前年龄,可以不存,用出生日期计算;如果要存,用TINYINT UNSIGNED即可,不需要INT。
- 入学时间:日期类型用DATE,如果还要精确到时分秒,用DATETIME。
- 邮箱:VARCHAR(100)或者VARCHAR(255),加唯一约束。
- 手机号:VARCHAR(20)或CHAR(11),因为手机号前可能有+86或分机号,VARCHAR更灵活。
这个梳理过程能帮你避免两种错误:一是把全部字段都定义成VARCHAR(255),导致索引长度受限、存储膨胀;二是对不需要NULL约束的字段放任NULL,导致查询时出现IS NULL和某个具体值的比较结果不一致的问题。
3.2 常用字段类型选择实战
字段类型选得好,表和索引效率高一截。这里整理几个高频类型,直接对照选择:
| 数据类型 | 存储容量 | 适用场景 | 注意事项 |
|---|---|---|---|
| TINYINT | 1字节 | 状态值0/1,年龄 | 有符号范围-128~127 |
| INT | 4字节 | 一般整数主键 | 范围约±21亿 |
| BIGINT | 8字节 | 雪花ID、自增主键 | 如果预期数据量过亿,用它 |
| DECIMAL(M,D) | 变长 | 金额、价格 | 绝不用FLOAT存钱 |
| DATE | 3字节 | 生日、日期 | 格式YYYY-MM-DD |
| DATETIME | 8字节 | 创建时间 | 带时区无法存储,暂不用 |
| TIMESTAMP | 4字节 | 自动记录时间 | 受2038年问题影响 |
| CHAR(N) | 定长 | 身份证号、手机号、MD5 | 长度固定时用 |
| VARCHAR(N) | 变长 | 名称、邮箱、地址 | N代表字符数,非字节数 |
| TEXT | 变长 | 长文本 | 无法全文索引,建议抽取 |
一个特别容易踩的坑是:用FLOAT存金额。浮点类型在计算机里是近似存储,计算累计金额时会出现0.1+0.2=0.30000000000000004的尴尬结果。金额必须用DECIMAL,比如DECIMAL(10,2)表示最多8位整数+2位小数,足够覆盖绝大多数业务。
3.3 创建表的完整SQL示例
回到刚才的学生表,一个完整的建表SQL长这样:
CREATE TABLE student ( id BIGINT UNSIGNED AUTO_INCREMENT COMMENT '自增主键', student_no VARCHAR(20) NOT NULL COMMENT '学号', name VARCHAR(50) NOT NULL COMMENT '姓名', gender ENUM('M','F') NOT NULL DEFAULT 'M' COMMENT '性别', birth_date DATE DEFAULT NULL COMMENT '出生日期', email VARCHAR(100) DEFAULT NULL COMMENT '邮箱', phone VARCHAR(20) DEFAULT NULL COMMENT '联系方式', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (id), UNIQUE KEY uk_student_no (student_no), UNIQUE KEY uk_email (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='学生表';这里有几个细节想重点说:
AUTO_INCREMENT自增列必须建立索引,通常就是主键。如果表里用BIGINT,那主键就用BIGINT UNSIGNED,避免自增到负数。DEFAULT CURRENT_TIMESTAMP可以在插入时不传创建时间,MySQL自动填充当前时间。ON UPDATE CURRENT_TIMESTAMP会在行更新时自动刷新updated_at,这个组合在业务表里很实用。UNIQUE KEY加在学号和邮箱上,防止重复数据。- 存储引擎明确写成
InnoDB,不要用MyISAM。原因很简单:InnoDB支持事务、行级锁、外键和崩溃恢复,MyISAM除了某些全表扫描场景,几乎没优势。
建表后可以用下面的命令确认表结构:
DESC student; SHOW CREATE TABLE student;DESC能看到列名、类型、是否为空、默认值、是否为主键等信息。SHOW CREATE TABLE能看清完整的建表语句,包括索引名、字符集、存储引擎,迁移数据库时这个输出就是标准参考。
3.4 修改表结构:ALTER TABLE的细节
项目上线后改表结构太常见了,比如加列、删列、改列类型、加索引。几个高频操作:
添加列
ALTER TABLE student ADD COLUMN class_id INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '班级ID' AFTER name;AFTER name表示把新列加在name后面。如果不想指定位置,可以不加AFTER,新列默认加在最后。
修改列类型
ALTER TABLE student MODIFY COLUMN name VARCHAR(100) NOT NULL COMMENT '姓名';MODIFY COLUMN会修改列的完整定义,包括类型、约束、注释,相当于把原来那一列重新定义一遍。如果只想改类型,其他属性也要完整写出来,否则会被重置。
修改列名
ALTER TABLE student CHANGE COLUMN gender sex ENUM('M','F') NOT NULL DEFAULT 'M' COMMENT '性别';CHANGE COLUMN比MODIFY COLUMN多了一个新列名参数,因为它既改名字又改定义。需要注意,CHANGE必须写出完整的列定义,否则默认值和注释会丢。
删除列
ALTER TABLE student DROP COLUMN class_id;添加索引
ALTER TABLE student ADD INDEX idx_name (name);删除索引
ALTER TABLE student DROP INDEX idx_name;修改表结构时,InnoDB对很多操作会重建表,比如修改列类型、添加索引。所以大表上执行ALTER,一定要看准时机。几条经验:
- 业务低峰期执行,大表重建可能锁表或复制延迟很久。
- 先确认磁盘空间足够,因为操作会产生临时表文件。
- MySQL 5.7的在线DDL能力有限,8.0优化了很多,但依然不建议上班时间在几千万行的表上直接改结构。
3.5 删除表与清空表:TRUNCATE和DELETE的区别
删除表:
DROP TABLE student;这个操作会连表结构和数据一起删掉,表里如果有外键引用,可能报错。我一般建议,如果没有100%确认表废弃,先RENAME TABLE student TO student_bak_20250101,保留一段时间,确认无影响再删。
清空表数据但不删表结构,有两个选择:
TRUNCATE TABLE student; DELETE FROM student;两者区别非常大:
TRUNCATE是DDL操作,直接释放整个表的存储空间,速度极快,执行后自增ID会从1重新开始,而且无法通过事务回滚。DELETE是DML操作,逐行删除,支持WHERE条件,速度慢,但可以回滚,自增ID不会重置。
如果只是测试数据,想快速清空并重置自增列,用TRUNCATE。如果只删部分数据或需要事务保护,用DELETE。
4. 常见问题与排查技巧实录
4.1 连接不上数据库怎么办
连接MySQL失败是新手遇到最多的坑,报错基本分几类。
第一类:ERROR 2003 (HY000): Can't connect to MySQL server on 'localhost'。这个说明MySQL服务没启动,或者端口不对。先检查服务是否在运行,比如在Linux上执行systemctl status mysqld,Windows上检查服务管理器弹窗里MySQL对应的服务是否已启动。
第二类:ERROR 1045 (28000): Access denied for user 'root'@'localhost'。这表示用户名或密码错误,也可能是该用户不允许从当前主机登录。先核对密码,如果密码忘了,需要跳过授权表重置,这个操作比较险,具体不展开,生产环境请找有经验的人操作。
第三类:ERROR 1130 (HY000): Host 'xxx' is not allowed to connect to this MySQL server。这是用户权限问题,说明你的账号只允许在某个主机登录。解决办法是授权用户允许从对应IP访问,标准的授权方式是:
CREATE USER 'app'@'10.0.0.10' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGED ON app_db.* TO 'app'@'10.0.0.10'; FLUSH PRIVILEGES;不要偷懒用'app'@'%',直接开放所有主机,权限收得越紧越好。
4.2 中文乱码的根源和解决方案
中文乱码的根源只有一个:数据某几个环节的字符集不一致。一个完整的链路包括:客户端字符集、连接字符集、服务器字符集、数据库字符集、表字符集、字段字符集。其中任何一环不一致,写入和读取就可能出问题。
排查乱码,先看当前会话的字符集:
SHOW VARIABLES LIKE 'character_set%';重点看character_set_client、character_set_connection、character_set_results这三个变量,应该都是utf8mb4。如果不是,执行SET NAMES utf8mb4;。
再看数据库、表、字段的字符集:
SHOW CREATE DATABASE school; SHOW CREATE TABLE student;如果发现表或字段是latin1或utf8,需要修改。修改表默认字符集:
ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;CONVERT TO会同时转换表字符集和所有字符串字段的字符集,但要注意,如果表里有LONGTEXT或巨大长度字段,这个操作锁表时间会很长,同样需要低峰期执行。
4.3 字段类型选错导致的数据异常
类型选错的表现是慢、错、丢。
慢:字符串类型做主键。如果用VARCHAR存手机号当主键,InnoDB的聚簇索引会非常臃肿,每次插入数据还可能要页分裂,性能远不如BIGINT自增主键。所以除非有强业务场景,否则一律用无意义的自增ID做主键,唯一业务编号用唯一索引约束。
错:日期存成字符串。很多人喜欢用VARCHAR存日期,觉得“格式自己控制比较灵活”。可一旦要按日期范围查询,字符串比较的结果和真实日期顺序可能不一致,比如'2025-1-1'按字符串排序会比'2024-12-31'靠前,导致范围查询出错。日期就用DATE或DATETIME。
丢:ID用INT但数据量增长超过21亿。MySQL INT最大2147483647,很多业务上线三五年就达到千万级,看起来离21亿很远,但如果你把订单ID直接暴露在URL里被恶意刷,或者好几个来源同时写入,自增ID耗尽的速度比想象快。预留余量用BIGINT,是成本最低的保险。
4.4 误删数据的应急处理思路
误删数据,手别抖,先做这几件事:
第一,立刻停止写入,把相关表或库设为只读,或者直接把应用停掉,防止新数据覆盖旧数据的存储空间。
第二,尽快用备份恢复。如果你有mysqldump的定时备份,把备份恢复到临时实例,然后导出需要的数据,再导回生产。日常备份命令参考:
mysqldump -u root -p school > school_20250101.sql恢复也简单:
mysql -u root -p school < school_20250101.sql第三,如果你开了binlog,并且确认binlog覆盖了误删之前的时间点,可以考虑用mysqlbinlog解析binlog,反向生成恢复语句。这个过程很复杂,通常需要专业人士现场操作。我的建议是:与其事后恢复,不如平时就养成好习惯,每次执行DROP或TRUNCATE前,先备份一遍目标库,再动手。
最后说一个关于表结构变更的土办法:改表前先用SHOW CREATE TABLE把建表语句存到本地文件,命名成student_table_bak_20250101.sql。万一改坏了,可以直接重建表再导入备份数据。这个习惯救过我很多次,推荐你也用起来。
说实话,数据库和表的基本操作,听起来基础,但越基础的东西越容易被忽视。我见过不少写了几年SQL的开发者,遇到“数据库和表的基本操作”这部分内容时不屑一顾,直到线上出现问题才回头补课。我个人从这些坑里走过后,最大的体会是:基础操作不是背语法,而是要理解MySQL在背后做了什么,字符集为什么不能乱选,表结构为什么不能上来就写,删除为什么不能无脑执行。这些认知一旦建立起来,后面学复杂查询、事务、索引调优,都会顺很多。
如果你刚接触MySQL,建议准备一个干净的测试环境,把本文的命令挨个敲一遍,建库、建表、改表、删表,每个动作执行后都用SHOW CREATE TABLE或DESC看一遍结构变化。只有亲手感受过,才能把这些细节变成肌肉记忆。下一篇我会继续聊“MySQL数据库之(二)”——插入、更新、删除数据的高级技巧,包括批量插入性能对比、UPDATE误操作防护、大事务拆分思路,到时候见。