Skip to content

MySQL基础

MySQL 基础不是只背 SQL 语法,而是学会把业务对象设计成可靠的表结构。表结构一旦上线,后面再改字段类型、主键、唯一约束、索引都会有成本,所以基础阶段要先建立正确习惯。

这一页要解决四个问题:

  1. SQL 分几类,各自负责什么。
  2. 建表时为什么要关注数据类型、主键、约束、默认值。
  3. 常用增删改查怎么写,哪些写法容易出问题。
  4. 初学者如何从一张表开始验证执行计划。

SQL 分类

类型说明示例使用场景
DDL定义数据库对象createalterdrop建库、建表、改字段
DML操作数据insertupdatedelete写入、修改、删除行
DQL查询数据select查询业务数据
DCL权限控制grantrevoke分配账号权限
TCL事务控制begincommitrollback控制一组操作的原子性

为什么要区分这些类型:

原因说明
权限不同线上通常不允许普通应用账号执行 DDL
风险不同drop table 的风险远高于普通 select
锁影响不同DDL 可能影响表结构和元数据锁
事务行为不同DML 可以被事务控制,很多 DDL 会隐式提交

建表流程

mermaid
flowchart TD
    A["分析业务对象"] --> B["确定字段含义"]
    B --> C["选择数据类型"]
    C --> D["确定主键"]
    D --> E["添加唯一约束"]
    E --> F["设计必要索引"]
    F --> G["设置 not null 和默认值"]
    G --> H["补充字段注释"]
    H --> I["用 Demo SQL 验证查询"]

如果跳过这套流程,后果通常不是马上出错,而是在数据量增加后暴露:

跳过的步骤常见后果
不分析字段含义一个字段被多个业务复用,后期无法理解
随便选类型存不下、排序错误、索引变大
没有主键InnoDB 会生成隐藏主键,查询和复制都不友好
没有唯一约束依赖代码防重,最终容易出现重复数据
不看查询设计索引上线后慢 SQL 集中爆发
允许大量 null查询条件复杂,索引统计和业务判断都容易混乱

数据类型怎么选

数值类型

场景推荐类型不推荐原因
主键 IDbigintint 容量有限,业务增长后可能不够
状态枚举tinyint用字符串保存状态会占空间,也不利于统一约束
金额decimal(10,2) 或以分为单位的 bigintfloat/double 有精度误差
计数intbigint不要用 varchar 保存数字

金额为什么不能用 float

sql
-- 浮点数是近似值,不适合做金额精确计算
create table wrong_amount (
  id bigint primary key,
  amount double not null
);

-- 更推荐
create table right_amount (
  id bigint primary key,
  amount decimal(10, 2) not null
);

字符串类型

类型适合场景注意点
varchar长度变化的名称、标题、手机号长度不是越大越好
char固定长度编码,例如性别、固定短码不适合长文本
text大段内容不适合频繁作为查询条件

varchar(50) 不是“只能存 50 字节”,而是最多 50 个字符,具体占多少字节与字符集有关。utf8mb4 下中文和 emoji 都可能占更多字节。

时间类型

类型特点建议
datetime保存日期时间,不随会话时区转换业务创建时间、更新时间常用
timestamp与时区有关,范围相对小需要时区转换时使用
date只保存日期生日、账期日期

大多数业务表可以使用:

sql
created_at datetime not null,
updated_at datetime not null

主键为什么重要

InnoDB 使用聚簇索引组织数据。主键索引的叶子节点保存整行数据,所以主键不只是“唯一标识”,还决定数据在 B+Tree 中如何组织。

mermaid
flowchart TD
    A["主键 id"] --> B["聚簇索引 B+Tree"]
    B --> C["叶子节点保存整行数据"]
    C --> D["通过主键查询路径最短"]

主键设计建议:

建议原因不这样会怎样
每张表都要有主键InnoDB 表需要聚簇索引没有显式主键时会选择唯一非空索引或隐藏 row_id
主键尽量短二级索引叶子节点会保存主键值主键越大,所有二级索引越大
主键尽量递增新记录更容易追加到 B+Tree 末尾随机主键容易页分裂和碎片
主键不要带业务含义业务规则可能变化主键变化会影响关联关系

约束不是可有可无

约束的价值是让数据库帮你兜底,而不是完全依赖应用代码。

约束作用示例
primary key唯一标识一行id bigint primary key
unique key防止业务唯一字段重复用户名、订单号
not null防止未知值状态、金额、创建时间
default给字段默认值status tinyint default 1
foreign key维护引用完整性互联网业务中不一定强依赖,需结合场景

唯一约束 Demo:

sql
create table account_user (
  id bigint primary key auto_increment,
  username varchar(50) not null,
  email varchar(100) not null,
  unique key uk_username (username),
  unique key uk_email (email)
) engine = InnoDB default charset = utf8mb4;

如果没有唯一约束,只在代码里先查再插:

sql
select id from account_user where username = 'tom';
insert into account_user(username, email) values ('tom', 'tom@example.com');

两个请求并发执行时,可能都先查不到,然后都插入成功。唯一约束可以在数据库层阻止这种重复数据。

建表示例

用户表:

sql
create table user_profile (
  id bigint primary key auto_increment comment '主键 ID',
  username varchar(50) not null comment '用户名',
  email varchar(100) not null comment '邮箱',
  status tinyint not null default 1 comment '状态:1启用,0禁用',
  last_login_at datetime null comment '最后登录时间',
  created_at datetime not null comment '创建时间',
  updated_at datetime not null comment '更新时间',
  unique key uk_username (username),
  unique key uk_email (email),
  key idx_status_created_at (status, created_at)
) engine = InnoDB default charset = utf8mb4 comment = '用户资料表';

为什么这样设计:

设计原因
id bigint auto_increment递增主键适合 InnoDB 聚簇索引
username/email unique数据库兜底防止重复账号
status tinyint状态字段小、固定,适合数字枚举
created_at/updated_at方便排序、审计、排查问题
idx_status_created_at支持按状态筛选并按创建时间分页

增删改查 Demo

插入:

sql
insert into user_profile(username, email, status, created_at, updated_at)
values ('tom', 'tom@example.com', 1, now(), now());

查询:

sql
select id, username, email
from user_profile
where status = 1
order by created_at desc
limit 20;

更新:

sql
update user_profile
set status = 0,
    updated_at = now()
where id = 1;

删除一般分两类:

sql
-- 物理删除:数据真的删除
delete from user_profile where id = 1;

-- 逻辑删除:增加 deleted 字段,用状态表示删除
update user_profile
set status = 0,
    updated_at = now()
where id = 1;

业务系统通常更常用逻辑删除,因为审计、恢复、排查问题都需要历史数据。但逻辑删除会让表持续变大,需要配合归档策略。

常用查看命令

sql
-- 查看数据库
show databases;

-- 使用数据库
use demo;

-- 查看表
show tables;

-- 查看表结构
desc user_profile;

-- 查看建表语句
show create table user_profile;

-- 查看索引
show index from user_profile;

-- 查看当前连接
show processlist;

基础阶段最容易犯的错

错误后果建议
用字符串保存数字排序和比较可能不符合预期数字用数值类型
金额用 double精度误差decimal 或整数分
主键用很长字符串二级索引膨胀用短且稳定的主键
字段大量允许 null业务判断复杂,查询容易漏条件非必要字段尽量 not null
没有唯一约束并发下出现重复数据业务唯一字段加 unique key
只建单列索引多条件查询不一定高效根据高频 SQL 设计联合索引
大表随意 alter table可能锁表或长时间变更上线前评估变更方式和窗口

练习

  1. 建一张 orders 表,包含订单号、用户 ID、状态、金额、创建时间。
  2. 给订单号加唯一索引。
  3. (user_id, created_at) 加联合索引。
  4. 写一条查询某个用户最近 20 条订单的 SQL。
  5. explain 验证是否使用了联合索引。

参考:

sql
create table orders (
  id bigint primary key auto_increment,
  order_no varchar(64) not null,
  user_id bigint not null,
  status tinyint not null default 1,
  amount decimal(10, 2) not null,
  created_at datetime not null,
  updated_at datetime not null,
  unique key uk_order_no (order_no),
  key idx_user_created_at (user_id, created_at)
) engine = InnoDB default charset = utf8mb4;

explain
select id, order_no, amount
from orders
where user_id = 1001
order by created_at desc
limit 20;

下一步建议阅读 存储结构索引知识点。基础页解决“表怎么设计”,索引页解决“为什么这样查会快”。