Skip to content

SQL 基础

SQL 是使用 MySQL 的第一层能力。零基础不要只背语法,而要理解:SQL 是你告诉数据库“我要什么结果”,数据库再根据表结构、索引和统计信息决定“怎么拿到结果”

学习目标

学完这一页,你要能做到:

  1. 会写增删改查。
  2. 知道 whereorder bylimit 的执行含义。
  3. 知道 deletetruncatedrop 的区别。
  4. 知道为什么线上不能无条件更新和删除。
  5. 会用最小 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());

为什么推荐批量插入:

  1. 减少网络往返。
  2. 减少 SQL 解析次数。
  3. 减少事务提交次数。

但不要无限大批量插入。单次太大可能导致事务太大、锁持有太久、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;

没有 whereupdate 会修改整张表。线上执行更新前必须确认:

  1. 有没有 where
  2. where 是否命中索引。
  3. 影响行数是否可控。
  4. 是否需要先 select 预览。
  5. 是否需要备份或灰度执行。

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 建索引:

  1. user_id 只能定位这个用户的所有订单。
  2. created_at 可以继续缩小时间范围。
  3. 索引顺序还能服务 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 判断。

关联学习:表设计索引EXPLAIN