本文更新于 2026-03-28
基础书写规则
分号结束:每条 SQL 语句通常以分号
;结尾(虽然某些单条执行环境不强制,但在脚本中是必需的)。不区分大小写:SQL 关键字(如
SELECT,FROM)不区分大小写,但为了代码可读性,习惯上关键字大写,表名和列名小写。- 推荐写法:
SELECT name FROM users;
- 推荐写法:
空格与缩进:SQL 对空格和换行不敏感。合理的缩进能显著提高代码的可读性。
注释规范:
单行注释:
-- 注释内容或# 注释内容(仅限 MySQL)。多行注释:
/* 注释内容 */。
| 缩写 | 全称 | 操作对象 | 常用动词 | 场景示例 |
|---|---|---|---|---|
| DDL | Definition | 表结构/数据库 | CREATE, DROP | “建个名为‘用户’的表” |
| DML | Manipulation | 行记录 | INSERT, UPDATE | “把小明的年龄改成18” |
| DQL | Query | 数据结果集 | SELECT | “找出所有北京的用户” |
| DCL | Control | 权限/安全 | GRANT, REVOKE | “允许小李查看工资表” |
数据类型
整型
| 类型名称 | 取值范围 | 大小 |
|---|---|---|
TINYINT | -128〜127 | 1个字节 |
SMALLINT | -32768〜32767 | 2个宇节 |
MEDIUMINT | -8388608〜8388607 | 3个字节 |
INT (INTEGER) | -2147483648〜2147483647 | 4个字节 |
BIGINT | -9223372036854775808〜9223372036854775807 | 8个字节 |
无符号在数据类型后加 unsigned 关键字。
浮点型
| 类型名称 | 说明 | 存储需求 |
|---|---|---|
FLOAT | 单精度浮点数 | 4 个字节 |
DOUBLE | 双精度浮点数 | 8 个字节 |
DECIMAL (M, D),DEC | 压缩的“严格”定点数 | M+2 个字节 |
日期和时间
| 类型名称 | 日期格式 | 日期范围 | 存储需求 |
|---|---|---|---|
YEAR | YYYY | 1901 ~ 2155 | 1 个字节 |
TIME | HH:MM:SS | -838:59:59 ~ 838:59:59 | 3 个字节 |
DATE | YYYY-MM-DD | 1000-01-01 ~ 9999-12-3 | 3 个字节 |
DATETIME | YYYY-MM-DD HH:MM:SS | 1000-01-01 00:00:00 ~ 9999-12-31 23:59:59 | 8 个字节 |
TIMESTAMP | YYYY-MM-DD HH:MM:SS | 1980-01-01 00:00:01 UTC ~ 2040-01-19 03:14:07 UTC | 4 个字节 |
字符串
| 类型名称 | 说明 | 存储需求 |
|---|---|---|
CHAR(M) | 固定长度非二进制字符串 | M 字节,1<=M<=255 |
VARCHAR(M) | 变长非二进制字符串 | L+1字节,在此,L <= M和 1<=M<=255 |
TINYTEXT | 非常小的非二进制字符串 | L+1字节,在此,L<2^8 |
TEXT | 小的非二进制字符串 | L+2字节,在此,L<2^16 |
MEDIUMTEXT | 中等大小的非二进制字符串 | L+3字节,在此,L<2^24 |
LONGTEXT | 大的非二进制字符串 | L+4字节,在此,L<2^32 |
ENUM | 枚举类型,只能有一个枚举字符串值 | 1或2个字节,取决于枚举值的数目 (最大值为65535) |
SET | 一个设置,字符串对象可以有零个或 多个SET成员 | 1、2、3、4或8个字节,取决于集合 成员的数量(最多64个成员) |
二进制类型
| 类型名称 | 说明 | 存储需求 |
|---|---|---|
BIT(M) | 位字段类型 | 大约 (M+7)/8 字节 |
BINARY(M) | 固定长度二进制字符串 | M 字节 |
VARBINARY (M) | 可变长度二进制字符串 | M+1 字节 |
TINYBLOB (M) | 非常小的BLOB | L+1 字节,在此,L<2^8 |
BLOB (M) | 小 BLOB | L+2 字节,在此,L<2^16 |
MEDIUMBLOB (M) | 中等大小的BLOB | L+3 字节,在此,L<2^24 |
LONGBLOB (M) | 非常大的BLOB | L+4 字节,在此,L<2^32 |
DDL
CREATE(创建)
用于建立新的数据库对象。
-- 创建一个名为 users 的表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT, -- 主键,自动增长
username VARCHAR(50) NOT NULL, -- 不允许为空
email VARCHAR(100) UNIQUE, -- 唯一约束
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 默认值
);
ALTER(修改)
用于修改现有的表结构,例如增加新列、更改数据类型或重命名。
1.添加字段
ALTER TABLE 表名 ADD 字段名 类型(长度) [comment] [约束];2.修改数据类型
ALTER TABLE MODIFY 字段名 新数据类型(长度);3.修改字段名和字段类型
ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度)[comment][约束];4.删除字段
ALTER TABLE 表名 DROP 字段名;5.修改表名
ALTER TABLE 表名 RENAME TO 新表名
-- 在 users 表中增加一行“手机号”
ALTER TABLE users ADD phone_number VARCHAR(20);
-- 修改字段类型
ALTER TABLE users MODIFY COLUMN username VARCHAR(100);
-- 删除某一列
ALTER TABLE users DROP COLUMN email;
DROP(删除)
将整个对象从数据库中彻底抹去。
-- 删除整个 users 表(结构和数据全部消失)
DROP TABLE users;
-- 删除整个数据库
DROP DATABASE my_app;
TRUNCATE(清空)
用于删除表中的所有数据,但保留表结构。
- 与 DELETE 的区别:
DELETE是一行行删,速度慢;TRUNCATE是直接销毁表并重建一个空的,速度极快且不记录逐行日志。
TRUNCATE TABLE users;
DML
- DML 主要由三个核心关键字组成:
INSERT(增)、UPDATE(改)、DELETE(删)。
INSERT (插入数据)
用于向表中添加新行。
- 指定列插入:(推荐,更安全)
INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...);
INSERT INTO users (username, age, email)
VALUES ('张三', 25, 'zhangsan@example.com');
- 全列插入:(必须按表结构顺序提供所有值)
INSERT INTO 表名 VALUES (值1, 值2, ...);
INSERT INTO users VALUES (NULL, '李四', 30, 'lisi@example.com', NOW());
- 批量插入:
-- 指定字段的批量插入(推荐)
INSERT INTO 表名 (字段名1, 字段名2, ...)
VALUES
(值1, 值2, ...),
(值1, 值2, ...),
(值1, 值2, ...);
INSERT INTO student (name, age, gender)
VALUES
('张三', 20, '男'),
('李四', 19, '女'),
('王五', 21, '男');
-- 省略字段的批量插入(所有字段)
INSERT INTO 表名
VALUES
(值1, 值2, ...),
(值1, 值2, ...),
(值1, 值2, ...);
INSERT INTO student
VALUES
(4, '赵六', 20, '男'),
(5, '钱七', 18, '女');
[!IMPORTANT]
- 添加数据时,字段名与值的顺序、个数必须一一对应。
- 字符串类型和日期类型的值,需要包含在引号中。
- 插入的数据必须在字段允许的范围之内。
UPDATE (更新数据)
用于修改表中已存在的记录。
- 关键点: 永远不要忘记
WHERE子句,否则全表数据都会被修改!
UPDATE 表名 SET 字段名1 = 值1, 字段名2 = 值2, ... [WHERE 条件];
UPDATE users SET age = 26, email = 'san_new@example.com'
WHERE username = '张三'; -- 只更新张三的数据
[!NOTE] 注意:修改语句的条件可以有,也可以没有,如果没有条件,则会修改整张表的所有数据。
DELETE (删除数据)
用于删除表中的特定行。
- 关键点: 同样必须配合
WHERE使用,否则会清空整个表的数据。
DELETE FROM 表名 [WHERE 条件];
DELETE FROM users
WHERE id = 101; -- 删除 ID 为 1
[!NOTE]
- DELETE 语句的条件可以有,也可以没有,如果没有条件,则会删除整张表的所有数据。
- DELETE 语句不能删除某一个字段的值(可以使用 UPDATE)。
对比
| 维度 | DELETE (DML) | TRUNCATE (DDL) |
|---|---|---|
| 底层逻辑 | 逐行扫描与标记。像“精细手术”,逐条检查并标记删除。 | 数据页释放。像“拆迁重建”,直接解除数据页与表的映射。 |
| 事务日志 (Logging) | 全额记录。记录每一行数据的变化,以便回滚。 | 最小化记录。仅记录页面释放的操作,日志开销极小。 |
| 执行速度 | 慢。随着数据量增加,性能呈线性下降。 | 极快。无论数据量是 1 万还是 1 亿,耗时基本一致。 |
| 空间回收 (HWM) | 不释放。高水位线(High Water Mark)不变,表占用的物理空间不减小。 | 立即释放。重置高水位线,将空间还给表空间/操作系统。 |
| 自增列 (Identity) | 不重置。删除后插入新数据,ID 会接着原来的序号递增。 | 重置。将自增计数器恢复到初始状态(通常是 1)。 |
| 触发器 (Triggers) | 激活。每一行被删除时都会触发对应的 DELETE 触发器。 | 忽略。不会激活任何触发器,因为不进行逐行操作。 |
| WHERE 子句 | 支持。可以精确删除某一部分符合条件的数据。 | 不支持。只能“一锅端”,清空整个表。 |
| 回滚能力 (Rollback) | 支持。在未提交的事务中可以完全撤销。 | 视数据库而定。MySQL 等多数库不支持,PostgreSQL/SQL Server 支持。 |
| 外键约束 | 支持。受外键约束限制,若有关联数据会报错。 | 受限。如果表被外键引用,通常必须先删除外键才能执行。 |
| 锁机制 | 行级锁。执行时只锁定被操作的行,并发性较高。 | 表级锁。执行时通常会锁定整张表,阻止其他访问。 |
什么时候必须用 DELETE?
需要根据特定条件(如
status = 'expired')删除数据时。当你需要依赖 事务安全性(即如果操作失败,必须能撤回所有更改)时。
表中有复杂的 触发器 逻辑需要维持数据的一致性或审计。
什么时候必须用 TRUNCATE?
清理日志表/临时表:当数据已经过期且量级巨大(百万、千万级以上)时。
重置环境:在开发测试阶段,希望快速清空表并让自增主键归零。
优化存储:当表删除大量数据后,物理空间依然占用过高,需要通过
TRUNCATE彻底释放磁盘空间。
DQL
基本查询
- 设置别名(别名会在表头显示)
SELECT 字段1 [AS 别名1], 字段2 [AS 别名2] ... FROM 表名;
-- 使用 AS 给列起中文名
SELECT name, age AS '年龄' FROM users;
-- 直接空格加别名
SELECT name '姓名', entrydate '入职日期' FROM emp;
- **去除重复记录 **(DISTINCT)
SELECT DISTINCT 字段列表 FROM 表名;
-- 查询公司有哪些不同的工作岗位 单字段去重
SELECT DISTINCT job FROM emp;
-- 只有当 job 和 dept 同时相同时才会被去重 多字段去重
SELECT DISTINCT job, dept FROM emp;
条件查询
语法
SELECT 字段列表 FROM 表名 WHERE 条件列表;
条件
比较运算符
| 运算符 | 功能描述 | 示例代码块 |
|---|---|---|
>, >=, <, <= | 大于、大于等于、小于、小于等于 | WHERE age >= 18 |
=, <> 或 != | 等于、不等于 | WHERE job != '经理' |
| BETWEEN … AND … | 在范围内(含最小值和最大值) | WHERE age BETWEEN 20 AND 30 |
| IN(…) | 在指定的集合/列表中,多选一 | WHERE city IN ('北京', '上海', '广州') |
| LIKE 占位符 | 模糊匹配(_ 匹配单个字符,% 匹配任意字符) | WHERE name LIKE '张%' (姓张的) |
| IS NULL | 判断值是否为空 | WHERE email IS NULL |
逻辑运算符
| 运算符 | 功能描述 | 示例代码块 |
|---|---|---|
AND 或 && | 并且(多个条件必须同时成立) | WHERE age > 18 AND gender = '女' |
| OR 或 ` | ` | |
NOT 或 ! | 非、不是(取反) | WHERE age NOT BETWEEN 20 AND 30 |
聚合函数
常见聚合函数
| 函数 | 功能 | 示例代码 |
|---|---|---|
| COUNT() | 统计数量(行数) | SELECT COUNT(*) FROM emp; |
| SUM() | 求和 | SELECT SUM(salary) FROM emp; |
| AVG() | 求平均值 | SELECT AVG(salary) FROM emp; |
| MAX() | 求最大值 | SELECT MAX(age) FROM emp; |
| MIN() | 求最小值 | SELECT MIN(age) FROM emp; |
基础语法
聚合函数通常直接紧跟在 SELECT 之后:
SELECT 聚合函数(字段列表) FROM 表名 [WHERE 条件];
聚合函数注意点:
NULL 值被忽略:所有的聚合函数(尤其是
COUNT,AVG,SUM)都会自动忽略 NULL 值。
_例子_:如果 10 个人里有 2 个人没发工资(NULL),`AVG(salary)` 的结果是 `总工资 / 8`,而不是除以 10。
WHERE 的限制:聚合函数不能直接写在
WHERE子句中。
_错误写法_:`SELECT * FROM emp WHERE salary > AVG(salary);` (这是初学者最容易犯的错)。
正确做法:需要使用子查询或使用
HAVING。单值输出:聚合函数的结果是一个单行单列的数值。除非配合
GROUP BY,否则不能在SELECT中同时查询聚合函数和普通字段(如SELECT name, COUNT(*)在大多数数据库中是逻辑错误的)。
分组查询
- 基本查询
SELECT 字段列表 FROM 表名 [WHERE 条件] GROUP BY 分组字段名 [HAVING 分组后过滤条件];
-- 执行时机不同:where 是分组之前进行过滤,不满足 where 条件,不参与分组;而 having 是分组之后对结果进行过滤。
-- 判断条件不同:where 不能对聚合函数进行判断,而 having 可以.
WHERE > 聚合函数 > HAVING
注意点:
-- 逻辑 1:统计行数(包含 NULL)
-- 只要这一组有 2 行,哪怕地址全是空,结果也显示 2
SELECT workaddress, COUNT(*)
FROM emp
GROUP BY workaddress
HAVING COUNT(*) >= 2;
-- 逻辑 2:统计非空值数量
-- 虽然这一组有 2 行(满足 HAVING),但由于这两行地址是 NULL,
-- COUNT(workaddress) 统计不到任何非空值,结果显示 0
SELECT workaddress, COUNT(workaddress)
FROM emp
GROUP BY workaddress
HAVING COUNT(*) >= 2;
排序查询
- 基本查询
SELECT 字段列表 FROM 表名 [WHERE 条件] [GROUP BY 分组字段] [HAVING 过滤条件] ORDER BY 字段1 排序方式1, 字段2 排序方式2;
ASC: 升序(默认值)
DESC:降序
-- 需求:查询所有员工信息,并按照年龄升序排序
SELECT * FROM emp ORDER BY age ASC;
-- 需求:查询所有员工,按入职时间降序排序(最近入职的在前)
SELECT * FROM emp ORDER BY entrydate DESC;
- 多字段排序(二次排序)
-- 需求:按照年龄升序排序;如果年龄相同,再按照入职时间降序排序
SELECT * FROM emp ORDER BY age ASC, entrydate DESC;
[!NOTE] NULL:在 MySQL 中,
NULL被视为最小值 执行顺序:ORDER BY是在SELECT之后执行的。这意味着你可以在ORDER BY中使用你在SELECT里起的别名。
分页查询
- 基础语法
SELECT 字段列表 FROM 表名 LIMIT 起始索引, 查询记录数;
-- 查询第一页 (前 10 条)
-- 起始索引为 0 时,可以省略不写
SELECT * FROM emp LIMIT 0, 10;
-- 等价于:
SELECT * FROM emp LIMIT 10;
-- 查询第二页 (第 11-20 条)
-- 从索引 10 开始(即第 11 条),往后查 10 条
SELECT * FROM emp LIMIT 10, 10;
-- 查询第三页 (第 21-30 条)
SELECT * FROM emp LIMIT 20, 10;
计算公式: 起始索引 = (查询页码 - 1) * 每页显示记录数
例子:想看第 5 页,每页 10 条。
计算:
(5 - 1) * 10 = 40。SQL:
SELECT * FROM emp LIMIT 40, 10;
另外注意点:
方言限制:
LIMIT是 MySQL 的方言。如果用的是 Oracle,需要用ROWNUM;如果是 SQL Server,需要用TOP执行顺序:
LIMIT永远是 SQL 语句中最后执行的一个环节大分页性能:当数据量达到百万级,
LIMIT 1000000, 10会变得很慢,因为数据库需要先扫描前 100 万条再丢弃,后续需要通过索引优化或子查询来解决
执行顺序 (逻辑顺序)
理解 DQL 的关键不在于怎么写,而在于数据库怎么读。书写顺序和执行顺序是不一样的:
SELECT
字段列表 -- 5. 确定要显示的字段
FROM
表名列表 -- 1. 确定去哪张表找
WHERE
条件列表 -- 2. 第一次行过滤
GROUP BY
分组字段列表 -- 3. 数据分组
HAVING
分组后过滤条件 -- 4. 第二次组过滤
ORDER BY
排序字段列表 -- 6. 对结果进行排序
LIMIT
起始索引, 记录数; -- 7. 截取最终结果
| 执行步骤 | 关键字 | 作用 | |
|---|---|---|---|
| Step 1 | FROM | 确定去哪张表找数据。 | 表名列表 |
| Step 2 | WHERE | 按照条件过滤原始行数据。 | 条件列表 |
| Step 3 | GROUP BY | 将过滤后的数据分组(如按部门)。 | 分组字段列表 |
| Step 4 | HAVING | 对分组后的结果再次进行过滤。 | 分组后条件列表 |
| Step 5 | SELECT | 选出最终要显示的列。 | 字段列表 |
| Step 6 | ORDER BY | 对最终结果进行排序。 | 排序字段列表 |
| Step 7 | LIMIT | 限制显示的行数(分页)。 | 分页参数 |
DCL
管理用户
- 查询用户
USE mysql;
SELECT * FROM user;
- 创建用户
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';
-- 示例:创建一个只能在当前主机(localhost)登录的用户
CREATE USER 'work_user'@'localhost' IDENTIFIED BY '123456';
-- 示例:创建一个可以在任意主机(%)登录的用户
CREATE USER 'remote_user'@'%' IDENTIFIED BY '123456';
- 修改/删除用户
-- 修改用户密码 (MySQL 8.0+)
ALTER USER 'work_user'@'localhost'
IDENTIFIED WITH mysql_native_password BY 'new_password';
-- 删除用户
DROP USER 'work_user'@'localhost';
权限控制
- 常用权限
| 权限名称 | 作用描述 |
|---|---|
| SELECT | 查询数据(最常用) |
| INSERT | 插入新数据 |
| UPDATE | 修改现有数据 |
| DELETE | 删除现有数据 |
| ALTER | 修改表结构(如增加列) |
| DROP | 删除表或数据库 |
| CREATE | 创建新表或数据库 |
| ALL [PRIVILEGES] | 所有权限(最高权限) |
- 查询权限
-- 查询指定用户的权限
SHOW GRANTS FOR 'heima'@'localhost';
- 授予权限
GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名';
-- 1. 授予单个权限:允许用户查询 test 库中的 emp 表
GRANT SELECT ON test.emp TO 'heima'@'localhost';
-- 2. 授予多个权限:允许用户对 test 库所有表进行查询、插入、修改
GRANT SELECT, INSERT, UPDATE ON test.* TO 'heima'@'localhost';
-- 3. 授予所有权限:允许用户操作所有数据库的所有表(DBA 级别)
GRANT ALL ON *.* TO 'admin'@'%';
- 撤销权限
REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名';
-- 撤回用户对 test 库所有表的插入(INSERT)权限
REVOKE INSERT ON test.* FROM 'heima'@'localhost';
-- 撤回所有权限
REVOKE ALL ON *.* FROM 'admin'@'%';
豫公网安备41019702004633号