增删改查(CRUD)是操作数据库最基础的四个动作,分别对应Create、Read、Update、Delete。不管你后面学的是Java、Python还是PHP开发,只要和mysql打交道,这四类SQL语句就天天都要写。这篇文章从零开始,先建一张学生表,再围绕这张表把插入、查询、更新、删除的常用写法逐一演示,顺便讲讲实际使用中容易踩的坑。

准备工作:建库建表
开始写CRUD之前,得先有一个数据库和一张表。假设我们要做一个学生信息管理的场景,先创建数据库并切换过去,然后建一张student表。字段设计上,id用INT类型并设置自增主键,姓名用VARCHAR,年龄用TINYINT就够存,入学时间用DATETIME记录。完整的SQL如下:
-- 创建数据库并使用
CREATE DATABASE school DEFAULT CHARACTER SET utf8mb4;
USE school;
-- 创建学生表
CREATE TABLE student (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '学生编号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
age TINYINT UNSIGNED DEFAULT 0 COMMENT '年龄',
gender CHAR(1) DEFAULT '男' COMMENT '性别',
score DECIMAL(5,2) COMMENT '成绩',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '入学时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生表';几个细节值得注意。字符集建议统一用utf8mb4而不是utf8,因为mysql的utf8其实是残缺的三字节实现,存emoji表情会直接报错。age这种小范围整数用TINYINT能省空间,UNSIGNED表示只允许非负数。score用DECIMAL而不是FLOAT,是因为浮点类型存在精度问题,涉及金额、成绩这类数据时必须用定点类型,否则可能出现0.1加0.2不等于0.3的情况。
建表完成后可以用DESC student;查看表结构,用SHOW TABLES;确认当前库下有哪些表,确认无误后再进入下一步。
新增数据:INSERT语句的各种写法
最基础的插入方式是指定字段名并给出对应的值,这是生产环境中最推荐的写法,因为它不依赖表字段顺序,后续加字段也不容易出问题:
-- 插入单条数据
INSERT INTO student (name, age, gender, score) VALUES ('张三', 18, '男', 88.5);
-- 插入多条数据
INSERT INTO student (name, age, gender, score) VALUES
('李四', 19, '女', 92.0),
('王五', 20, '男', 76.5),
('赵六', 18, '女', 84.0);这里故意没有写id和created_at字段,因为id是自增主键会自动生成,created_at有DEFAULT CURRENT_TIMESTAMP默认值,都会自动填充。如果某条插入语句执行失败,比如name字段没给值且定义为NOT NULL,mysql会直接报错,整条语句都不会生效,不会出现插一半的情况,这是InnoDB事务保证的特性。
还有一种全字段插入写法INSERT INTO student VALUES (...),省略了字段名列表,此时必须按表定义的顺序把所有字段的值都写全,包括id和created_at。这种写法一旦表结构变动就容易出错,维护性差,写练习代码时可以用,实际项目中不要这么写。
查询数据:SELECT的常用套路
查询是CRUD中变化最多的一环。最简单的查全表是SELECT * FROM student;,但生产环境强烈建议明确写出字段名,只查需要的列,减少网络传输和内存开销。下面看几种高频用法:
-- 条件查询:查年龄大于18的学生 SELECT id, name, age, score FROM student WHERE age > 18; -- 模糊查询:查姓张的学生 SELECT * FROM student WHERE name LIKE '张%'; -- 排序 + 分页:按成绩降序取前10条 SELECT id, name, score FROM student ORDER BY score DESC LIMIT 10; -- 分页:第2页,每页10条 SELECT id, name, score FROM student ORDER BY id ASC LIMIT 10 OFFSET 10; -- 聚合统计 SELECT gender, COUNT(*) AS total, AVG(score) AS avg_score FROM student GROUP BY gender;
WHERE条件里可以使用比较运算符、AND、OR、IN、BETWEEN等组合。比如WHERE age IN (18, 19)和WHERE age BETWEEN 18 AND 19效果类似。需要提醒的是LIKE模糊查询中,以通配符开头的条件如LIKE '%三'会放弃使用索引,走全表扫描,数据量大时性能很差,能避免尽量避免。
分页写法LIMIT 10 OFFSET 10表示跳过前10条再取10条,也可以简写成LIMIT 10, 10,两种写法含义相同。当偏移量非常大时,比如LIMIT 1000000, 10,mysql依然要扫过前一百万行再丢弃,效率极低,这种深分页场景需要用游标方式优化,例如WHERE id > 上次最大id LIMIT 10。
更新和删除:危险操作要谨慎
更新用UPDATE,删除用DELETE,这两条是生产事故的高发区,核心原则是永远先写WHERE条件。基本用法如下:
-- 更新:把张三的成绩改成95 UPDATE student SET score = 95.0 WHERE name = '张三'; -- 同时更新多个字段 UPDATE student SET age = age + 1, score = 90.0 WHERE id = 2; -- 删除:删除id为5的记录 DELETE FROM student WHERE id = 5; -- 删除成绩低于60分的记录 DELETE FROM student WHERE score < 60;
特别注意,如果UPDATE或DELETE忘了写WHERE条件,mysql会对全表所有记录生效。比如执行DELETE FROM student;会清空整张表。一个有效的保险措施是,执行前先把WHERE条件用SELECT跑一遍,确认查出来的就是要改的数据,再把SELECT换成UPDATE或DELETE执行。
还可以在连接层设置安全模式,执行SET SQL_SAFE_UPDATES = 1;后,如果UPDATE和DELETE的WHERE条件里没有用到主键或索引字段,mysql会直接拒绝执行,能有效防止误操作。另外DELETE和TRUNCATE都能清空表,但TRUNCATE会重置自增id且不可回滚,DELETE逐行删除可以配合事务回滚,两者适用场景不同。
对于重要的写操作,建议用事务包裹:BEGIN;执行语句后用COMMIT;提交,发现不对就ROLLBACK;回滚,给数据多加一层后悔药。掌握以上这些语句,日常开发中绝大多数的mysql增删改查需求就都能应付了。