【计算基础|MySQL02】【上手】SQL 增删改查

【计算基础|MySQL02】【上手】SQL 增删改查

上一篇把库表建好了,这篇开始天天写的那四类 SQL:INSERT、UPDATE、DELETE、SELECT。看着简单,生产事故一半都出自这四个字------UPDATE 忘了 WHERE、SELECT * 满天飞、LIKE '%xx' 慢到崩、= NULL 查不出数据。

这篇在上一篇建的 shop 库(users / products / orders 三张表)上练手,每条 SQL 给:语法 → 实例 → 真实输出 → 易错点。结尾 16 个查询练习,全部真实跑过。

环境:Docker MySQL 8.0.46,库 shop,数据见上篇。所有输出均为真实执行结果。


一、INSERT:插入数据

1.1 三种写法

sql 复制代码
-- 写法一:标准批量插入(最常用)
INSERT INTO 表(列1,列2) VALUES (v1,v2),(v3,v4);

-- 写法二:SET 写法(列少时直观)
INSERT INTO 表 SET 列1=v1, 列2=v2;

-- 写法三:从另一张表查出来插入
INSERT INTO 表(列1,列2) SELECT 列1,列2 FROM 另一张表;

在临时表 t_crud 上实测:

sql 复制代码
CREATE TABLE t_crud(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20), city VARCHAR(20), score INT);

INSERT INTO t_crud(name,city,score) VALUES ('a','北京',90),('b','上海',80),('c','北京',70);
INSERT INTO t_crud SET name='d', city='广州', score=60;
SELECT * FROM t_crud;

输出:

text 复制代码
id  name  city  score
1   a     北京  90
2   b     上海  80
3   c     北京  70
4   d     广州  60

🔴 重点:

  • 自增主键可以不指定 ,上面没插 id,它自己从 1 往上长。
  • 批量插入性能远好于逐条 。插 1 万行,一条 VALUES (...),(...),... 比循环插 1 万次快几十倍------因为减少了网络往返和事务提交次数。
  • INSERT ... SELECT 做数据迁移/备份时很好用,但注意目标表和来源表别是同一张(会死循环)。

二、UPDATE / DELETE:不带 WHERE 就是事故

2.1 语法

sql 复制代码
UPDATE 表 SET 列=值, 列2=值2 WHERE 条件;
DELETE FROM 表 WHERE 条件;

实测(还是临时表,安全):

sql 复制代码
UPDATE t_crud SET score=score+5 WHERE city='北京';
SELECT * FROM t_crud;

输出(北京两个用户各加 5 分):

text 复制代码
id  name  city  score
1   a     北京  95
2   b     上海  80
3   c     北京  75
4   d     广州  60
sql 复制代码
DELETE FROM t_crud WHERE score<70;
SELECT * FROM t_crud;

输出(score=60 的 d 被删):

text 复制代码
id  name  city  score
1   a     北京  95
2   b     上海  80
3   c     北京  75

⚠️ 避坑(生产保命):

  1. UPDATE / DELETE 不写 WHERE 会操作全表 。UPDATE users SET balance=0; 一行 SQL 把全库余额清零。执行前先把 WHERE 条件放到 SELECT 里查一遍,确认影响的行数对不对,再改成 UPDATE。
  2. 大表删除别一次性 DELETE 全删 ,会锁表、撑爆 undo log。用 DELETE FROM t WHERE ... LIMIT 1000; 循环删,每次删一小批,中间 sleep。
  3. DELETE 是 DML 可以回滚(在事务里);TRUNCATE 是 DDL 不可回滚,区别见上篇。

三、SELECT 基础:查什么列

3.1 别再 SELECT *

sql 复制代码
-- ❌ 生产禁止
SELECT * FROM users;

-- ✅ 显式列名
SELECT id, username, balance FROM users;

为什么生产禁止 SELECT *:

问题 后果
传输量大 多查了用不到的列(比如 TEXT 大字段),浪费网络和内存
覆盖索引失效 索引里没带全列,本来能走覆盖索引,逼得回表
表结构变更脆弱 加了列,你的代码/结果集映射就跟着变

3.2 列别名与表达式

sql 复制代码
SELECT username AS 用户名, balance*1.1 AS 加息后余额 FROM users;

输出:

text 复制代码
用户名  加息后余额
alice   1100.000
bob     550.550
carol   2200.000
david   880.000
erin    330.000
frank   110.000

AS 起别名(AS 可省略),SELECT 里可以直接写表达式、函数。别名不能在 WHERE 里用(WHERE 先于 SELECT 执行),但能在 ORDER BY / GROUP BY 里用。


四、WHERE:过滤条件的全部套路

4.1 比较与逻辑运算

sql 复制代码
-- 比较:=  <>  >  <  >=  <=
SELECT username,age FROM users WHERE age >= 28;

输出:

text 复制代码
username  age
bob       30
carol     28
david     35

🔴 AND 优先级高于 OR。下面这句不是"(北京或上海)且status=1",而是"北京的全部,加上(上海且status=1)的":

sql 复制代码
SELECT username,city,status FROM users WHERE city='北京' OR city='上海' AND status=1;

输出:

text 复制代码
username  city  status
alice     北京  1
bob       上海  1
carol     北京  1
erin      上海  1

注意 david(广州)不在内,而所有北京用户(不管 status)都进来了。想按你以为的逻辑,就老老实实加括号 :(city='北京' OR city='上海') AND status=1。

4.2 IN / BETWEEN / LIKE

sql 复制代码
-- IN:等于列表中任意一个
SELECT username,city FROM users WHERE city IN ('北京','广州');
text 复制代码
username  city
alice     北京
carol     北京
david     广州
sql 复制代码
-- BETWEEN:含边界
SELECT username,age FROM users WHERE age BETWEEN 25 AND 30;
text 复制代码
username  age
alice     25
bob       30
carol     28
sql 复制代码
-- LIKE:% 任意多字符,_ 单个字符
SELECT name FROM products WHERE name LIKE '4K%';
text 复制代码
name
4K显示器

⚠️ 三个坑:

  1. NOT IN 子查询里只要有 NULL,整个结果为空 。WHERE x NOT IN (1,NULL) 永远返回空行,因为 NULL 参与的比较结果是 UNKNOWN。
  2. LIKE '%xx'(左模糊)用不了索引 。% 在前面,B+树不知道从哪开始找,只能全表扫。LIKE 'xx%'(右模糊)才能走索引。
  3. BETWEEN min AND max 含两头边界,25 和 30 都在。

4.3 IS NULL:NULL 只能这么查

sql 复制代码
SELECT username,age,city FROM users WHERE city IS NULL;
text 复制代码
username  age   city
frank     NULL  NULL

⚠️ = NULL 永远查不出东西,因为 NULL 不等于任何值,包括它自己:

sql 复制代码
SELECT COUNT(*) AS wrong_count FROM users WHERE city = NULL;
text 复制代码
wrong_count
0

🔴 记住 :判空用 IS NULL / IS NOT NULL,永远别写 = NULL。


五、ORDER BY 与 LIMIT

5.1 排序

sql 复制代码
-- 单列降序
SELECT username,balance FROM users ORDER BY balance DESC;
text 复制代码
username  balance
carol     2000.00
alice     1000.00
david     800.00
bob       500.50
erin      300.00
frank     100.00
sql 复制代码
-- 多字段:先按城市升序,同城市内按余额降序
SELECT username,city,balance FROM users ORDER BY city ASC, balance DESC;
text 复制代码
username  city  balance
frank     NULL  100.00
bob       上海  500.50
erin      上海  300.00
carol     北京  2000.00
alice     北京  1000.00
david     广州  800.00

🔴 两个细节:

  • ASC 升序是默认,DESC 降序。
  • MySQL 中 NULL 排最前(上面 frank 的 NULL city 在最上面)。
  • ORDER BY 的字段如果没索引,会触发 filesort(额外排序),数据量大时慢------这是后面索引篇要优化的重点。

5.2 LIMIT 分页

sql 复制代码
-- 前 3 条
SELECT name,price FROM products ORDER BY price DESC LIMIT 3;
text 复制代码
name      price
4K显示器  1299.00
耳机      599.00
机械键盘  399.00
sql 复制代码
-- 跳过 1 条取 2 条(第 2、3 名)
SELECT name,price FROM products ORDER BY price DESC LIMIT 1,2;
text 复制代码
name      price
耳机      599.00
机械键盘  399.00

⚠️ 深分页问题 :LIMIT 1000000,10 看着只取 10 条,MySQL 却要先扫描并丢弃前 100 万行,越翻越慢。优化手段(用主键"书签")留到索引篇讲。


六、DISTINCT 去重

sql 复制代码
SELECT DISTINCT city FROM users;
text 复制代码
city
北京
上海
广州
NULL
sql 复制代码
-- 多列是"组合去重"
SELECT DISTINCT city,status FROM users;
text 复制代码
city  status
北京  1
上海  1
广州  0
NULL  1

⚠️ DISTINCT 作用于所有查询列的组合 ,不是只去重第一列。DISTINCT city,status 是把 (city,status) 整个组合去重。要算个数用 COUNT(DISTINCT city),聚合篇讲。


七、最容易被忽视的坑:对列套函数会让索引失效

这是上篇铺垫、这篇必须亲眼看到的东西。orders 表给 created_at 建了索引后对比:

sql 复制代码
-- 写法 A:对列套函数
EXPLAIN SELECT * FROM orders WHERE DATE(created_at)='2026-09-24'\G
text 复制代码
type: ALL          ← 全表扫描
key: NULL          ← 没用上索引
possible_keys: NULL
sql 复制代码
-- 写法 B:范围查询,列本身裸露
EXPLAIN SELECT * FROM orders WHERE created_at>='2026-09-24' AND created_at<'2026-09-25'\G
text 复制代码
type: range        ← 走了索引
key: idx_created
possible_keys: idx_created

🔴 结论 :WHERE DATE(created_at)=...、WHERE id+1=2、WHERE UPPER(name)='ABC' 这类对索引列套函数/运算 ,MySQL 没法直接用索引(它得先算每一行的函数值),只能全表扫。想查某一天的数据,写成范围区间 >= 当天 0 点 AND < 次日 0 点,别套 DATE()。


总结:你真正需要记住的 9 件事

  1. 批量插入一条 VALUES(...),(...) 比逐条快几十倍;自增主键不用指定。
  2. UPDATE/DELETE 不写 WHERE 就是清全表,执行前先用 SELECT 核对影响行数。
  3. 生产禁止 SELECT *,显式写列名,省传输、保覆盖索引、抗表结构变更。
  4. AND 优先级高于 OR,条件复杂就加括号,别赌自己的逻辑。
  5. IN、BETWEEN(含边界)、LIKE(% 任意、_ 单字符);左模糊 %xx 用不了索引。
  6. 判空用 IS NULL,=NULL 永远查不出东西。
  7. ORDER BY 无索引会 filesort,NULL 默认排最前;LIMIT offset,n 深分页会越翻越慢。
  8. DISTINCT 是对所有列组合去重,不是单列。
  9. WHERE 里别对索引列套函数/运算 ,会让索引失效(type=ALL),日期范围用区间写。

验证清单

  • 能用一条批量 INSERT 插入 3 行,并说清为什么比逐条快
  • 能复现"忘了 WHERE 会操作全表"的危险,并养成先 SELECT 核对的习惯
  • 能解释 AND/OR 优先级,并用括号改对一个条件
  • 能正确用 IS NULL 查出 frank,并说明 =NULL 为什么查不到
  • 能写出 LIKE 'xx%'(走索引)和 LIKE '%xx'(不走索引)的区别
  • 能用 ORDER BY ... DESC LIMIT 3 查出价格 TOP3
  • 能用 EXPLAIN 对比出"对列套函数"和"范围查询"在 type 上的差异(ALL vs range)

参考资源


下一篇《【上手】多表查询与聚合》讲:INNER JOIN / LEFT JOIN 的区别与笛卡尔积、自连接与子查询(IN / EXISTS)、COUNT/SUM/AVG 聚合、GROUP BY + HAVING 的执行顺序,以及 UNION 与 UNION ALL。会在 shop 库上跑"每个用户的订单总额""从未下单的用户""销量 TOP3 商品"这些经典查询。

相关推荐
ITxiaobing20231 小时前
IP 库与 AppsFlyer 国家/地区数据不一致,如何系统地对账?
数据库·网络协议·tcp/ip
姜鱼问生1 小时前
密码明明一样,为什么 GPG 说“错的“?
后端·算法·架构
.Bryce.1 小时前
在智能体中使用A2A工具:让大模型自己决定“该找谁帮忙“
android·java·服务器·redis·后端·agent·a2a
xiaoqiMikko1 小时前
fastjson 1.2.84 的补丁关掉了一个缓存:addAccept 包前缀白名单下,反序列化慢了 13~28 倍
java·后端·性能优化
崎岖Qiu1 小时前
MCP 的架构、协议分层与通信过程
后端·ai·架构·agent·mcp
这个DBA有点耶1 小时前
从时间到语义:时序数据库与向量数据库的核心机制、选型与多模融合趋势
数据库·mysql·架构
Henry-SAP2 小时前
SAP PP主数据关联业务视角全解析
后端
对空六课2 小时前
RFM分层边界变化的回溯计算
大数据·服务器·前端·数据库·人工智能·算法·数据分析
小羊没烦恼!2 小时前
如何在IIS6,7中部署ASP.NET网站?
数据库·后端·python·web安全·asp.net