Skip to content

SQL

1. SQL 简介

SQL(Structured Query Language,结构化查询语言)是用于管理关系型数据库的标准语言,由 IBM 在 1970 年代开发。

主要数据库系统对比

数据库 特点 适用场景
MySQL 开源、高性能、使用广泛 Web 应用、中小型系统
PostgreSQL 开源、功能强大、支持 JSON 复杂查询、企业级应用
SQLite 轻量级、嵌入式、无服务端 移动应用、本地存储
SQL Server 微软出品、企业级 Windows 生态系统
Oracle 功能完备、性能极强 大型企业、金融系统

SQL 语言分类

SQL
├── DDL(Data Definition Language)  数据定义语言  — CREATE / ALTER / DROP
├── DML(Data Manipulation Language)数据操作语言  — INSERT / UPDATE / DELETE
├── DQL(Data Query Language)       数据查询语言  — SELECT
└── DCL(Data Control Language)     数据控制语言  — GRANT / REVOKE

2. 数据库基础概念

核心术语

术语 说明
数据库 按结构组织的数据集合
数据以行列形式存储的二维结构
行/记录 表中的一条数据
列/字段 表中某一类属性
主键 唯一标识一条记录的字段,不可为 NULL
外键 引用另一张表主键的字段,用于建立表间关系
索引 加速查询的数据结构,类似书的目录
视图 基于查询结果的虚拟表
事务 一组原子性操作,要么全部成功,要么全部回滚

3. 数据类型

数值类型

INT           -- 整数,4字节,范围 -2^31 ~ 2^31-1
BIGINT        -- 大整数,8字节
SMALLINT      -- 小整数,2字节
TINYINT       -- 极小整数,1字节(0~255)
DECIMAL(p,s)  -- 精确小数,p位总长,s位小数,适合金额
FLOAT         -- 单精度浮点
DOUBLE        -- 双精度浮点

字符串类型

CHAR(n)       -- 固定长度字符串,不足补空格,最大 255
VARCHAR(n)    -- 可变长度字符串,最大 65535(实际受行长限制)
TEXT          -- 长文本,最大 65535 字节
LONGTEXT      -- 超长文本,最大 4GB
ENUM('a','b') -- 枚举类型,只能取预定义值之一

日期时间类型

DATE          -- 日期,格式 YYYY-MM-DD
TIME          -- 时间,格式 HH:MM:SS
DATETIME      -- 日期时间,格式 YYYY-MM-DD HH:MM:SS
TIMESTAMP     -- 时间戳,自动记录修改时间,受时区影响
YEAR          -- 年份

其他类型

BOOLEAN       -- 布尔值(MySQL 中实为 TINYINT(1))
JSON          -- JSON 格式数据(MySQL 5.7+,PostgreSQL 原生支持)
BLOB          -- 二进制大对象,存储图片/文件等

4. DDL — 数据定义语言

4.1 创建数据库

-- 创建数据库
CREATE DATABASE shop;

-- 创建时指定字符集(推荐 utf8mb4 以支持 emoji)
CREATE DATABASE shop
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

-- 如果不存在则创建
CREATE DATABASE IF NOT EXISTS shop;

-- 查看所有数据库
SHOW DATABASES;

-- 切换数据库
USE shop;

-- 删除数据库(谨慎!)
DROP DATABASE shop;

4.2 创建表

CREATE TABLE users (
    id          INT             NOT NULL AUTO_INCREMENT,
    username    VARCHAR(50)     NOT NULL,
    email       VARCHAR(100)    NOT NULL,
    password    VARCHAR(255)    NOT NULL,
    age         TINYINT         UNSIGNED,
    balance     DECIMAL(10, 2)  DEFAULT 0.00,
    status      ENUM('active', 'inactive', 'banned') DEFAULT 'active',
    created_at  DATETIME        DEFAULT CURRENT_TIMESTAMP,
    updated_at  DATETIME        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    UNIQUE KEY uk_email (email),
    INDEX idx_username (username)
);

4.3 修改表结构

-- 添加列
ALTER TABLE users ADD COLUMN phone VARCHAR(20) AFTER email;

-- 删除列
ALTER TABLE users DROP COLUMN phone;

-- 修改列定义(改类型/约束)
ALTER TABLE users MODIFY COLUMN age SMALLINT UNSIGNED;

-- 重命名列
ALTER TABLE users CHANGE COLUMN username user_name VARCHAR(50) NOT NULL;

-- 添加索引
ALTER TABLE users ADD INDEX idx_status (status);

-- 删除索引
ALTER TABLE users DROP INDEX idx_status;

-- 重命名表
ALTER TABLE users RENAME TO members;
-- 或
RENAME TABLE members TO users;

4.4 删除与清空表

-- 删除表(结构和数据一起删除)
DROP TABLE users;

-- 安全删除
DROP TABLE IF EXISTS users;

-- 清空表数据(保留结构,重置自增 ID,DDL 操作,不可回滚)
TRUNCATE TABLE users;

-- 清空数据(DML 操作,可回滚,自增不重置)
DELETE FROM users;

5. DML — 数据操作语言

5.1 INSERT 插入数据

-- 插入单行(推荐明确列名)
INSERT INTO users (username, email, password, age)
VALUES ('alice', 'alice@example.com', 'hashed_pwd', 25);

-- 插入多行
INSERT INTO users (username, email, password)
VALUES
    ('bob',   'bob@example.com',   'pwd1'),
    ('carol', 'carol@example.com', 'pwd2'),
    ('dave',  'dave@example.com',  'pwd3');

-- 从另一张表查询并插入
INSERT INTO users_backup (username, email)
SELECT username, email FROM users WHERE status = 'active';

-- 存在则更新,不存在则插入(MySQL)
INSERT INTO users (id, username, email)
VALUES (1, 'alice_new', 'alice_new@example.com')
ON DUPLICATE KEY UPDATE username = VALUES(username), email = VALUES(email);

5.2 UPDATE 更新数据

-- 更新单列
UPDATE users SET status = 'inactive' WHERE id = 1;

-- 更新多列
UPDATE users
SET username = 'alice2', email = 'alice2@example.com', updated_at = NOW()
WHERE id = 1;

-- 批量更新(加 LIMIT 防止误操作)
UPDATE users SET status = 'inactive'
WHERE created_at < '2024-01-01'
LIMIT 100;

-- 基于另一张表的值更新(JOIN UPDATE)
UPDATE users u
JOIN orders o ON u.id = o.user_id
SET u.balance = u.balance - o.amount
WHERE o.status = 'paid';

5.3 DELETE 删除数据

-- 删除指定行
DELETE FROM users WHERE id = 1;

-- 删除多行
DELETE FROM users WHERE status = 'banned';

-- 限制删除数量(防止误删大量数据)
DELETE FROM users WHERE status = 'inactive' LIMIT 10;

-- 多表联合删除(删除 users 表中有订单未支付的用户)
DELETE u FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.status = 'unpaid';

6. DQL — 数据查询语言

6.1 SELECT 基础

-- 查询所有列
SELECT * FROM users;

-- 查询指定列
SELECT id, username, email FROM users;

-- 列别名
SELECT username AS name, email AS contact FROM users;

-- 去重
SELECT DISTINCT status FROM users;

-- 计算列
SELECT username, balance * 1.1 AS adjusted_balance FROM users;

-- 字符串拼接
SELECT CONCAT(username, ' <', email, '>') AS display_name FROM users;

6.2 WHERE 条件过滤

-- 比较运算符
SELECT * FROM users WHERE age > 18;
SELECT * FROM users WHERE age BETWEEN 18 AND 30;   -- 包含边界
SELECT * FROM users WHERE age NOT BETWEEN 18 AND 30;

-- 字符串匹配
SELECT * FROM users WHERE username LIKE 'a%';      -- 以 a 开头
SELECT * FROM users WHERE email LIKE '%@gmail.com';-- 以 @gmail.com 结尾
SELECT * FROM users WHERE username LIKE '_lice';   -- _ 匹配单个字符

-- 集合匹配
SELECT * FROM users WHERE status IN ('active', 'inactive');
SELECT * FROM users WHERE id NOT IN (1, 2, 3);

-- NULL 判断(不能用 = NULL)
SELECT * FROM users WHERE age IS NULL;
SELECT * FROM users WHERE age IS NOT NULL;

-- 逻辑运算
SELECT * FROM users WHERE age > 18 AND status = 'active';
SELECT * FROM users WHERE status = 'banned' OR balance < 0;
SELECT * FROM users WHERE NOT (status = 'active');

6.3 ORDER BY 排序

-- 升序(默认)
SELECT * FROM users ORDER BY age ASC;

-- 降序
SELECT * FROM users ORDER BY created_at DESC;

-- 多列排序:先按 status 升序,status 相同时按 created_at 降序
SELECT * FROM users ORDER BY status ASC, created_at DESC;

-- NULL 值排序(MySQL 中 NULL 排在最前,可用 IS NULL 控制)
SELECT * FROM users ORDER BY age IS NULL, age ASC;

6.4 LIMIT 分页

-- 取前 10 条
SELECT * FROM users LIMIT 10;

-- 分页:跳过前 20 条,取第 21~30 条(第 3 页,每页 10 条)
SELECT * FROM users LIMIT 10 OFFSET 20;
-- 等价写法(MySQL 特有,更简洁)
SELECT * FROM users LIMIT 20, 10;

-- 通用分页公式
-- LIMIT {pageSize} OFFSET {(pageNum - 1) * pageSize}

6.5 常用函数

字符串函数

SELECT LENGTH('hello');              -- 5(字节数)
SELECT CHAR_LENGTH('hello');         -- 5(字符数,多字节字符安全)
SELECT UPPER('hello');               -- HELLO
SELECT LOWER('HELLO');               -- hello
SELECT TRIM('  hello  ');            -- 'hello'
SELECT LTRIM('  hello');             -- 'hello'
SELECT RTRIM('hello  ');             -- 'hello'
SELECT SUBSTRING('hello', 2, 3);     -- 'ell'(从第2位取3个字符)
SELECT REPLACE('hello world', 'world', 'SQL'); -- 'hello SQL'
SELECT CONCAT('a', 'b', 'c');        -- 'abc'
SELECT CONCAT_WS(',', 'a', 'b', 'c');-- 'a,b,c'(带分隔符)
SELECT INSTR('hello', 'ell');        -- 2(位置)
SELECT LPAD('5', 3, '0');            -- '005'
SELECT RPAD('5', 3, '0');            -- '500'

数值函数

SELECT ABS(-5);        -- 5
SELECT CEIL(4.1);      -- 5(向上取整)
SELECT FLOOR(4.9);     -- 4(向下取整)
SELECT ROUND(4.567, 2);-- 4.57(四舍五入保留2位小数)
SELECT MOD(10, 3);     -- 1(取余)
SELECT POWER(2, 10);   -- 1024
SELECT SQRT(16);       -- 4
SELECT RAND();         -- 0~1 之间随机数

日期函数

SELECT NOW();                        -- 当前日期时间
SELECT CURDATE();                    -- 当前日期
SELECT CURTIME();                    -- 当前时间
SELECT YEAR(NOW());                  -- 年
SELECT MONTH(NOW());                 -- 月
SELECT DAY(NOW());                   -- 日
SELECT HOUR(NOW());                  -- 时
SELECT DAYOFWEEK(NOW());             -- 星期几(1=周日)
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'); -- 格式化
SELECT DATE_ADD(NOW(), INTERVAL 7 DAY);  -- 7天后
SELECT DATE_SUB(NOW(), INTERVAL 1 MONTH);-- 1个月前
SELECT DATEDIFF('2026-12-31', '2026-01-01'); -- 相差天数 364
SELECT TIMESTAMPDIFF(MONTH, '2026-01-01', '2026-05-01'); -- 相差月数 4

条件函数

-- IF(条件, 真值, 假值)
SELECT username, IF(age >= 18, '成年', '未成年') AS is_adult FROM users;

-- IFNULL(值, 默认值)
SELECT IFNULL(age, 0) AS safe_age FROM users;

-- COALESCE:返回第一个非 NULL 值
SELECT COALESCE(phone, email, '无联系方式') AS contact FROM users;

-- CASE WHEN
SELECT username,
    CASE
        WHEN age < 18  THEN '未成年'
        WHEN age < 60  THEN '成年'
        ELSE '老年'
    END AS age_group
FROM users;

-- CASE 值匹配(类似 switch)
SELECT username,
    CASE status
        WHEN 'active'   THEN '正常'
        WHEN 'inactive' THEN '停用'
        WHEN 'banned'   THEN '封禁'
        ELSE '未知'
    END AS status_text
FROM users;

7. DCL — 数据控制语言

-- 创建用户
CREATE USER 'dev'@'localhost' IDENTIFIED BY 'password123';

-- 授权
GRANT SELECT, INSERT, UPDATE ON shop.* TO 'dev'@'localhost';

-- 授予所有权限
GRANT ALL PRIVILEGES ON shop.* TO 'dev'@'localhost';

-- 刷新权限
FLUSH PRIVILEGES;

-- 查看用户权限
SHOW GRANTS FOR 'dev'@'localhost';

-- 撤销权限
REVOKE INSERT ON shop.* FROM 'dev'@'localhost';

-- 删除用户
DROP USER 'dev'@'localhost';

8. 约束

约束类型

CREATE TABLE orders (
    id          INT         NOT NULL AUTO_INCREMENT,
    user_id     INT         NOT NULL,
    product_id  INT         NOT NULL,
    quantity    INT         NOT NULL DEFAULT 1,
    amount      DECIMAL(10,2) NOT NULL,
    status      VARCHAR(20) NOT NULL DEFAULT 'pending',

    PRIMARY KEY (id),                              -- 主键约束
    UNIQUE KEY uk_user_product (user_id, product_id), -- 联合唯一约束
    CONSTRAINT fk_user
        FOREIGN KEY (user_id) REFERENCES users(id)
        ON DELETE CASCADE                          -- 父记录删除时级联删除子记录
        ON UPDATE CASCADE,                         -- 父记录更新时级联更新
    CONSTRAINT chk_quantity CHECK (quantity > 0),  -- 检查约束(MySQL 8.0+)
    INDEX idx_status (status)
);

外键行为说明

行为 说明
RESTRICT 父记录有子记录时拒绝删除/更新(默认)
CASCADE 父记录删除/更新时,子记录跟着操作
SET NULL 父记录删除/更新时,子外键列置 NULL
NO ACTION 与 RESTRICT 类似

9. 索引

索引类型

-- 普通索引
CREATE INDEX idx_username ON users(username);

-- 唯一索引
CREATE UNIQUE INDEX uk_email ON users(email);

-- 联合索引(遵循最左前缀原则)
CREATE INDEX idx_status_created ON users(status, created_at);

-- 全文索引(适合大文本搜索)
CREATE FULLTEXT INDEX ft_content ON articles(title, content);
-- 使用全文索引
SELECT * FROM articles WHERE MATCH(title, content) AGAINST('SQL 教程' IN BOOLEAN MODE);

-- 前缀索引(节省索引空间)
CREATE INDEX idx_email_prefix ON users(email(20));

-- 查看索引
SHOW INDEX FROM users;

-- 删除索引
DROP INDEX idx_username ON users;
ALTER TABLE users DROP INDEX idx_username;

最左前缀原则

联合索引 (a, b, c) 等价于同时建立了: - (a) - (a, b) - (a, b, c)

-- 以下查询可以使用索引
SELECT * FROM t WHERE a = 1;
SELECT * FROM t WHERE a = 1 AND b = 2;
SELECT * FROM t WHERE a = 1 AND b = 2 AND c = 3;

-- 以下查询无法使用索引(跳过了 a)
SELECT * FROM t WHERE b = 2;
SELECT * FROM t WHERE b = 2 AND c = 3;

索引失效场景

-- 对列使用函数(索引失效)
SELECT * FROM users WHERE YEAR(created_at) = 2026;
-- 改写为范围查询(使用索引)
SELECT * FROM users WHERE created_at BETWEEN '2026-01-01' AND '2026-12-31';

-- LIKE 以通配符开头(失效)
SELECT * FROM users WHERE username LIKE '%alice';
-- LIKE 以通配符结尾(可用索引)
SELECT * FROM users WHERE username LIKE 'alice%';

-- 隐式类型转换(字段是 VARCHAR,传入整数,失效)
SELECT * FROM users WHERE email = 12345;

-- 使用 OR 连接不同列(失效,除非两列都有索引)
SELECT * FROM users WHERE id = 1 OR username = 'alice';

-- != 或 NOT IN 通常无法使用索引
SELECT * FROM users WHERE status != 'active';

10. JOIN 连接查询

JOIN 类型

-- 准备数据
-- users: id, username
-- orders: id, user_id, amount

-- INNER JOIN(内连接):只返回两表都匹配的行
SELECT u.username, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

-- LEFT JOIN(左外连接):返回左表所有行,右表无匹配则 NULL
SELECT u.username, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

-- RIGHT JOIN(右外连接):返回右表所有行,左表无匹配则 NULL
SELECT u.username, o.amount
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;

-- FULL OUTER JOIN(全外连接,MySQL 不直接支持,用 UNION 模拟)
SELECT u.username, o.amount
FROM users u LEFT JOIN orders o ON u.id = o.user_id
UNION
SELECT u.username, o.amount
FROM users u RIGHT JOIN orders o ON u.id = o.user_id;

-- CROSS JOIN(笛卡尔积,慎用)
SELECT u.username, p.name
FROM users u CROSS JOIN products p;

-- 自连接(表和自身连接,常用于层级数据)
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

多表 JOIN

SELECT
    u.username,
    o.id AS order_id,
    p.name AS product_name,
    oi.quantity,
    oi.price
FROM users u
JOIN orders o        ON u.id = o.user_id
JOIN order_items oi  ON o.id = oi.order_id
JOIN products p      ON oi.product_id = p.id
WHERE o.status = 'paid'
ORDER BY o.created_at DESC;

11. 子查询

标量子查询(返回单个值)

-- 查询余额高于平均值的用户
SELECT username, balance
FROM users
WHERE balance > (SELECT AVG(balance) FROM users);

列子查询(返回一列多行)

-- 查询有订单的用户
SELECT username FROM users
WHERE id IN (SELECT DISTINCT user_id FROM orders);

-- 查询没有订单的用户
SELECT username FROM users
WHERE id NOT IN (SELECT DISTINCT user_id FROM orders WHERE user_id IS NOT NULL);

行子查询(返回一行多列)

SELECT * FROM orders
WHERE (user_id, amount) = (SELECT user_id, MAX(amount) FROM orders WHERE user_id = 1);

表子查询(返回多行多列,用于 FROM)

-- 每个用户的订单统计
SELECT u.username, order_stats.order_count, order_stats.total_amount
FROM users u
JOIN (
    SELECT user_id, COUNT(*) AS order_count, SUM(amount) AS total_amount
    FROM orders
    GROUP BY user_id
) AS order_stats ON u.id = order_stats.user_id;

EXISTS / NOT EXISTS

-- 查询有订单的用户(EXISTS 通常比 IN 性能好,尤其是大数据量)
SELECT username FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.id
);

-- 查询没有订单的用户
SELECT username FROM users u
WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.id
);

CTE(公共表表达式,WITH 语句)

-- 简单 CTE
WITH active_users AS (
    SELECT id, username FROM users WHERE status = 'active'
)
SELECT u.username, COUNT(o.id) AS order_count
FROM active_users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.username;

-- 多个 CTE
WITH
    monthly_sales AS (
        SELECT DATE_FORMAT(created_at, '%Y-%m') AS month, SUM(amount) AS total
        FROM orders WHERE status = 'paid'
        GROUP BY month
    ),
    avg_sales AS (
        SELECT AVG(total) AS avg_monthly FROM monthly_sales
    )
SELECT ms.month, ms.total,
       ROUND(ms.total / a.avg_monthly * 100, 1) AS pct_of_avg
FROM monthly_sales ms, avg_sales a
ORDER BY ms.month;

-- 递归 CTE(处理层级数据,如组织架构)
WITH RECURSIVE org_tree AS (
    -- 基础查询:顶层员工
    SELECT id, name, manager_id, 0 AS level
    FROM employees WHERE manager_id IS NULL

    UNION ALL

    -- 递归查询:向下扩展
    SELECT e.id, e.name, e.manager_id, ot.level + 1
    FROM employees e
    JOIN org_tree ot ON e.manager_id = ot.id
)
SELECT LPAD('', level * 2, ' ') || name AS hierarchy
FROM org_tree
ORDER BY level, name;

12. 聚合函数与分组

聚合函数

SELECT
    COUNT(*)           AS total_rows,      -- 总行数(含NULL)
    COUNT(age)         AS age_not_null,    -- 非NULL的行数
    COUNT(DISTINCT status) AS unique_status,-- 去重计数
    SUM(balance)       AS total_balance,
    AVG(balance)       AS avg_balance,
    MAX(balance)       AS max_balance,
    MIN(balance)       AS min_balance,
    GROUP_CONCAT(username ORDER BY username SEPARATOR ', ') AS names -- 字符串聚合
FROM users;

GROUP BY 分组

-- 按状态统计用户数和平均余额
SELECT status, COUNT(*) AS cnt, AVG(balance) AS avg_balance
FROM users
GROUP BY status;

-- 多列分组
SELECT status, YEAR(created_at) AS year, COUNT(*) AS cnt
FROM users
GROUP BY status, year
ORDER BY year, status;

HAVING 分组后过滤

-- WHERE 在分组前过滤原始数据,HAVING 在分组后过滤聚合结果
-- 找出订单数超过 5 的用户
SELECT user_id, COUNT(*) AS order_count, SUM(amount) AS total
FROM orders
WHERE status = 'paid'          -- 先过滤:只统计已支付订单
GROUP BY user_id
HAVING order_count > 5         -- 再过滤:只返回订单数 > 5 的
ORDER BY total DESC;

ROLLUP(小计/合计)

SELECT status, YEAR(created_at) AS year, COUNT(*) AS cnt
FROM users
GROUP BY status, year WITH ROLLUP;
-- ROLLUP 会额外生成每个 status 的小计行,以及所有数据的总计行

13. 窗口函数

窗口函数在不折叠行的情况下,对每一行基于一个"窗口"(分区 + 排序)进行计算,是分析类查询的利器。

语法

函数名() OVER (
    PARTITION BY 分区列        -- 可选:按哪列分区,类似 GROUP BY
    ORDER BY 排序列            -- 可选:窗口内排序
    ROWS BETWEEN ... AND ...   -- 可选:窗口帧范围
)

排名函数

SELECT
    username,
    balance,
    ROW_NUMBER()   OVER (ORDER BY balance DESC) AS row_num,  -- 唯一序号,无并列
    RANK()         OVER (ORDER BY balance DESC) AS rnk,      -- 并列时跳号(1,1,3)
    DENSE_RANK()   OVER (ORDER BY balance DESC) AS dense_rnk,-- 并列时不跳号(1,1,2)
    NTILE(4)       OVER (ORDER BY balance DESC) AS quartile   -- 分成4组
FROM users;

分区排名

-- 每个状态组内的余额排名
SELECT
    username, status, balance,
    RANK() OVER (PARTITION BY status ORDER BY balance DESC) AS rank_in_group
FROM users;

偏移函数

-- 查看每个用户相比前一名的余额差
SELECT
    username,
    balance,
    LAG(balance, 1)  OVER (ORDER BY balance DESC) AS prev_balance,
    LEAD(balance, 1) OVER (ORDER BY balance DESC) AS next_balance,
    balance - LAG(balance, 1) OVER (ORDER BY balance DESC) AS diff
FROM users;

聚合窗口函数

-- 累计求和(running total)
SELECT
    created_at,
    amount,
    SUM(amount) OVER (ORDER BY created_at) AS running_total
FROM orders WHERE status = 'paid';

-- 移动平均(前后各 2 行)
SELECT
    created_at,
    amount,
    AVG(amount) OVER (
        ORDER BY created_at
        ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING
    ) AS moving_avg
FROM orders;

-- 分组内占比
SELECT
    status,
    username,
    balance,
    ROUND(balance / SUM(balance) OVER (PARTITION BY status) * 100, 2) AS pct
FROM users;

14. 视图

-- 创建视图
CREATE VIEW active_user_summary AS
SELECT
    u.id,
    u.username,
    u.email,
    COUNT(o.id)   AS order_count,
    SUM(o.amount) AS total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active'
GROUP BY u.id, u.username, u.email;

-- 使用视图(像普通表一样查询)
SELECT * FROM active_user_summary WHERE total_spent > 1000;

-- 修改视图
CREATE OR REPLACE VIEW active_user_summary AS
SELECT id, username FROM users WHERE status = 'active';

-- 删除视图
DROP VIEW active_user_summary;

视图的优缺点:

优点 缺点
简化复杂查询 不存储数据,查询时实时计算
数据安全(隐藏敏感字段) 复杂视图性能差
逻辑独立(修改底层表不影响视图) 大多数视图不可直接 INSERT/UPDATE

15. 存储过程与函数

存储过程

DELIMITER $$

CREATE PROCEDURE get_user_orders(IN p_user_id INT, IN p_status VARCHAR(20))
BEGIN
    SELECT o.id, o.amount, o.created_at
    FROM orders o
    WHERE o.user_id = p_user_id
      AND (p_status IS NULL OR o.status = p_status)
    ORDER BY o.created_at DESC;
END$$

DELIMITER ;

-- 调用存储过程
CALL get_user_orders(1, 'paid');
CALL get_user_orders(1, NULL);  -- NULL 表示不过滤状态

带 OUT 参数的存储过程

DELIMITER $$

CREATE PROCEDURE transfer_balance(
    IN  p_from_id  INT,
    IN  p_to_id    INT,
    IN  p_amount   DECIMAL(10,2),
    OUT p_result   VARCHAR(50)
)
BEGIN
    DECLARE v_balance DECIMAL(10,2);

    START TRANSACTION;

    SELECT balance INTO v_balance FROM users WHERE id = p_from_id FOR UPDATE;

    IF v_balance < p_amount THEN
        ROLLBACK;
        SET p_result = '余额不足';
    ELSE
        UPDATE users SET balance = balance - p_amount WHERE id = p_from_id;
        UPDATE users SET balance = balance + p_amount WHERE id = p_to_id;
        COMMIT;
        SET p_result = '转账成功';
    END IF;
END$$

DELIMITER ;

-- 调用
CALL transfer_balance(1, 2, 100.00, @result);
SELECT @result;

自定义函数

DELIMITER $$

CREATE FUNCTION age_group(age INT)
RETURNS VARCHAR(10)
DETERMINISTIC
BEGIN
    DECLARE result VARCHAR(10);
    IF age < 18 THEN
        SET result = '未成年';
    ELSEIF age < 60 THEN
        SET result = '成年';
    ELSE
        SET result = '老年';
    END IF;
    RETURN result;
END$$

DELIMITER ;

-- 使用函数
SELECT username, age, age_group(age) AS group_name FROM users;

16. 事务

ACID 特性

特性 说明
原子性 (A) 事务中所有操作要么全部成功,要么全部回滚
一致性 (C) 事务前后数据库处于合法状态
隔离性 (I) 并发事务之间互不干扰
持久性 (D) 事务提交后,数据永久保存,即使系统崩溃

基本事务操作

-- 开启事务
START TRANSACTION;
-- 或
BEGIN;

-- 执行操作
UPDATE accounts SET balance = balance - 500 WHERE id = 1;
UPDATE accounts SET balance = balance + 500 WHERE id = 2;

-- 提交(持久化所有操作)
COMMIT;

-- 回滚(撤销所有操作)
ROLLBACK;

-- 保存点(可以回滚到中间状态)
START TRANSACTION;
  UPDATE users SET balance = balance - 100 WHERE id = 1;
  SAVEPOINT sp1;
  UPDATE users SET balance = balance - 200 WHERE id = 2;
  ROLLBACK TO SAVEPOINT sp1;  -- 只回滚到 sp1,第一个 UPDATE 保留
COMMIT;

隔离级别

-- 查看当前隔离级别
SELECT @@transaction_isolation;

-- 设置隔离级别(SESSION 只影响当前连接)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

MySQL InnoDB 默认 REPEATABLE READ,通过 MVCC 和间隙锁在很大程度上解决了幻读问题。


17. 性能优化

使用 EXPLAIN 分析查询

EXPLAIN SELECT u.username, COUNT(o.id) AS cnt
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active'
GROUP BY u.id;

EXPLAIN 关键字段:

字段 说明
type 访问类型:system > const > eq_ref > ref > range > index > ALL
key 实际使用的索引
key_len 索引使用的字节数
rows 预估扫描行数
Extra Using filesort / Using temporary 表示需要优化

优化技巧

-- 1. 避免 SELECT *,只取需要的列
SELECT id, username FROM users WHERE status = 'active';

-- 2. 分页深翻优化(用 id 条件代替大 OFFSET)
-- 慢(OFFSET 100000 会扫描大量数据)
SELECT * FROM orders LIMIT 100000, 10;
-- 快(利用主键索引)
SELECT * FROM orders WHERE id > 100000 LIMIT 10;

-- 3. 用 EXISTS 替代 IN(大数据集时)
-- 慢
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000);
-- 快
SELECT * FROM users u WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.amount > 1000
);

-- 4. 批量插入替代逐条插入
INSERT INTO logs (user_id, action, created_at) VALUES
    (1, 'login',  NOW()),
    (2, 'logout', NOW()),
    (3, 'login',  NOW());

-- 5. 避免在 WHERE 中对索引列使用函数
-- 慢(索引失效)
SELECT * FROM orders WHERE DATE(created_at) = '2026-05-01';
-- 快(范围扫描,使用索引)
SELECT * FROM orders
WHERE created_at >= '2026-05-01 00:00:00'
  AND created_at <  '2026-05-02 00:00:00';

18. 常见面试题

题 1:查找重复数据

-- 找出 email 重复的记录
SELECT email, COUNT(*) AS cnt
FROM users
GROUP BY email
HAVING cnt > 1;

-- 删除重复保留 id 最小的一条
DELETE FROM users
WHERE id NOT IN (
    SELECT MIN(id) FROM users GROUP BY email
);

题 2:查询每个部门薪资最高的员工

-- 方法 1:子查询
SELECT e.*
FROM employees e
WHERE salary = (
    SELECT MAX(salary) FROM employees WHERE dept_id = e.dept_id
);

-- 方法 2:窗口函数(推荐,更简洁)
SELECT *
FROM (
    SELECT *, RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rnk
    FROM employees
) t
WHERE rnk = 1;

题 3:连续登录天数

-- 找出每个用户最长连续登录天数
WITH daily AS (
    SELECT user_id, DATE(login_time) AS login_date
    FROM login_logs
    GROUP BY user_id, login_date
),
grouped AS (
    SELECT
        user_id, login_date,
        DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (
            PARTITION BY user_id ORDER BY login_date
        ) DAY) AS grp
    FROM daily
)
SELECT user_id, MAX(cnt) AS max_consecutive
FROM (
    SELECT user_id, grp, COUNT(*) AS cnt
    FROM grouped
    GROUP BY user_id, grp
) t
GROUP BY user_id;

题 4:行列转换(PIVOT)

-- 原始数据:user_id, subject, score
-- 目标:每人一行,科目为列

SELECT user_id,
    MAX(CASE WHEN subject = '语文' THEN score END) AS chinese,
    MAX(CASE WHEN subject = '数学' THEN score END) AS math,
    MAX(CASE WHEN subject = '英语' THEN score END) AS english
FROM scores
GROUP BY user_id;

题 5:同比/环比计算

WITH monthly AS (
    SELECT
        DATE_FORMAT(created_at, '%Y-%m') AS month,
        SUM(amount) AS revenue
    FROM orders WHERE status = 'paid'
    GROUP BY month
)
SELECT
    month,
    revenue,
    LAG(revenue, 1)  OVER (ORDER BY month) AS last_month,
    LAG(revenue, 12) OVER (ORDER BY month) AS same_month_last_year,
    ROUND((revenue - LAG(revenue, 1) OVER (ORDER BY month)) /
          LAG(revenue, 1) OVER (ORDER BY month) * 100, 2) AS mom_growth_pct
FROM monthly;

快速参考卡

SELECT  列           -- 选择哪些列
FROM    表            -- 数据来源
JOIN    表 ON 条件    -- 关联其他表
WHERE   条件          -- 过滤原始行(在聚合前)
GROUP BY 列          -- 分组聚合
HAVING  条件          -- 过滤聚合后的结果
ORDER BY 列 [ASC|DESC]-- 排序
LIMIT   n OFFSET m   -- 分页取数

执行顺序:FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT