MySQL 数据库 SQL 语句完全指南:新手必学的50个实战案例(附源码)
MySQL 数据库 SQL 语句完全指南
对于 Java 后端开发者来说,SQL 不是加分项,而是核心必备技能。它直接决定你能否高效处理数据、保障系统性能,甚至影响项目的稳定性和你的职业竞争力。
本文从基础到进阶,系统讲解 MySQL 常用 SQL 语句,涵盖 90% 以上的实际开发场景。建议边看边实践,在本地数据库中运行示例代码,才能真正掌握。
基础知识
注释
单行注释(最常用)
语法:-- 注释内容(注意:-- 后面必须加一个空格)
作用:注释单行内容,从 -- 开始到行尾的内容都会被数据库忽略。
多行注释(块注释)
语法:/* 注释内容 */(可跨多行)
作用:注释多行内容,适合详细说明 SQL 的业务逻辑、参数含义等。
SQL 执行顺序
理解 SQL 的执行顺序对于写出正确的查询语句至关重要:
1. FROM, JOIN
2. WHERE
3. GROUP BY
4. HAVING
5. SELECT
5.1 窗口函数计算
6. ORDER BY
7. LIMIT
错误示范:在 WHERE 中使用聚合函数
-- 错误:WHERE 执行时聚合函数还未计算
SELECT NAME, salary, AVG(salary)
FROM user
WHERE salary > AVG(salary);
-- 正确写法1:使用子查询
SELECT NAME, salary, (SELECT AVG(salary) FROM user) AS avg_salary
FROM user
WHERE salary > (SELECT AVG(salary) FROM user);
-- 正确写法2:使用窗口函数
SELECT NAME, salary, AVG(salary) OVER () AS avg_salary
FROM user
WHERE salary > (SELECT AVG(salary) FROM user);
一、DML(数据操作)
示例表结构
为了让示例更贴近实际,先创建一个 user 表并插入测试数据:
-- 创建用户表
CREATE TABLE `user` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
`age` INT,
`gender` VARCHAR(10),
`salary` DECIMAL(10,2),
`dept` VARCHAR(50),
`create_time` DATE
);
-- 插入测试数据
INSERT INTO `user` (name, age, gender, salary, dept, create_time) VALUES
('张三', 25, '男', 5000.00, '研发部', '2024-01-10'),
('李四', 30, '男', 8000.00, '研发部', '2024-02-15'),
('王五', 28, '女', 6500.00, '市场部', '2024-01-20'),
('赵六', 35, '男', 9000.00, '销售部', '2024-03-05'),
('孙七', 26, '女', 5500.00, '市场部', '2024-02-28'),
('周八', NULL, '男', 7000.00, '研发部', '2024-01-18'),
('吴九', 32, '女', NULL, '销售部', '2024-03-10');
SELECT 查询
避免使用 SELECT *:生产环境必须指定字段,如 SELECT name, age FROM user,这样可以减少网络传输和磁盘 IO,且便于索引覆盖。
| 语法点 | 示例 SQL | 说明 |
|---|---|---|
| 字段筛选 | SELECT * FROM user; |
查询所有字段(开发中尽量避免) |
| 指定字段 | SELECT name, age, salary FROM user; |
只查询需要的字段 |
| 别名 | SELECT name AS 姓名, age AS 年龄 FROM user; |
AS 可省略,给字段起别名 |
| 去重 | SELECT DISTINCT dept FROM user; |
查询所有不重复的部门 |
INSERT 插入
| 语法点 | 示例 SQL | 说明 |
|---|---|---|
| 单行插入 | INSERT INTO user (name, age, gender, salary, dept) VALUES ('郑十', 29, '男', 7500.00, '研发部'); |
指定字段插入 |
| 多行插入 | INSERT INTO user (name, age, gender, dept) VALUES ('钱十一', 27, '女', '市场部'), ('孙十二', 31, '男', '销售部'); |
一次插入多条,效率更高 |
UPDATE 更新
| 语法点 | 示例 SQL | 说明 |
|---|---|---|
| 单字段更新 | UPDATE user SET salary = 8500.00 WHERE name = '李四'; |
必须加 WHERE 避免全表更新 |
| 多字段更新 | UPDATE user SET age = 26, salary = 5800.00 WHERE name = '孙七'; |
同时更新多个字段 |
| 危险操作 | UPDATE user SET salary = 6000.00; |
会更新全表,生产环境禁用 |
DELETE 删除
| 语法点 | 示例 SQL | 说明 |
|---|---|---|
| 带条件删除 | DELETE FROM user WHERE name = '郑十'; |
只删除指定记录 |
| 先查后删 | SELECT * FROM user WHERE dept = '销售部';DELETE FROM user WHERE dept = '销售部'; |
先确认再删除 |
| 危险操作 | DELETE FROM user; |
会删除全表数据,生产环境禁用 |
二、条件与排序
WHERE 条件筛选
| 运算符 | 示例 SQL | 说明 |
|---|---|---|
| = | SELECT * FROM user WHERE gender = '女'; |
查询所有女性用户 |
| >, <, >=, <= | SELECT * FROM user WHERE age > 30; |
查询年龄大于 30 的用户 |
| LIKE | SELECT * FROM user WHERE name LIKE '张%'; |
匹配以"张"开头的姓名 |
| IN | SELECT * FROM user WHERE dept IN ('研发部', '市场部'); |
查询指定部门的用户 |
| BETWEEN | SELECT * FROM user WHERE salary BETWEEN 5000 AND 8000; |
查询薪资在范围内的用户 |
| IS NULL | SELECT * FROM user WHERE age IS NULL; |
查询年龄为空的用户 |
| AND/OR | SELECT * FROM user WHERE dept = '研发部' AND age < 30; |
多条件组合查询 |
ORDER BY 排序
| 语法点 | 示例 SQL | 说明 |
|---|---|---|
| 升序 | SELECT name, age FROM user ORDER BY age ASC; |
ASC 可省略,默认升序 |
| 降序 | SELECT name, salary FROM user ORDER BY salary DESC; |
按薪资降序排列 |
| 多字段排序 | SELECT name, dept, age FROM user ORDER BY dept ASC, age DESC; |
先按部门升序,再按年龄降序 |
三、聚合与分组
聚合函数
| 函数 | 示例 SQL | 说明 |
|---|---|---|
| COUNT(*) | SELECT COUNT(*) AS 总用户数 FROM user; |
统计所有用户数量 |
| COUNT(字段) | SELECT COUNT(age) AS 年龄非空用户数 FROM user; |
统计非空记录数 |
| SUM() | SELECT SUM(salary) AS 研发部总薪资 FROM user WHERE dept = '研发部'; |
计算总和 |
| AVG() | SELECT AVG(salary) AS 市场部平均薪资 FROM user WHERE dept = '市场部'; |
计算平均值 |
| MAX() | SELECT MAX(salary) AS 最高薪资 FROM user; |
查询最大值 |
| MIN() | SELECT MIN(age) AS 最小年龄 FROM user WHERE age IS NOT NULL; |
查询最小值 |
GROUP BY 分组
| 语法点 | 示例 SQL | 说明 |
|---|---|---|
| 基础分组 | SELECT dept, COUNT(*) AS 部门人数 FROM user GROUP BY dept; |
按部门分组统计 |
| 多字段分组 | SELECT dept, gender, AVG(salary) AS 平均薪资 FROM user GROUP BY dept, gender; |
按多个字段分组 |
| 分组规则 | SELECT dept, MAX(name), COUNT(*) FROM user GROUP BY dept; |
SELECT 中的字段要么在 GROUP BY 中,要么被聚合函数包裹 |
HAVING 分组后过滤
| 语法点 | 示例 SQL | 说明 |
|---|---|---|
| HAVING 使用 | SELECT dept, AVG(salary) AS 平均薪资 FROM user GROUP BY dept HAVING AVG(salary) > 6000; |
筛选平均薪资大于 6000 的部门 |
| WHERE vs HAVING | SELECT dept, COUNT(*) AS 人数 FROM user WHERE age > 25 GROUP BY dept HAVING COUNT(*) > 1; |
WHERE 先过滤,HAVING 后过滤 |
分组的意义:将零散的单条记录,按指定维度聚合为统计结果,满足业务分析需求。
四、多表连接查询
注意:当 SELECT 的字段在多表中存在时,必须加上表别名,如 u.name 而不是 name。
-- 示例表结构
CREATE TABLE `user` (
`user_id` INT PRIMARY KEY AUTO_INCREMENT,
`user_name` VARCHAR(50) NOT NULL,
`age` INT
);
CREATE TABLE `order` (
`order_id` INT PRIMARY KEY AUTO_INCREMENT,
`user_id` INT,
`product_id` INT,
`create_time` DATE,
`amount` DECIMAL(10,2)
);
CREATE TABLE `product` (
`product_id` INT PRIMARY KEY AUTO_INCREMENT,
`product_name` VARCHAR(100) NOT NULL,
`price` DECIMAL(10,2)
);
-- 插入测试数据
INSERT INTO `user` (user_name, age) VALUES ('张三', 25), ('李四', 30), ('王五', 28);
INSERT INTO `order` (user_id, product_id, create_time, amount) VALUES
(1, 1, '2024-01-10', 100.00),
(1, 2, '2024-01-15', 200.00),
(2, NULL, '2024-02-01', 150.00);
INSERT INTO `product` (product_name, price) VALUES ('手机', 1000.00), ('耳机', 200.00);
内连接(INNER JOIN)
内连接只返回多个表中满足关联条件的交集记录。
三表内连接示例:
-- 查询有订单且订单关联了商品的用户信息
SELECT u.user_name, o.order_id, p.product_name, o.amount
FROM `user` u
INNER JOIN `order` o ON u.user_id = o.user_id
INNER JOIN `product` p ON o.product_id = p.product_id;
左连接(LEFT JOIN)
以左边的表为主表,主表的所有行都会被保留;右边的表只匹配关联的行,匹配不上的字段显示 NULL。
两表左连接示例:
-- 查询所有用户的订单信息,无订单的用户也会显示
SELECT u.user_name, o.order_id, o.amount
FROM `user` u
LEFT JOIN `order` o ON u.user_id = o.user_id;
三表左连接示例:
-- 查询所有用户的完整订单和商品信息
SELECT u.user_name, o.order_id, p.product_name, o.amount
FROM `user` u
LEFT JOIN `order` o ON u.user_id = o.user_id
LEFT JOIN `product` p ON o.product_id = p.product_id;
右连接(RIGHT JOIN)
右连接与左连接类似,只是基准表在右侧。实际开发中几乎可以完全用左连接替代。
SELECT u.user_name, o.order_id, o.amount
FROM `order` o
RIGHT JOIN `user` u ON o.user_id = u.user_id;
笛卡尔积(CROSS JOIN)
笛卡尔积会生成两表的所有组合,实际开发中很少使用。
-- 无条件笛卡尔积(结果集通常无意义)
SELECT u.user_name, p.product_name
FROM `user` u
CROSS JOIN `product` p;
显式关联 vs 隐式关联
推荐使用显式关联(INNER JOIN),语义更清晰:
-- 显式关联(推荐)
SELECT u.user_name, o.order_id
FROM `user` u
INNER JOIN `order` o ON u.user_id = o.user_id;
-- 隐式关联(不推荐,老项目才有)
SELECT u.user_name, o.order_id
FROM `user` u, `order` o
WHERE u.user_id = o.user_id;
多表关联复杂场景
SELECT
u.user_name,
o.order_id,
p.product_name,
o.amount,
o.create_time
FROM `user` u
LEFT JOIN `order` o ON u.user_id = o.user_id
LEFT JOIN `product` p ON o.product_id = p.product_id
WHERE o.create_time LIKE '2024-01%'
ORDER BY o.amount DESC;
子查询
子查询是嵌套在另一个 SQL 语句内部的查询。
注意:三层及以上的嵌套子查询不推荐使用,可读性差,性能一般,难以维护。
单行子查询(用 =):
-- 查询订单 id 为 3 的用户信息
SELECT user_id, user_name, age
FROM `user`
WHERE user_id = (SELECT user_id FROM `order` WHERE order_id = 3);
多行子查询(用 IN):
-- 查询购买了耳机的用户信息
SELECT user_id, user_name, age
FROM `user`
WHERE user_id IN (
SELECT DISTINCT user_id
FROM `order`
WHERE product_id = (
SELECT product_id
FROM `product`
WHERE product_name = '耳机'
)
);
上面的例子可以用内连接完成,性能更好:
-- 内连接实现
SELECT DISTINCT u.user_id, u.user_name, u.age
FROM `user` u
INNER JOIN `order` o ON u.user_id = o.user_id
INNER JOIN `product` p ON o.product_id = p.product_id
WHERE p.product_name = '耳机';
五、高频基础语法
LIMIT 分页
| 语法点 | 示例 SQL | 说明 |
|---|---|---|
| 基础分页 | SELECT * FROM user LIMIT 5; |
只返回前 5 条记录 |
| 偏移量 | SELECT * FROM user LIMIT 10, 5; |
跳过前 10 条,取接下来 5 条 |
| 分页优化 | SELECT * FROM user WHERE id > 20 LIMIT 5; |
避免大偏移量性能问题 |
CASE WHEN 条件分支
-- 将性别编码转中文,按薪资分级
SELECT
name,
CASE gender
WHEN '男' THEN '男性'
WHEN '女' THEN '女性'
ELSE '未知'
END AS 性别_中文,
CASE
WHEN salary >= 8000 THEN '高薪'
WHEN salary >= 6000 THEN '中薪'
ELSE '底薪'
END AS 薪资等级
FROM user;
自增主键重置
-- 清空表并重置自增主键
TRUNCATE TABLE user;
-- 仅重置自增主键(保留数据)
ALTER TABLE user AUTO_INCREMENT = 1;
NULL 处理
| 函数 | 示例 SQL | 说明 |
|---|---|---|
| IFNULL | SELECT name, IFNULL(salary, 0) AS 薪资 FROM user; |
薪资为 NULL 时显示 0 |
| COALESCE | SELECT name, COALESCE(mobile, telephone, email, '无法联系') AS 联系方式 FROM customer; |
按顺序取第一个非 NULL 值 |
COALESCE 比 IFNULL 更常用的原因:
- 标准 SQL,跨数据库通用(MySQL、PostgreSQL、SQL Server、Oracle 都支持)
- 支持多参数,更灵活
- 代码可读性更好
-- COALESCE 多级备选
SELECT
COALESCE(mobile, wechat, email, telephone, '无联系方式') AS contact
FROM customers;
-- IFNULL 嵌套很混乱
SELECT IFNULL(a, IFNULL(b, IFNULL(c, '默认'))) FROM table;
-- COALESCE 清晰明了
SELECT COALESCE(a, b, c, '默认') FROM table;
六、常用函数
EXISTS 函数
EXISTS 是一个布尔型判断函数,用来检查子查询是否返回至少一行结果:
- 子查询返回 ≥1 行 → EXISTS 为 TRUE → 保留该行
- 子查询返回 0 行 → EXISTS 为 FALSE → 过滤该行
EXISTS 只关心子查询有没有结果,不关心具体返回什么值,所以子查询里写 SELECT 1 或 SELECT * 效果相同,推荐写 SELECT 1,性能略优。
基本语法:
SELECT 外部查询字段
FROM 主表 别名
WHERE EXISTS (
SELECT 1
FROM 子查询表 别名
WHERE 子查询表.关联字段 = 主表.关联字段
);
示例:
-- 查询下过单的用户信息
SELECT user_id, user_name, user_city
FROM user u
WHERE EXISTS (
SELECT 1
FROM `order` o
WHERE o.user_id = u.user_id
);
-- 查询没有下过单的用户信息
SELECT user_id, user_name, user_city
FROM user u
WHERE NOT EXISTS (
SELECT 1
FROM `order` o
WHERE o.user_id = u.user_id
);
EXISTS vs IN 对比:
| 对比项 | IN | EXISTS |
|---|---|---|
| 执行逻辑 | 先查出子查询所有结果,再和主表匹配 | 主表遍历一行,子查询只查是否存在,找到即停 |
| 大数据量性能 | 差(子查询结果大时,内存占用高) | 优(无需缓存子查询结果) |
| 空值处理 | 子查询返回 NULL 时,IN 结果为 FALSE | 不受空值影响 |
实战建议:
- 子查询关联的表是大表时,优先用 EXISTS
- 子查询是固定小集合时(如
IN (1,2,3)),用 IN 更简洁
窗口函数
窗口函数在不聚合行的情况下,对数据的"窗口"进行计算,保持原始行不变,同时为每行添加计算结果。
基本语法:
函数名() OVER (
[PARTITION BY 分组字段]
[ORDER BY 排序字段]
[ROWS/RANGE 范围子句]
)
三大窗口函数类型:
- 聚合类窗口函数(新手用的最多)
| 函数 | 说明 | 示例 |
|---|---|---|
| AVG() | 平均值 | AVG(salary) OVER(PARTITION BY dept) |
| SUM() | 求和 | SUM(sales) OVER(ORDER BY date) |
| COUNT() | 计数 | COUNT(*) OVER(PARTITION BY dept) |
| MAX()/MIN() | 最大/最小值 | MAX(salary) OVER() |
-- 为每行添加部门平均工资和公司平均工资
SELECT
name,
dept,
salary,
AVG(salary) OVER(PARTITION BY dept) AS dept_avg,
AVG(salary) OVER() AS company_avg
FROM employees;
- 排名类窗口函数
| 函数 | 特点 | 示例结果 |
|---|---|---|
| ROW_NUMBER() | 连续唯一排名 | 1,2,3,4,5 |
| RANK() | 相同值同排名,留空位 | 1,2,3,3,5 |
| DENSE_RANK() | 相同值同排名,不留空 | 1,2,3,3,4 |
| NTILE(n) | 分为 n 组 | 1,1,2,2,3,3 |
-- 部门内工资排名
SELECT
name,
dept,
salary,
ROW_NUMBER() OVER(PARTITION BY dept ORDER BY salary DESC) AS rank_in_dept,
RANK() OVER(ORDER BY salary DESC) AS company_rank
FROM employees;
- 偏移类窗口函数
| 函数 | 作用 | 说明 |
|---|---|---|
| LAG(expr, n) | 获取前 n 行的值 | 常用于计算环比 |
| LEAD(expr, n) | 获取后 n 行的值 | 常用于计算同比 |
| FIRST_VALUE(expr) | 窗口第一个值 | |
| LAST_VALUE(expr) | 窗口最后一个值 | 注意默认范围 |
-- 计算工资月度环比增长
SELECT
month,
salary,
LAG(salary, 1) OVER(ORDER BY month) AS prev_month,
salary - LAG(salary, 1) OVER(ORDER BY month) AS month_over_month
FROM monthly_salary;
关键子句:
PARTITION BY - 定义窗口分区
-- 按部门分区
AVG(salary) OVER(PARTITION BY department)
-- 多字段分区
ROW_NUMBER() OVER(PARTITION BY year, month)
ORDER BY - 窗口内排序
-- 排序影响排名、累计计算等
SUM(sales) OVER(PARTITION BY dept ORDER BY date)
窗口范围子句 - 定义滑动窗口
-- 常用范围定义
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
ROWS BETWEEN 2 PRECEDING AND 1 FOLLOWING
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
重要特性:
- 不减少行数(与 GROUP BY 的本质区别)
- 可多窗口并存
- 支持嵌套
- 通常优于多重子查询
字符串函数
| 函数名 | 核心用途 | 示例 |
|---|---|---|
| CONCAT() | 拼接字符串 | CONCAT(user_name, '-', user_city) |
| TRIM() | 去除首尾空格 | TRIM(product_name) |
| SUBSTRING() | 截取字符串 | SUBSTRING(product_name, 1, 5) |
| LENGTH() | 计算字符串长度 | LENGTH(user_name) > 4 |
| UPPER()/LOWER() | 转大写/小写 | UPPER(product_name) |
示例:
-- 拼接用户名和城市
SELECT user_id, CONCAT(user_name, '-', user_city) AS user_info
FROM user LIMIT 10;
-- 清洗商品名首尾空格
UPDATE product SET product_name = TRIM(product_name);
-- 截取商品名前 5 个字符
SELECT product_id, SUBSTRING(product_name, 1, 5) AS short_name
FROM product LIMIT 10;
-- 筛选用户名长度大于 4 的用户
SELECT user_id, user_name
FROM user
WHERE LENGTH(user_name) > 4;
-- 商品名转大写
SELECT product_id, UPPER(product_name) AS upper_name
FROM product LIMIT 10;
日期时间函数
| 函数名 | 核心用途 | 示例 |
|---|---|---|
| NOW()/CURDATE() | 获取当前时间/日期 | DATE(order_time) = CURDATE() |
| YEAR()/MONTH() | 提取年/月 | YEAR(order_time) = 2025 |
| DATE_SUB() | 日期减 | order_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) |
| DATEDIFF() | 计算日期差 | DATEDIFF(NOW(), register_time) |
示例:
-- 查询今日订单
SELECT order_id, user_id, order_time
FROM `order`
WHERE DATE(order_time) = CURDATE();
-- 查询 2025 年 3 月的订单
SELECT order_id, order_time, total_amount
FROM `order`
WHERE YEAR(order_time) = 2025 AND MONTH(order_time) = 3;
-- 查询近 7 天的订单
SELECT order_id, order_time
FROM `order`
WHERE order_time >= DATE_SUB(NOW(), INTERVAL 7 DAY);
-- 计算用户注册天数
SELECT user_id, user_name,
DATEDIFF(NOW(), register_time) AS days_since_register
FROM user LIMIT 10;
数值函数
| 函数名 | 核心用途 | 示例 |
|---|---|---|
| ROUND() | 四舍五入 | ROUND(price, 1) |
| FLOOR()/CEIL() | 向下/向上取整 | FLOOR(total_amount) |
| ABS() | 取绝对值 | ABS(refund_amount) |
示例:
-- 商品价格四舍五入保留 1 位小数
SELECT product_id, product_name, price,
ROUND(price, 1) AS price_rounded
FROM product LIMIT 10;
-- 订单金额向下取整
SELECT order_id, total_amount,
FLOOR(total_amount) AS total_amount_floor
FROM `order` LIMIT 10;
-- 退款金额取绝对值
SELECT order_id, refund_amount, ABS(refund_amount) AS refund_abs
FROM `order` WHERE order_id = 1;
条件函数
| 函数名 | 核心用途 | 示例 |
|---|---|---|
| IF() | 双条件判断 | IF(total_amount > 100, '高', '低') |
| IFNULL() | 替换 NULL 值 | IFNULL(SUM(total_amount), 0) |
| CASE WHEN | 多条件判断 | CASE WHEN price < 50 THEN '低价' ELSE '高价' END |
示例:
-- 判断订单金额是否超过 100 元
SELECT order_id, total_amount,
IF(total_amount > 100, '高额订单', '低额订单') AS order_level
FROM `order` LIMIT 10;
-- 左连接查询用户订单总额,无订单则显示 0
SELECT u.user_id, u.user_name,
IFNULL(SUM(o.total_amount), 0) AS total_spend
FROM user u
LEFT JOIN `order` o ON u.user_id = o.user_id
GROUP BY u.user_id, u.user_name
LIMIT 10;
-- 给商品按价格分级
SELECT product_id, product_name, price,
CASE
WHEN price < 50 THEN '低价商品'
WHEN price BETWEEN 50 AND 500 THEN '中价商品'
ELSE '高价商品'
END AS price_grade
FROM product LIMIT 10;
七、进阶查询
批量操作优化
批量更新/删除:避免一次性操作全表,分批处理(比如每次 1000 条)
-- 分批更新薪资低于 5000 的用户
UPDATE user SET salary = salary * 1.1 WHERE id IN (
SELECT id FROM user WHERE salary < 5000 LIMIT 1000
);
插入优化:关闭自动提交 + 批量插入
SET autocommit = 0;
INSERT INTO user (...) VALUES (...), (...), ...;
COMMIT;
SET autocommit = 1;
索引
索引的本质
索引就像书籍的目录:
- 无索引:查数据时数据库要逐行扫描全表
- 有索引:数据库先查索引,直接定位到数据所在的行
核心作用:
- 加速查询(SELECT):这是索引的核心价值,尤其是大表(10 万+行)
- 减慢增删改(INSERT/UPDATE/DELETE):因为修改数据时,数据库需要同步更新索引
- 核心权衡:查询多、修改少的表要建索引;修改多、查询少的表少建索引
MySQL 常用索引类型:
| 索引类型 | 核心特点 | 适用场景 | 示例 |
|---|---|---|---|
| 主键索引 | 自动创建,唯一且非 NULL | 表的唯一标识 | ALTER TABLE user ADD PRIMARY KEY (user_id); |
| 唯一索引 | 索引值唯一,允许 NULL | 唯一字段(手机号、邮箱) | CREATE UNIQUE INDEX idx_user_phone ON user(phone); |
| 普通索引 | 最常用,无唯一性要求 | 普通筛选/关联字段 | CREATE INDEX idx_order_user_id ON \order`(user_id);` |
| 联合索引 | 基于多个字段的索引 | 多字段组合筛选 | CREATE INDEX idx_order_uid_ctime ON \order`(user_id, create_time);` |
| 全文索引 | 针对文本内容的模糊查询 | 长文本模糊搜索 | CREATE FULLTEXT INDEX idx_prod_name ON product(product_name); |
最左匹配原则(联合索引的核心):
联合索引 idx_order_uid_ctime (user_id, create_time) 的生效规则:
- 生效:
WHERE user_id = 1、WHERE user_id = 1 AND create_time > '2024-01-01' - 失效:
WHERE create_time > '2024-01-01'(跳过了最左的 user_id) - 核心:联合索引必须从最左字段开始匹配,中间不能跳过
索引的基本操作:
创建索引:
-- 方式1:CREATE INDEX(推荐)
CREATE INDEX idx_order_amount ON `order`(amount);
CREATE UNIQUE INDEX idx_user_email ON user(email);
CREATE INDEX idx_order_uid_amt ON `order`(user_id, amount);
-- 方式2:ALTER TABLE
ALTER TABLE product ADD PRIMARY KEY (product_id);
ALTER TABLE `user` ADD UNIQUE INDEX idx_user_phone (phone);
-- 方式3:建表时直接指定
CREATE TABLE `order` (
order_id INT PRIMARY KEY,
user_id INT,
amount DECIMAL(10,2),
create_time DATETIME,
INDEX idx_order_ctime (create_time),
UNIQUE INDEX idx_order_uid_oid (user_id, order_id)
);
删除索引:
-- 方式1:DROP INDEX(推荐)
DROP INDEX idx_order_amount ON `order`;
-- 方式2:ALTER TABLE(删除主键索引只能用这个)
ALTER TABLE user DROP PRIMARY KEY;
ALTER TABLE user DROP INDEX idx_user_phone;
查看索引:
-- 查看单表所有索引
SHOW INDEX FROM `order`;
-- 查看建表语句(包含索引)
SHOW CREATE TABLE `order`;
索引生效/失效的核心规则:
索引生效的条件:
- 筛选字段/关联字段必须是索引字段
- 联合索引遵循最左匹配
- 排序字段是索引字段
索引失效的常见场景:
| 失效场景 | 错误示例 | 修正方案 |
|---|---|---|
| 索引字段做函数操作 | WHERE YEAR(create_time) = 2024 |
WHERE create_time BETWEEN '2024-01-01' AND '2024-12-31' |
| 索引字段用运算符(!=/<>) | WHERE user_id != 1 |
WHERE user_id IN (2,3,4) |
| LIKE 以 % 开头 | WHERE product_name LIKE '%手机' |
LIKE '手机%' 或用全文索引 |
| 索引字段类型不匹配 | WHERE user_id = '1'(user_id 是 INT) |
WHERE user_id = 1 |
| OR 连接无索引字段 | WHERE user_id = 1 OR amount > 100(amount 无索引) |
拆分查询,或给 amount 加索引 |
视图
视图是基于一条 SELECT 查询语句创建的虚拟表,它不存储任何实际数据,只保存查询逻辑。每次查询视图时,数据库都会重新执行底层的 SELECT 语句,返回实时结果。
视图的作用:
- 复用:多处调用同一视图,避免重复写联查 SQL
- 简化:对外只暴露视图名,隐藏内部复杂逻辑
基础语法:
-- 创建视图
CREATE [OR REPLACE] VIEW 视图名 AS
SELECT 查询语句;
-- 查询视图
SELECT * FROM 视图名 [WHERE 条件];
-- 修改视图
CREATE OR REPLACE VIEW 视图名 AS
新的SELECT查询语句;
-- 删除视图
DROP VIEW [IF EXISTS] 视图名;
示例1:创建用户订单详情视图
-- 创建视图
DROP VIEW IF EXISTS v_user_order_detail;
CREATE VIEW v_user_order_detail AS
SELECT
u.user_id,
u.user_name,
u.user_city,
o.order_id,
o.order_time,
o.total_amount,
p.product_name,
p.category,
p.price
FROM user u
JOIN `order` o ON u.user_id = o.user_id
JOIN product p ON o.product_id = p.product_id;
-- 查询视图
SELECT * FROM v_user_order_detail
WHERE user_city = '北京' AND category = '数码'
LIMIT 10;
-- 统计各城市的数码商品销售额
SELECT user_city, SUM(total_amount) AS total_sales
FROM v_user_order_detail
WHERE category = '数码'
GROUP BY user_city;
示例2:创建带筛选的视图(隐藏敏感数据)
DROP VIEW IF EXISTS v_user_sales_simple;
CREATE VIEW v_user_sales_simple AS
SELECT
u.user_id,
u.user_name,
o.order_id,
o.total_amount
FROM user u
LEFT JOIN `order` o ON u.user_id = o.user_id;
-- 查询视图
SELECT * FROM v_user_sales_simple LIMIT 10;
注意事项:
- 视图是虚拟表,不存数据:原表数据更新后,视图查询结果会自动更新
- 视图的更新限制:简单视图可直接更新,复杂视图(多表联查、含聚合/分组)无法直接更新
- 视图不提升查询性能:要提升速度,需给底层表的关联字段/筛选字段加索引
- 避免过度使用复杂视图:不要创建视图嵌套视图
存储过程
存储过程是预编译并存储在数据库中的一组 SQL 语句的集合,可以理解成数据库里的"自定义函数/脚本"。
优势:
- 简化操作:一次封装,多次调用
- 提高效率:预编译后执行更快
- 减少网络传输:复杂逻辑在数据库端执行
- 权限控制:可以授予调用权限,但不直接暴露底层表
基本语法:
DELIMITER //
CREATE PROCEDURE 存储过程名(参数列表)
BEGIN
-- 要执行的 SQL 逻辑
END //
DELIMITER ;
参数类型:
| 类型 | 含义 |
|---|---|
| IN | 输入参数(默认):调用时传入值,存储过程内可使用 |
| OUT | 输出参数:存储过程内给参数赋值,调用后能获取到这个值 |
| INOUT | 输入输出参数:既可以传入值,也能返回修改后的值 |
调用语法:
-- 无参数/仅 IN 参数
CALL 存储过程名(参数值);
-- 有 OUT/INOUT 参数
SET @变量名 = 初始值;
CALL 存储过程名(@变量名);
SELECT @变量名;
-- 查看所有存储过程
SHOW PROCEDURE STATUS WHERE db = '你的数据库名';
-- 删除存储过程
DROP PROCEDURE IF EXISTS 存储过程名;
案例1:无参数存储过程
-- 创建:查询 order 表所有数据
DELIMITER //
CREATE PROCEDURE query_orders()
BEGIN
SELECT * FROM ssmbuild.`order`;
END //
DELIMITER ;
-- 调用
CALL query_orders();
案例2:带 IN 参数的存储过程
-- 创建:根据 order_id 查询订单
DELIMITER //
CREATE PROCEDURE query_order_by_id(IN oid INT)
BEGIN
SELECT * FROM ssmbuild.`order` WHERE order_id = oid;
END //
DELIMITER ;
-- 调用
CALL query_order_by_id(4);
案例3:带 OUT 参数的存储过程
-- 创建:统计订单总数
DELIMITER //
CREATE PROCEDURE count_order(OUT total INT)
BEGIN
SELECT COUNT(*) INTO total FROM ssmbuild.`order`;
END //
DELIMITER ;
-- 调用
SET @t = 0;
CALL count_order(@t);
SELECT @t AS 订单总数;
案例4:带逻辑的存储过程
-- 创建:给指定订单的 amount 加指定数值
DELIMITER //
CREATE PROCEDURE update_order_amount(IN oid INT, IN add_num INT)
BEGIN
IF (SELECT COUNT(*) FROM ssmbuild.`order` WHERE order_id = oid) > 0 THEN
UPDATE ssmbuild.`order` SET amount = amount + add_num WHERE order_id = oid;
SELECT '更新成功' AS 结果;
ELSE
SELECT '订单不存在' AS 结果;
END IF;
END //
DELIMITER ;
-- 调用
CALL update_order_amount(4, 5);
注意事项:
- 务必正确处理语句结束符(DELIMITER)
- 严格区分参数类型(IN/OUT/INOUT)
- 避免在存储过程中写过于复杂的逻辑
- 注意存储过程的预编译特性,修改逻辑需重新创建
- 做好存储过程的文档和命名规范
触发器
触发器是与表关联的、自动执行的一段 SQL 逻辑,当你对表执行 INSERT/UPDATE/DELETE 操作时,数据库会自动触发预设的 SQL 逻辑。
优势:
- 自动执行:无需手动调用
- 与表绑定:一个触发器只能属于一张表
- 触发时机固定:只能在操作前(BEFORE)或操作后(AFTER)触发
触发器的核心要素:
触发时机:
| 时机 | 含义 |
|---|---|
| BEFORE | 在执行操作之前触发(可修改即将插入/更新的数据) |
| AFTER | 在执行操作之后触发(仅能读取操作后的数据) |
触发事件:
- INSERT:插入数据时触发
- UPDATE:修改数据时触发
- DELETE:删除数据时触发
特殊变量:
| 变量 | 适用场景 | 含义 |
|---|---|---|
| OLD.字段名 | UPDATE/DELETE | 操作前的旧数据 |
| NEW.字段名 | INSERT/UPDATE | 操作后的新数据 |
注意:DELETE 只有 OLD,INSERT 只有 NEW,UPDATE 既有 OLD 也有 NEW。
基本语法:
DELIMITER //
CREATE TRIGGER 触发器名
触发时机 触发事件 ON 表名
FOR EACH ROW
BEGIN
-- 触发器要执行的 SQL 逻辑
END //
DELIMITER ;
查看和删除:
-- 查看所有触发器
SHOW TRIGGERS;
-- 删除触发器
DROP TRIGGER IF EXISTS 触发器名;
案例1:AFTER INSERT 触发器(插入订单后自动扣库存)
-- 创建 goods 表
CREATE TABLE IF NOT EXISTS ssmbuild.goods (
id INT PRIMARY KEY,
name VARCHAR(50),
stock INT DEFAULT 0
);
INSERT INTO ssmbuild.goods VALUES (1, '华为手机', 100);
-- 创建触发器
DELIMITER //
CREATE TRIGGER tri_order_insert
AFTER INSERT ON `order`
FOR EACH ROW
BEGIN
UPDATE ssmbuild.goods
SET stock = stock - NEW.num
WHERE id = NEW.goods_id;
END //
DELIMITER ;
-- 测试
INSERT INTO ssmbuild.`order` (goods_id, num, amount) VALUES (1, 2, 5998);
SELECT * FROM ssmbuild.goods WHERE id = 1;
案例2:BEFORE UPDATE 触发器(限制价格修改)
-- 给 goods 表加 price 字段
ALTER TABLE ssmbuild.goods ADD COLUMN price DECIMAL(10,2);
UPDATE ssmbuild.goods SET price = 2999 WHERE id = 1;
-- 创建触发器
DELIMITER //
CREATE TRIGGER tri_goods_update
BEFORE UPDATE ON goods
FOR EACH ROW
BEGIN
IF NEW.price < 0 THEN
SET NEW.price = 0;
END IF;
END //
DELIMITER ;
-- 测试
UPDATE ssmbuild.goods SET price = -100 WHERE id = 1;
SELECT price FROM ssmbuild.goods WHERE id = 1;
案例3:AFTER DELETE 触发器(删除用户前备份数据)
-- 创建备份表
CREATE TABLE IF NOT EXISTS ssmbuild.user_backup (
id INT,
username VARCHAR(50),
delete_time DATETIME
);
-- 创建触发器
DELIMITER //
CREATE TRIGGER tri_user_delete
AFTER DELETE ON `user`
FOR EACH ROW
BEGIN
INSERT INTO ssmbuild.user_backup
VALUES (OLD.id, OLD.username, NOW());
END //
DELIMITER ;
-- 测试
DELETE FROM ssmbuild.`user` WHERE id = 1;
SELECT * FROM ssmbuild.user_backup;
注意事项:
- 严格控制触发逻辑,避免无限/嵌套触发
- 用好 BEFORE/AFTER 时机 + OLD/NEW 变量
- 触发器与触发操作共享事务,关注数据一致性
- 简化触发器逻辑,降低维护/调试成本
总结
以上就是 MySQL 高频 SQL 语法的核心内容,从基础的 DML 操作到进阶的窗口函数、多表连接,覆盖了 Java 后端开发中 90% 以上的业务场景。
SQL 的核心是"练",建议结合实际业务场景多写多试。把本文的示例语句在本地数据库中实操验证,才能真正吃透。如果在使用过程中遇到特殊场景的 SQL 难题,可以针对性拆解逻辑,比如:
- 复杂分组用窗口函数替代嵌套子查询
- 大数据量用 EXISTS 优化性能
- 多表关联优先使用显式 JOIN
希望这份教程能帮你夯实 SQL 基础,提升数据处理效率。
关键词:MySQL、SQL、数据库、Java 后端、DML、多表连接、窗口函数、索引优化、存储过程、触发器、视图
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)