SQL 基础
SQL 是使用 MySQL 的第一层能力。零基础不要只背语法,而要理解:SQL 是你告诉数据库“我要什么结果”,数据库再根据表结构、索引和统计信息决定“怎么拿到结果”。
学习目标
学完这一页,你要能做到:
- 会写增删改查。
- 知道
where、order by、limit的执行含义。 - 知道
delete、truncate、drop的区别。 - 知道为什么线上不能无条件更新和删除。
- 会用最小 Demo 验证 SQL 行为。
SQL 执行顺序
初学者常以为 SQL 是从 select 开始执行,其实逻辑顺序不是这样。
mermaid
flowchart TD
A["from<br/>确定数据来源"] --> B["where<br/>过滤行"]
B --> C["group by<br/>分组"]
C --> D["having<br/>过滤分组"]
D --> E["select<br/>选择返回列"]
E --> F["order by<br/>排序"]
F --> G["limit<br/>截取结果"]示例:
sql
select status, count(*) as cnt
from orders
where created_at >= '2026-01-01'
group by status
having count(*) > 10
order by cnt desc
limit 10;理解这个顺序很重要,因为:
| 现象 | 原因 |
|---|---|
where 不能直接使用聚合函数 | 聚合发生在 group by 之后 |
having 可以过滤 count(*) | having 处理的是分组后的结果 |
order by 可能很慢 | 排序发生在过滤和分组之后,数据量大时成本高 |
limit 10 不代表只扫描 10 行 | 数据库可能先扫描、过滤、排序大量数据,再取 10 行 |
建表 Demo
sql
create table users (
id bigint primary key auto_increment,
username varchar(50) not null,
mobile varchar(20) not null,
status tinyint not null default 1,
created_at datetime not null,
updated_at datetime not null,
unique key uk_mobile (mobile),
key idx_status_created (status, created_at)
) engine = InnoDB default charset = utf8mb4;这张表体现了几个基础原则:
| 设计 | 目的 |
|---|---|
bigint primary key auto_increment | 使用递增主键,减少 B+Tree 随机插入 |
not null | 避免三值逻辑和索引统计复杂化 |
unique key uk_mobile | 用数据库约束保证手机号唯一 |
idx_status_created | 支持按状态查询最近用户 |
utf8mb4 | 支持中文和 emoji,避免字符集问题 |
Insert
插入一行:
sql
insert into users(username, mobile, status, created_at, updated_at)
values ('Tom', '13800138000', 1, now(), now());批量插入:
sql
insert into users(username, mobile, status, created_at, updated_at)
values
('Jerry', '13800138001', 1, now(), now()),
('Bob', '13800138002', 0, now(), now());为什么推荐批量插入:
- 减少网络往返。
- 减少 SQL 解析次数。
- 减少事务提交次数。
但不要无限大批量插入。单次太大可能导致事务太大、锁持有太久、binlog 变大、主从延迟。
Select
按主键查:
sql
select id, username, mobile
from users
where id = 1;按唯一索引查:
sql
select id, username, mobile
from users
where mobile = '13800138000';按状态查最近用户:
sql
select id, username, created_at
from users
where status = 1
order by created_at desc
limit 20;为什么不要无脑 select *:
| 问题 | 解释 |
|---|---|
| 可能无法覆盖索引 | 查了索引外字段就可能回表 |
| 网络传输更多 | 返回字段越多,网络和应用反序列化越重 |
| 表结构变更风险 | 新增大字段后,旧 SQL 也会返回它 |
| 可读性差 | 不知道业务到底需要哪些字段 |
Update
正确写法:
sql
update users
set status = 0,
updated_at = now()
where id = 1;危险写法:
sql
update users
set status = 0;没有 where 的 update 会修改整张表。线上执行更新前必须确认:
- 有没有
where。 where是否命中索引。- 影响行数是否可控。
- 是否需要先
select预览。 - 是否需要备份或灰度执行。
Delete、Truncate、Drop
| 命令 | 作用 | 是否删除表结构 | 是否逐行删除 | 常见风险 |
|---|---|---|---|---|
delete | 删除满足条件的数据 | 否 | 是 | 条件写错会删大量数据 |
truncate | 清空整张表 | 否 | 通常不是逐行删除 | 无法按条件删除,风险很高 |
drop table | 删除整张表 | 是 | 否 | 表结构和数据都没了 |
商业系统里,很多业务不会物理删除,而是逻辑删除:
sql
alter table users add column deleted tinyint not null default 0;
update users
set deleted = 1,
updated_at = now()
where id = 1;逻辑删除的好处是可以恢复、审计、避免误删;坏处是所有查询都要带 deleted = 0,数据量会持续变大,需要归档治理。
Where 条件
常见比较:
sql
where id = 1
where amount > 100
where created_at >= '2026-01-01' and created_at < '2026-02-01'
where status in (1, 2)
where username like 'tom%'不推荐:
sql
where date(created_at) = '2026-01-01'
where username like '%tom'
where mobile = 13800138000原因:
| 写法 | 问题 |
|---|---|
| 对列使用函数 | B+Tree 保存的是原始值,函数计算后可能无法利用索引顺序 |
前置 % | 不知道从索引哪个位置开始查 |
| 字符串不加引号 | 可能发生隐式类型转换,影响索引 |
分页
普通分页:
sql
select id, username, created_at
from users
where status = 1
order by created_at desc
limit 10000, 20;问题:limit 10000, 20 不是直接跳到第 10001 行,而是通常要先找到前 10020 行,再丢弃前 10000 行。
更适合大分页的写法:
sql
select id, username, created_at
from users
where status = 1
and created_at < '2026-07-01 10:00:00'
order by created_at desc
limit 20;这种叫游标分页或 seek pagination,适合订单列表、消息列表、日志列表。
商业场景
订单列表常见查询:
sql
select id, order_no, amount, status, created_at
from orders
where user_id = 1001
and created_at >= '2026-01-01'
and created_at < '2026-02-01'
order by created_at desc
limit 20;推荐索引:
sql
create index idx_user_created on orders(user_id, created_at);为什么不是只给 user_id 建索引:
user_id只能定位这个用户的所有订单。created_at可以继续缩小时间范围。- 索引顺序还能服务
order by created_at。
面试标准回答
text
SQL 基础不能只会写 select、insert、update、delete,还要理解逻辑执行顺序。MySQL 查询通常先 from 确定数据源,再 where 过滤,再 group by 分组,再 having 过滤分组,再 select 返回列,最后 order by 和 limit。limit 不代表只扫描这么多行,深分页可能扫描大量数据。线上写 update 和 delete 必须带可控 where,并确认条件命中索引。select 不建议无脑写星号,因为可能增加回表、网络传输和表结构变更风险。SQL 是否高效最终要结合索引和 EXPLAIN 判断。