目录
[1.1 什么是SQL](#1.1 什么是SQL)
[1.2 核心作用](#1.2 核心作用)
[1.3 数据库核心概念](#1.3 数据库核心概念)
[二、数据定义语言(DDL)------ 定义数据库与表结构](#二、数据定义语言(DDL)—— 定义数据库与表结构)
[2.1 数据库操作](#2.1 数据库操作)
[2.1.1 创建数据库](#2.1.1 创建数据库)
[2.1.2 查询数据库](#2.1.2 查询数据库)
[2.1.3 切换数据库](#2.1.3 切换数据库)
[2.1.4 修改数据库](#2.1.4 修改数据库)
[2.1.5 删除数据库](#2.1.5 删除数据库)
[2.2 表结构操作](#2.2 表结构操作)
[2.2.1 创建表(CREATE TABLE)](#2.2.1 创建表(CREATE TABLE))
[2.2.2 查看表结构](#2.2.2 查看表结构)
[2.2.3 修改表结构(ALTER TABLE)](#2.2.3 修改表结构(ALTER TABLE))
[2.2.4 删除表(DROP TABLE)](#2.2.4 删除表(DROP TABLE))
[三、数据操纵语言(DML)------ 操作表中数据](#三、数据操纵语言(DML)—— 操作表中数据)
[3.1 插入数据(INSERT)](#3.1 插入数据(INSERT))
[3.1.1 插入单行数据](#3.1.1 插入单行数据)
[3.1.2 插入多行数据](#3.1.2 插入多行数据)
[3.1.3 插入查询结果](#3.1.3 插入查询结果)
[3.1.4 插入注意事项](#3.1.4 插入注意事项)
[3.2 更新数据(UPDATE)](#3.2 更新数据(UPDATE))
[3.2.1 单表更新](#3.2.1 单表更新)
[3.2.2 多表关联更新](#3.2.2 多表关联更新)
[3.2.3 更新注意事项](#3.2.3 更新注意事项)
[3.3 删除数据(DELETE)](#3.3 删除数据(DELETE))
[3.3.1 单表删除](#3.3.1 单表删除)
[3.3.2 多表关联删除](#3.3.2 多表关联删除)
[3.3.3 删除注意事项](#3.3.3 删除注意事项)
[四、数据查询语言(DQL)------ 检索数据(核心)](#四、数据查询语言(DQL)—— 检索数据(核心))
[4.1 基础查询语法](#4.1 基础查询语法)
[4.2 基础查询操作](#4.2 基础查询操作)
[4.2.1 检索指定列](#4.2.1 检索指定列)
[4.2.2 检索所有列](#4.2.2 检索所有列)
[4.2.3 去重查询(DISTINCT)](#4.2.3 去重查询(DISTINCT))
[4.2.4 限制结果集(LIMIT)](#4.2.4 限制结果集(LIMIT))
[4.3 条件筛选(WHERE子句)](#4.3 条件筛选(WHERE子句))
[4.3.1 比较运算符](#4.3.1 比较运算符)
[4.3.2 逻辑运算符](#4.3.2 逻辑运算符)
[4.3.3 空值判断(IS NULL / IS NOT NULL)](#4.3.3 空值判断(IS NULL / IS NOT NULL))
[4.3.4 模糊查询(LIKE)](#4.3.4 模糊查询(LIKE))
[4.4 排序(ORDER BY)](#4.4 排序(ORDER BY))
[4.5 聚合函数与分组查询](#4.5 聚合函数与分组查询)
[4.5.1 常用聚合函数](#4.5.1 常用聚合函数)
[4.5.2 分组查询(GROUP BY)](#4.5.2 分组查询(GROUP BY))
[4.5.3 分组筛选(HAVING)](#4.5.3 分组筛选(HAVING))
[4.6 多表关联查询(JOIN)](#4.6 多表关联查询(JOIN))
[4.6.1 关联查询类型](#4.6.1 关联查询类型)
[4.6.2 关联查询示例](#4.6.2 关联查询示例)
[4.7 子查询](#4.7 子查询)
[4.7.1 作为条件的子查询(IN / EXISTS)](#4.7.1 作为条件的子查询(IN / EXISTS))
[4.7.2 作为数据源的子查询(FROM子句中)](#4.7.2 作为数据源的子查询(FROM子句中))
[4.7.3 作为字段的子查询(SELECT子句中)](#4.7.3 作为字段的子查询(SELECT子句中))
[五、数据控制语言(DCL)------ 权限与事务管理](#五、数据控制语言(DCL)—— 权限与事务管理)
[5.1 权限管理](#5.1 权限管理)
[5.1.1 创建用户](#5.1.1 创建用户)
[5.1.2 授权(GRANT)](#5.1.2 授权(GRANT))
[5.1.3 撤销权限(REVOKE)](#5.1.3 撤销权限(REVOKE))
[5.1.4 删除用户](#5.1.4 删除用户)
[5.2 事务管理](#5.2 事务管理)
[5.2.1 事务的ACID特性](#5.2.1 事务的ACID特性)
[5.2.2 事务控制命令](#5.2.2 事务控制命令)
[6.1 视图(VIEW)](#6.1 视图(VIEW))
[6.2 存储过程(Stored Procedure)](#6.2 存储过程(Stored Procedure))
[6.2.1 存储过程语法](#6.2.1 存储过程语法)
[6.2.2 存储过程示例](#6.2.2 存储过程示例)
[6.2.3 存储过程管理](#6.2.3 存储过程管理)
[6.2.4 存储过程注意事项](#6.2.4 存储过程注意事项)
[6.3 函数(Function)](#6.3 函数(Function))
[6.3.1 函数语法](#6.3.1 函数语法)
[6.3.2 函数示例](#6.3.2 函数示例)
[6.3.3 函数管理与注意事项](#6.3.3 函数管理与注意事项)
[6.4 触发器(Trigger)](#6.4 触发器(Trigger))
[6.4.1 触发器核心要素](#6.4.1 触发器核心要素)
[6.4.2 触发器示例](#6.4.2 触发器示例)
[6.4.3 触发器管理与注意事项](#6.4.3 触发器管理与注意事项)
[7.1 合理使用索引](#7.1 合理使用索引)
[7.1.1 索引类型与创建](#7.1.1 索引类型与创建)
[7.1.2 索引使用原则](#7.1.2 索引使用原则)
[7.2 优化查询语句](#7.2 优化查询语句)
[7.3 表结构与存储优化](#7.3 表结构与存储优化)
[8.1 语法错误](#8.1 语法错误)
[8.2 索引失效](#8.2 索引失效)
[8.3 事务冲突](#8.3 事务冲突)
一直觉得没有系统的关于sql的笔记可以看,慢慢的学习下来也有了一点成果。分享给大家。有什么错误欢迎大家指正。欢迎来和我一起讨论编程。
SQL操作全面详细笔记
一、SQL基础认知
1.1 什么是SQL
SQL(Structured Query Language,结构化查询语言)是用于与关系型数据库交互的标准编程语言,可实现对数据库的创建、查询、更新、删除等一系列操作。它具有通用性强、简洁易懂的特点,兼容MySQL、Oracle、SQL Server、PostgreSQL等主流关系型数据库。
1.2 核心作用
-
数据定义:创建、修改、删除数据库和表结构(DDL)。
-
数据操纵:对表中数据进行插入、更新、删除(DML)。
-
数据查询:从数据库中检索满足条件的数据(DQL),是SQL最核心的功能。
-
数据控制:管理数据库用户权限、事务等(DCL)。
1.3 数据库核心概念
-
数据库(Database):有组织的数据集合,类似"电子文件柜",用于存储表、视图等数据对象。
-
表(Table):存储特定类型数据的结构化清单,每行是一条记录(对应一个实体),每列是一个字段(对应实体的属性),如"客户表""订单表"。
-
主键(Primary Key):表中唯一标识每条记录的列(或列组合),确保数据唯一性和非空,如"客户ID",是快速定位数据的关键。
-
外键(Foreign Key):表中关联另一表主键的列,用于建立表间关系,保证数据一致性,如"订单表"的"客户ID"关联"客户表"的主键,确保订单对应的客户一定存在于客户表中。
-
数据类型:限制列可存储的数据种类,优化存储和检索效率,常见类型如下表,需注意不同数据库的类型差异(如SQL Server用VARCHAR,Oracle用VARCHAR2):
| 类型分类 | 常见类型 | 用途说明 |
|---|---|---|
| 文本类型 | CHAR(n)、VARCHAR(n) | CHAR(n):固定长度字符串,n为字符数,适合手机号、身份证号等固定长度数据;VARCHAR(n):可变长度字符串,适合姓名、地址等长度不固定数据,更节省空间 |
| 数值类型 | INT、BIGINT、DECIMAL(p,s) | INT:常用整数类型;BIGINT:存储更大整数;DECIMAL(p,s):精确小数,p为总位数,s为小数位数,适合金额、价格等 |
| 日期类型 | DATE、DATETIME、TIMESTAMP | DATE:仅存储日期(YYYY-MM-DD);DATETIME:存储日期和时间(YYYY-MM-DD HH:MM:SS),范围1000-01-01至9999-12-31;TIMESTAMP:存储日期时间,范围1970-01-01至2038-01-19,可自动更新为操作时间,适合记录数据修改时间 注意:MySQL的TIMESTAMP受时区影响,而DATETIME不受;若需存储未来时间(如2038年后),优先使用DATETIME。 |
| 布尔类型 | BOOLEAN/TINYINT(1) | MySQL中常用TINYINT(1)替代BOOLEAN,1表示真,0表示假,直接插入TRUE/FALSE会自动转换为1/0 |
二、数据定义语言(DDL)------ 定义数据库与表结构
DDL用于创建、修改、删除数据库和表等数据对象,操作后会直接生效,无需提交事务。核心命令:CREATE(创建)、ALTER(修改)、DROP(删除)。
2.1 数据库操作
2.1.1 创建数据库
创建数据库时建议指定字符集(如utf8mb4,支持所有Unicode字符,包括emoji),避免中文乱码;同时使用IF NOT EXISTS避免"数据库已存在"错误。
细节补充:MySQL的"utf8"字符集实际仅支持3字节Unicode,无法存储emoji,必须使用utf8mb4;排序规则utf8mb4_general_ci为通用选择,若需精准中文排序可使用utf8mb4_unicode_ci。
-- 基础语法:创建数据库
CREATE DATABASE 数据库名;
-- 推荐用法:判断不存在则创建,并指定字符集
CREATE DATABASE IF NOT EXISTS my_database
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci; -- 排序规则,配合字符集使用
2.1.2 查询数据库
-- 查看所有数据库
SHOW DATABASES;
-- 查看当前使用的数据库
SELECT DATABASE();
2.1.3 切换数据库
操作表前必须先切换到目标数据库,否则会报"未选择数据库"错误。
USE my_database;
2.1.4 修改数据库
主要用于修改数据库字符集。
ALTER DATABASE my_database
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
2.1.5 删除数据库
删除数据库会清空其中所有表和数据,操作不可逆,生产环境需极度谨慎,建议添加IF EXISTS避免"数据库不存在"错误。
安全提示:生产环境删除数据库/表前,必须执行数据备份,可通过mysqldump -u 用户名 -p 数据库名 > 备份文件名.sql命令备份。
DROP DATABASE IF EXISTS my_database;
2.2 表结构操作
2.2.1 创建表(CREATE TABLE)
创建表是DDL的核心操作,需明确列名、数据类型、约束条件(主键、外键、非空等),确保数据完整性。
-- 示例:创建客户表(customer)
CREATE TABLE IF NOT EXISTS customer (
customer_id INT PRIMARY KEY AUTO_INCREMENT, -- 主键,自增(MySQL特有)
first_name VARCHAR(50) NOT NULL, -- 名,非空
last_name VARCHAR(50) NOT NULL, -- 姓,非空
email VARCHAR(100) NOT NULL UNIQUE, -- 邮箱,非空且唯一
phone VARCHAR(20), -- 电话,可空
create_date DATE DEFAULT CURRENT_DATE -- 创建日期,默认当前日期
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 指定存储引擎和字符集
实操细节:1. AUTO_INCREMENT默认起始值为1,可通过AUTO_INCREMENT=1000设置起始值;2. 若表中已有数据,新增自增主键需确保字段值唯一且非空。
关键说明:
-
约束条件: PRIMARY KEY:主键,唯一标识记录,自动包含非空约束;
-
NOT NULL:列值不可为空;
-
UNIQUE:列值唯一,允许空值(但空值只能出现一次);
-
DEFAULT:指定列的默认值,如CURRENT_DATE表示当前日期;
-
AUTO_INCREMENT:MySQL特有,主键为整数时自动递增,避免手动指定主键冲突。
存储引擎:InnoDB是MySQL默认引擎,支持事务和外键;MyISAM不支持事务,适合读多写少场景。
| 特性 | InnoDB | MyISAM |
|---|---|---|
| 事务支持 | 是 | 否 |
| 外键支持 | 是 | 否 |
| 锁粒度 | 行级锁(高并发友好) | 表级锁(并发差) |
| 适用场景 | 电商、金融等业务 | 日志、报表等只读场景 |
2.2.2 查看表结构
-- 查看当前数据库所有表
SHOW TABLES;
-- 查看表的详细结构(列名、类型、约束等)
DESCRIBE customer; -- 简写:DESC customer
-- 查看创建表的SQL语句(可验证表结构)
SHOW CREATE TABLE customer;
2.2.3 修改表结构(ALTER TABLE)
用于添加列、修改列、删除列、添加约束等,是表结构迭代的常用操作。
-- 1. 添加列(给客户表添加“地址”列)
ALTER TABLE customer
ADD COLUMN address VARCHAR(200) DEFAULT '未填写';
-- 2. 修改列(修改电话列长度为25,且非空)
ALTER TABLE customer
MODIFY COLUMN phone VARCHAR(25) NOT NULL DEFAULT '未知';
-- 3. 重命名列(将address列重命名为full_address)
ALTER TABLE customer
CHANGE COLUMN address full_address VARCHAR(200); -- CHANGE需重新指定数据类型
-- 4. 删除列(删除phone列)
ALTER TABLE customer
DROP COLUMN phone;
-- 5. 添加外键约束(订单表关联客户表)
-- 先创建订单表
CREATE TABLE IF NOT EXISTS orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT, -- 关联客户表的主键
order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
order_amount DECIMAL(10,2) NOT NULL
);
-- 给订单表添加外键
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id)
REFERENCES customer(customer_id) -- 关联客户表的customer_id列
ON DELETE CASCADE; -- 级联删除:若客户被删除,关联订单也删除
2.2.4 删除表(DROP TABLE)
删除表会清空表数据和表结构,操作不可逆,需谨慎。
-- 基础用法
DROP TABLE 表名;
-- 推荐用法:判断表存在则删除
DROP TABLE IF EXISTS orders;
三、数据操纵语言(DML)------ 操作表中数据
DML用于对表中数据进行插入、更新、删除,操作后需通过COMMIT提交事务(部分数据库如MySQL默认自动提交),ROLLBACK可回滚未提交的操作。核心命令:INSERT(插入)、UPDATE(更新)、DELETE(删除)。
3.1 插入数据(INSERT)
3.1.1 插入单行数据
指定列名插入,即使表结构变化也不易出错,是推荐用法。
-- 语法:INSERT INTO 表名 (列1, 列2, ...) VALUES (值1, 值2, ...)
INSERT INTO customer (first_name, last_name, email, create_date)
VALUES ('John', 'Doe', 'john.doe@example.com', '2025-01-01');
3.1.2 插入多行数据
批量插入比单行插入效率更高,适合初始化测试数据或批量导入场景。
INSERT INTO customer (first_name, last_name, email, create_date)
VALUES
('Alice', 'Smith', 'alice.smith@example.com', '2025-02-01'),
('Bob', 'Johnson', 'bob.johnson@example.com', '2025-03-01'),
('Charlie', 'Brown', 'charlie.brown@example.com', '2025-04-01');
3.1.3 插入查询结果
将一个查询的结果插入到另一个表中,实现数据批量迁移。
-- 先创建临时表(结构与customer部分列一致)
CREATE TABLE IF NOT EXISTS customer_temp (
name VARCHAR(100),
contact_email VARCHAR(100)
);
-- 将customer表的姓名拼接后插入临时表
INSERT INTO customer_temp (name, contact_email)
SELECT CONCAT(first_name, ' ', last_name), email -- 拼接姓名
FROM customer
WHERE create_date > '2025-02-01'; -- 筛选条件
3.1.4 插入注意事项
-
值与列的数量、数据类型必须匹配,如字符串需用单引号包裹,日期格式需符合数据库要求(如YYYY-MM-DD);
-
非空列必须提供值,或依赖默认值;
-
自增主键无需手动插入值,数据库会自动生成;
-
批量插入时,VALUES后括号组数量不宜过多(建议不超过1000组),避免超出数据库单次执行语句长度限制。
3.2 更新数据(UPDATE)
用于修改表中符合条件的记录,必须加WHERE子句指定筛选条件,否则会更新表中所有记录,是SQL操作中极易出错的场景。
3.2.1 单表更新
-- 语法:UPDATE 表名 SET 列1=值1, 列2=值2 WHERE 条件
-- 示例:更新John的邮箱和地址
UPDATE customer
SET email = 'john.new@example.com', full_address = '北京市朝阳区'
WHERE customer_id = 1; -- 用主键筛选,精准定位单条记录
3.2.2 多表关联更新
根据另一表的数据更新当前表,如根据客户等级更新订单折扣。
-- 示例:给VIP客户的订单添加10%折扣(假设customer表有vip_level列)
UPDATE orders o
JOIN customer c ON o.customer_id = c.customer_id
SET o.discount = 0.1 -- 添加折扣
WHERE c.vip_level = 'VIP'; -- 筛选VIP客户
3.2.3 更新注意事项
-
优先用主键或唯一键作为WHERE条件,确保更新范围精准;
-
更新前先执行对应的SELECT语句验证筛选结果,确认无误后再执行UPDATE;
-
避免更新主键值,可能破坏表间关联;
-
若更新大量数据(如10万+条),建议分批次执行(配合LIMIT),避免长时间锁表,示例:
UPDATE customer SET status=1 WHERE create_date < '2024-01-01' LIMIT 1000;,循环执行至无数据可更。
事务保障:重要数据更新需开启事务,执行后验证结果,无误再提交,错误则回滚。
3.3 删除数据(DELETE)
用于删除表中符合条件的记录,同样需加WHERE子句,否则会删除表中所有数据。
3.3.1 单表删除
-- 语法:DELETE FROM 表名 WHERE 条件
-- 示例:删除创建日期在2025年1月1日前的临时客户
DELETE FROM customer
WHERE create_date < '2025-01-01' AND email LIKE '%temp%';
3.3.2 多表关联删除
-- 示例:删除Alice的所有订单
DELETE o
FROM orders o
JOIN customer c ON o.customer_id = c.customer_id
WHERE c.first_name = 'Alice' AND c.last_name = 'Smith';
3.3.3 删除注意事项
-
与UPDATE同理,先通过SELECT验证筛选结果,再执行DELETE;
-
若需清空表中所有数据,且无需回滚,可使用TRUNCATE,效率比DELETE更高(直接删除表数据,保留表结构):
TRUNCATE TABLE customer_temp;; -
与UPDATE同理,先通过SELECT验证筛选结果,再执行DELETE;
-
若需清空表中所有数据,且无需回滚,可使用TRUNCATE,效率比DELETE更高(直接删除表数据,保留表结构):
TRUNCATE TABLE customer_temp;; -
TRUNCATE无法加WHERE条件,且会重置自增主键的起始值,DELETE不会;
-
TRUNCATE需要DROP权限,而DELETE需要DELETE权限;若表被外键关联,TRUNCATE会执行失败,需先删除外键约束或关联数据。
四、数据查询语言(DQL)------ 检索数据(核心)
DQL是SQL最常用、最核心的部分,用于从一个或多个表中检索满足条件的数据,核心命令为SELECT,可搭配WHERE、GROUP BY、HAVING、ORDER BY、LIMIT等子句实现复杂查询。
4.1 基础查询语法
完整语法:SELECT 列名 FROM 表名 WHERE 行筛选条件 GROUP BY 分组字段 HAVING 分组筛选条件 ORDER BY 排序字段 LIMIT 结果限制数;
执行顺序(重要):FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT,理解执行顺序是编写复杂查询的关键。
经典案例:SELECT中使用的列别名,不能在WHERE中使用(因WHERE执行在SELECT前),但可在ORDER BY中使用(ORDER BY执行在SELECT后)。例如:SELECT name, age+10 AS new_age FROM user WHERE age+10 > 30;(正确),SELECT name, age+10 AS new_age FROM user WHERE new_age > 30;(错误)。
4.2 基础查询操作
4.2.1 检索指定列
-- 检索单个列:查询所有客户的姓名和邮箱
SELECT first_name, last_name, email FROM customer;
-- 检索多个列:用逗号分隔
SELECT order_id, customer_id, order_amount FROM orders;
-- 列别名:用AS给列起别名,简化结果显示(AS可省略)
SELECT CONCAT(first_name, ' ', last_name) AS full_name, email AS contact_email
FROM customer;
4.2.2 检索所有列
用通配符*表示所有列,开发中不推荐使用(会检索无用列,降低效率,且表结构变化后结果易混乱),仅适合临时查询。
SELECT * FROM customer;
4.2.3 去重查询(DISTINCT)
用于过滤结果中重复的记录,仅保留唯一值,作用于所有指定列的组合。
-- 示例:查询所有有订单的客户ID(避免重复)
SELECT DISTINCT customer_id FROM orders;
4.2.4 限制结果集(LIMIT)
用于限制返回的记录数,常用于分页查询,MySQL特有;SQL Server用TOP,Oracle用ROWNUM。
-- 基础用法:返回前5条记录
SELECT * FROM customer LIMIT 5;
-- 分页用法:LIMIT 起始索引, 每页记录数(起始索引从0开始)
SELECT * FROM customer LIMIT 10, 5; -- 从第11条开始,返回5条(第2页,每页5条)
4.3 条件筛选(WHERE子句)
WHERE子句用于在分组前筛选行数据,支持多种条件运算符,是实现"精准查询"的核心。
4.3.1 比较运算符
| 运算符 | 含义 | 示例 |
|---|---|---|
| = | 等于 | WHERE customer_id = 1 |
| <> / != | 不等于 | WHERE order_amount != 0 |
| > / < | 大于 / 小于 | WHERE order_amount > 1000 |
| >= / <= | 大于等于 / 小于等于 | WHERE age <= 30 |
| BETWEEN ... AND ... | 在指定范围之间(包含边界) | WHERE order_date BETWEEN '2025-01-01' AND '2025-03-31' |
| IN (值列表) | 匹配列表中的任意一个值 | WHERE customer_id IN (1, 3, 5) |
| NOT IN (值列表) | 不匹配列表中的任何值 | WHERE vip_level NOT IN ('普通', '青铜') |
4.3.2 逻辑运算符
用于组合多个条件,优先级:NOT > AND > OR,可加括号改变优先级。
| 运算符 | 含义 | 示例 |
|---|---|---|
| AND | 同时满足多个条件 | WHERE order_amount > 500 AND order_date > '2025-02-01' |
| OR | 满足任一条件 | WHERE vip_level = 'VIP' OR order_amount > 2000 |
| NOT | 取反条件 | WHERE NOT email IS NULL |
4.3.3 空值判断(IS NULL / IS NOT NULL)
空值(NULL)不等于0或空字符串,必须用IS NULL判断,不能用=或!=。
常见误区:1. WHERE phone = NULL(错误,永远返回空结果);2. WHERE phone != NULL(错误);正确写法为WHERE phone IS NULL或WHERE phone IS NOT NULL。
-- 示例:查询没有填写电话的客户
SELECT * FROM customer WHERE phone IS NULL;
-- 示例:查询已填写地址的客户
SELECT * FROM customer WHERE full_address IS NOT NULL;
4.3.4 模糊查询(LIKE)
用于匹配字符串的部分内容,配合通配符使用,适合根据关键词搜索场景,但效率较低,避免在大数据量字段上频繁使用。
| 通配符 | 含义 | 示例 |
|---|---|---|
| % | 匹配零个或多个任意字符 | WHERE first_name LIKE 'J%'(匹配以J开头的名) |
| _ | 匹配单个任意字符 | WHERE last_name LIKE '_ohnson'(匹配第二个字符开始为ohnson的姓) |
-- 示例:查询邮箱包含example的客户
SELECT * FROM customer WHERE email LIKE '%example%';
4.4 排序(ORDER BY)
用于对查询结果按指定字段排序,默认升序(ASC),可指定降序(DESC),支持多字段排序。
-- 基础用法:按订单金额降序排序(从高到低)
SELECT order_id, order_amount FROM orders ORDER BY order_amount DESC;
-- 多字段排序:先按客户ID升序,再按订单日期降序
SELECT order_id, customer_id, order_date FROM orders
ORDER BY customer_id ASC, order_date DESC;
-- 按别名排序:按总金额排序(假设总金额是计算字段)
SELECT order_id, quantity * price AS total FROM order_detail
ORDER BY total DESC;
4.5 聚合函数与分组查询
4.5.1 常用聚合函数
聚合函数用于对一组数据进行统计计算,返回单个结果,常与GROUP BY搭配使用。
| 函数 | 作用 | 示例 |
|---|---|---|
| COUNT() | 统计记录数,COUNT(列名)排除空值,COUNT(*)包含所有记录 | COUNT(customer_id):统计非空客户ID数 |
| SUM() | 求数值列的总和,自动忽略空值 | SUM(order_amount):统计订单总金额 |
| AVG() | 求数值列的平均值 | AVG(order_amount):统计订单平均金额 |
| MAX() | 求列的最大值 | MAX(order_date):获取最新订单日期 |
| MIN() | 求列的最小值 | MIN(order_amount):获取最小订单金额 |
-- 示例:统计订单相关数据
SELECT
COUNT(order_id) AS total_orders, -- 总订单数
SUM(order_amount) AS total_sales, -- 总销售额
AVG(order_amount) AS avg_order_amount, -- 平均订单金额
MAX(order_amount) AS max_order, -- 最大订单金额
MIN(order_date) AS first_order_date -- 首单日期
FROM orders;
4.5.2 分组查询(GROUP BY)
将数据按指定字段分组,对每组单独应用聚合函数,实现"按组统计",如按地区统计销量、按客户统计订单数。
-- 示例1:按客户ID分组,统计每个客户的订单数和总消费金额
SELECT
customer_id,
COUNT(order_id) AS order_count,
SUM(order_amount) AS total_spent
FROM orders
GROUP BY customer_id; -- 分组字段
-- 示例2:按客户等级和创建月份分组,统计每组客户数
SELECT
vip_level,
DATE_FORMAT(create_date, '%Y-%m') AS create_month, -- 按月份格式化日期
COUNT(customer_id) AS customer_count
FROM customer
GROUP BY vip_level, create_month; -- 多字段分组:先按等级,再按月份
分组规则:GROUP BY后的字段必须出现在SELECT子句中(或在聚合函数内),否则会报错(依赖数据库严格模式)。
模式说明:MySQL的sql_mode参数若包含ONLY_FULL_GROUP_BY(默认开启),会强制遵循分组规则;若需临时关闭,可执行SET sql_mode = '';,但不推荐,可能导致数据查询不准确。
4.5.3 分组筛选(HAVING)
HAVING用于对分组后的结果进行筛选,与WHERE的区别:WHERE筛选行数据(分组前),HAVING筛选组数据(分组后),HAVING可使用聚合函数,WHERE不能。
-- 示例:统计订单数大于5且总消费超过10000的客户(分组后筛选)
SELECT
customer_id,
COUNT(order_id) AS order_count,
SUM(order_amount) AS total_spent
FROM orders
GROUP BY customer_id
HAVING order_count > 5 AND total_spent > 10000; -- 筛选符合条件的组
-- 错误示例:WHERE不能使用聚合函数
SELECT customer_id, COUNT(order_id) AS order_count
FROM orders
WHERE order_count > 5 -- 报错:order_count是聚合结果,分组前不存在
GROUP BY customer_id;
4.6 多表关联查询(JOIN)
当需要从多个相关表中获取数据时,需使用JOIN实现表间关联,核心是通过"关联字段"(通常是主键和外键)建立表间联系,避免产生笛卡尔积(表数据无意义的组合)。
笛卡尔积风险:若关联查询未加ON条件,会导致两表数据全量组合,例如100条客户数据关联1000条订单数据,会产生10万条结果,严重占用资源,务必确保关联条件正确。
4.6.1 关联查询类型
| 关联类型 | 含义 | 关键字 |
|---|---|---|
| 内连接 | 只返回两表中匹配关联条件的记录 | INNER JOIN(可省略INNER) |
| 左外连接 | 返回左表所有记录,右表匹配不到则显示NULL | LEFT JOIN / LEFT OUTER JOIN |
| 右外连接 | 返回右表所有记录,左表匹配不到则显示NULL | RIGHT JOIN / RIGHT OUTER JOIN |
| 全外连接 | 返回两表所有记录,匹配不到的部分显示NULL(MySQL不直接支持,需用UNION实现) | FULL JOIN / FULL OUTER JOIN |
4.6.2 关联查询示例
-- 1. 内连接:查询有订单的客户信息及对应订单号(只显示匹配记录)
SELECT
c.customer_id,
CONCAT(c.first_name, ' ', c.last_name) AS full_name,
o.order_id,
o.order_date
FROM customer c -- 表别名:用c简化customer
INNER JOIN orders o -- 表别名:用o简化orders
ON c.customer_id = o.customer_id; -- 关联条件:客户表主键=订单表外键
-- 2. 左外连接:查询所有客户信息及订单数(包括无订单的客户)
SELECT
c.customer_id,
c.full_name,
COUNT(o.order_id) AS order_count -- 无订单则为0
FROM customer c
LEFT JOIN orders o
ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.full_name; -- 左表字段分组
-- 3. 多表关联:查询客户-订单-订单详情的完整信息(三表关联)
SELECT
c.full_name,
o.order_id,
od.product_name, -- 订单详情表的商品名
od.quantity, -- 购买数量
od.unit_price -- 单价
FROM customer c
LEFT JOIN orders o ON c.customer_id = o.customer_id
LEFT JOIN order_detail od ON o.order_id = od.order_id -- 关联订单详情表
WHERE o.order_date BETWEEN '2025-01-01' AND '2025-03-31'; -- 筛选时间范围
4.7 子查询
子查询是嵌套在主查询中的查询语句,可作为主查询的条件、数据源或字段值,用于简化复杂查询,按作用场景分为以下几类:
4.7.1 作为条件的子查询(IN / EXISTS)
性能对比:1. 当子查询结果集较小时,IN效率更高;2. 当子查询结果集较大(1万+条),EXISTS效率更优,因EXISTS只需判断"是否存在",无需返回全部结果。
-- 示例1:用IN查询购买过“智能手机”的客户信息(子查询获取客户ID)
SELECT * FROM customer
WHERE customer_id IN (
SELECT customer_id FROM orders o
JOIN order_detail od ON o.order_id = od.order_id
WHERE od.product_name = '智能手机'
);
-- 示例2:用EXISTS判断是否存在关联订单(效率比IN更高,尤其大数据量)
SELECT * FROM customer c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id AND o.order_amount > 2000
); -- 存在金额>2000的订单的客户
-- 示例1:用IN查询购买过“智能手机”的客户信息(子查询获取客户ID)
SELECT * FROM customer
WHERE customer_id IN (
SELECT customer_id FROM orders o
JOIN order_detail od ON o.order_id = od.order_id
WHERE od.product_name = '智能手机'
);
-- 示例2:用EXISTS判断是否存在关联订单(效率比IN更高,尤其大数据量)
SELECT * FROM customer c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id AND o.order_amount > 2000
); -- 存在金额>2000的订单的客户
4.7.2 作为数据源的子查询(FROM子句中)
子查询的结果作为主查询的"临时表",需给临时表起别名。
-- 示例:查询每个客户的最新订单信息(先通过子查询获取每个客户的最新订单日期)
SELECT c.full_name, o.order_id, o.order_date, o.order_amount
FROM customer c
JOIN (
-- 子查询:按客户分组,获取每个客户的最新订单日期
SELECT customer_id, MAX(order_date) AS latest_date
FROM orders
GROUP BY customer_id
) temp ON c.customer_id = temp.customer_id -- 关联临时表
JOIN orders o ON temp.customer_id = o.customer_id AND temp.latest_date = o.order_date;
4.7.3 作为字段的子查询(SELECT子句中)
子查询的结果作为主查询的一个字段值,需确保子查询只返回单行单列。
-- 示例:查询每个客户的姓名及首单日期(子查询作为首单日期字段)
SELECT
CONCAT(first_name, ' ', last_name) AS full_name,
(SELECT MIN(order_date) FROM orders o WHERE o.customer_id = c.customer_id) AS first_order_date
FROM customer c;
五、数据控制语言(DCL)------ 权限与事务管理
5.1 权限管理
DCL用于管理数据库用户的创建和权限分配,确保数据库安全,核心命令:CREATE USER(创建用户)、GRANT(授权)、REVOKE(撤销权限)、DROP USER(删除用户)。
5.1.1 创建用户
-- 语法:CREATE USER '用户名'@'访问主机' IDENTIFIED BY '密码';
-- 示例1:创建本地用户(只能本地访问,主机为localhost)
CREATE USER 'user_local'@'localhost' IDENTIFIED BY 'User@123456';
-- 示例2:创建远程用户(允许任意主机访问,主机为%,生产环境需限制IP)
CREATE USER 'user_remote'@'%' IDENTIFIED BY 'User@123456';
5.1.2 授权(GRANT)
常见权限:SELECT(查询)、INSERT(插入)、UPDATE(更新)、DELETE(删除)、CREATE(创建)、DROP(删除)、ALL PRIVILEGES(所有权限)。
安全原则:遵循"最小权限"原则,例如给报表查询用户仅授予SELECT权限,避免授予ALL PRIVILEGES,降低数据泄露风险。
-- 语法:GRANT 权限 ON 数据库.表 TO '用户名'@'访问主机';
-- 示例1:给本地用户授予my_database数据库所有表的查询和插入权限
GRANT SELECT, INSERT ON my_database.* TO 'user_local'@'localhost';
-- 示例2:给远程用户授予所有数据库所有表的所有权限(谨慎使用)
GRANT ALL PRIVILEGES ON *.* TO 'user_remote'@'%';
-- 刷新权限(权限修改后生效)
FLUSH PRIVILEGES;
5.1.3 撤销权限(REVOKE)
-- 语法:REVOKE 权限 ON 数据库.表 FROM '用户名'@'访问主机';
-- 示例:撤销本地用户的插入权限
REVOKE INSERT ON my_database.* FROM 'user_local'@'localhost';
FLUSH PRIVILEGES;
5.1.4 删除用户
-- 语法:DROP USER '用户名'@'访问主机';
DROP USER 'user_remote'@'%';
5.2 事务管理
事务是一组不可分割的SQL操作,要么全部执行成功(COMMIT),要么全部执行失败(ROLLBACK),用于保证数据一致性,如转账操作(扣款和收款必须同时成功或失败)。
5.2.1 事务的ACID特性
-
原子性(Atomicity):事务是一个不可分割的整体,操作要么全成,要么全败;
-
一致性(Consistency):事务执行前后,数据总状态保持一致,如转账后总金额不变;
-
隔离性(Isolation):多个事务并发执行时,相互不干扰;
-
持久性(Durability):事务提交后,数据修改永久生效,即使数据库崩溃也不会丢失。
隔离级别补充:MySQL默认隔离级别为REPEATABLE READ(可重复读),可通过SET TRANSACTION ISOLATION LEVEL 级别修改,其他级别包括READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、SERIALIZABLE(串行化)。
5.2.2 事务控制命令
MySQL默认自动提交事务(每条SQL执行后自动COMMIT),需先关闭自动提交(SET AUTOCOMMIT = 0)开启手动事务。
-- 1. 关闭自动提交(开启手动事务)
SET AUTOCOMMIT = 0;
-- 2. 开启事务(可选,关闭自动提交后默认开启)
START TRANSACTION;
-- 3. 执行SQL操作(如转账:A扣1000,B加1000)
UPDATE account SET balance = balance - 1000 WHERE user_id = 1; -- A扣款
UPDATE account SET balance = balance + 1000 WHERE user_id = 2; -- B收款
-- 4. 验证操作结果(无语法错误且数据正确)
SELECT balance FROM account WHERE user_id IN (1, 2);
-- 5. 提交事务(操作生效,不可逆)
COMMIT;
-- 若步骤4验证失败,回滚事务(恢复到操作前状态)
-- ROLLBACK;
六、SQL高级特性
6.1 视图(VIEW)
视图是虚拟表,基于预定义的SQL查询创建,本身不存储数据,查询视图时会动态执行底层SQL,用于简化复杂查询、隐藏敏感数据、控制数据访问范围。
-- 1. 创建视图(延续前文客户订单统计场景)
CREATE VIEW v_customer_order AS
SELECT
c.customer_id,
CONCAT(c.first_name, ' ', c.last_name) AS full_name,
COUNT(o.order_id) AS order_count,
SUM(o.order_amount) AS total_spent
FROM customer c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, full_name;
-- 2. 查询视图(与操作普通表完全一致)
SELECT * FROM v_customer_order WHERE order_count > 3;
-- 3. 修改视图(两种方式:CREATE OR REPLACE 或 ALTER)
-- 方式1:CREATE OR REPLACE(推荐,不存在则创建,存在则覆盖)
CREATE OR REPLACE VIEW v_customer_order AS
SELECT
c.customer_id,
c.email, -- 新增邮箱字段
CONCAT(c.first_name, ' ', c.last_name) AS full_name,
COUNT(o.order_id) AS order_count
FROM customer c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.email, full_name;
-- 方式2:ALTER VIEW
ALTER VIEW v_customer_order AS
SELECT
c.customer_id,
full_name,
order_count,
IF(total_spent > 10000, '高价值', '普通') AS customer_level -- 新增客户等级判断
FROM v_customer_order; -- 可基于现有视图修改
-- 4. 删除视图
DROP VIEW IF EXISTS v_customer_order;
-- 5. 查看视图结构
DESC v_customer_order;
SHOW CREATE VIEW v_customer_order;
视图注意事项:
-
视图依赖底层表结构,若底层表字段删除或修改,视图可能失效,需同步更新;
-
并非所有视图都可修改(如含聚合函数、GROUP BY、DISTINCT的视图),修改视图本质是修改底层表数据;
-
避免在视图上嵌套视图,会降低查询效率,难以调试;
-
视图可设置权限,例如给普通用户授予视图的查询权限,隐藏底层表的敏感字段(如手机号、身份证号)。
-
视图依赖底层表结构,若底层表字段删除或修改,视图可能失效,需同步更新;
-
并非所有视图都可修改(如含聚合函数、GROUP BY、DISTINCT的视图),修改视图本质是修改底层表数据;
-
避免在视图上嵌套视图,会降低查询效率,难以调试。
6.2 存储过程(Stored Procedure)
存储过程是预先编译并存储在数据库中的一组SQL语句集合,可通过调用名称执行,支持传入参数和返回结果,适合实现复杂业务逻辑(如订单创建、数据批量处理),减少客户端与数据库的交互次数。
6.2.1 存储过程语法
-- 基本结构
DELIMITER // -- 临时修改语句结束符为//(避免与存储过程内的;冲突)
CREATE PROCEDURE 存储过程名(参数类型 参数名 数据类型)
BEGIN
-- SQL语句集合
END //
DELIMITER ; -- 恢复语句结束符为;
参数类型:
-
IN:输入参数(默认类型,用于向存储过程传递值);
-
OUT:输出参数(用于从存储过程返回值给调用者);
-
INOUT:输入输出参数(既传递值,又返回结果)。
6.2.2 存储过程示例
-- 示例1:无参数存储过程——查询所有高价值客户(消费>10000)
DELIMITER //
CREATE PROCEDURE sp_query_high_value_customer()
BEGIN
SELECT
CONCAT(first_name, ' ', last_name) AS full_name,
email,
total_spent
FROM v_customer_order
WHERE total_spent > 10000
ORDER BY total_spent DESC;
END //
DELIMITER ;
-- 调用存储过程
CALL sp_query_high_value_customer();
-- 示例2:带IN参数存储过程——根据客户ID查询订单详情
DELIMITER //
CREATE PROCEDURE sp_query_order_by_customer(IN c_id INT)
BEGIN
SELECT
o.order_id,
o.order_date,
od.product_name,
od.quantity,
od.unit_price,
od.quantity * od.unit_price AS item_total
FROM orders o
JOIN order_detail od ON o.order_id = od.order_id
WHERE o.customer_id = c_id;
END //
DELIMITER ;
-- 调用(查询客户ID=1的订单)
CALL sp_query_order_by_customer(1);
-- 示例3:带OUT参数存储过程——统计指定客户的订单总数和总消费
DELIMITER //
CREATE PROCEDURE sp_calculate_customer_spent(IN c_id INT, OUT order_cnt INT, OUT total_amt DECIMAL(10,2))
BEGIN
SELECT
COUNT(order_id),
SUM(order_amount)
INTO order_cnt, total_amt -- 将结果赋值给输出参数
FROM orders
WHERE customer_id = c_id;
END //
DELIMITER ;
-- 调用(需先定义变量接收输出结果)
SET @cnt = 0;
SET @amt = 0.00;
CALL sp_calculate_customer_spent(1, @cnt, @amt);
-- 查看结果
SELECT @cnt AS order_count, @amt AS total_spent;
6.2.3 存储过程管理
-- 查看所有存储过程
SHOW PROCEDURE STATUS;
-- 查看存储过程创建语句
SHOW CREATE PROCEDURE sp_query_high_value_customer;
-- 删除存储过程
DROP PROCEDURE IF EXISTS sp_query_high_value_customer;
6.2.4 存储过程注意事项
-
存储过程编译后存储在数据库,首次执行效率高,后续调用无需重新编译;
-
业务逻辑变更时只需修改存储过程,无需修改客户端代码,便于维护;
-
缺点是调试难度较高(需依赖数据库工具),且不同数据库的存储过程语法不兼容;
-
存储过程中可使用异常处理(DECLARE EXIT HANDLER)捕获错误,示例:
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;,确保异常时回滚事务。
6.3 函数(Function)
函数与存储过程类似,也是预编译的SQL集合,但核心区别是函数必须返回一个值,可在SELECT语句中直接使用,适合实现简单的计算逻辑(如数据格式化、数值计算)。
6.3.1 函数语法
DELIMITER //
CREATE FUNCTION 函数名(参数名 数据类型)
RETURNS 返回值数据类型 -- 必须指定返回值类型
DETERMINISTIC -- 可选,标识函数输入相同则输出相同
BEGIN
-- 函数体(需用RETURN返回结果)
RETURN 结果;
END //
DELIMITER ;
6.3.2 函数示例
-- 示例1:简单函数——拼接客户姓名(首字母大写)
DELIMITER //
CREATE FUNCTION fn_format_name(f_name VARCHAR(50), l_name VARCHAR(50))
RETURNS VARCHAR(100)
DETERMINISTIC
BEGIN
-- 首字母大写函数:UPPER(LEFT(字段,1)) + LOWER(SUBSTRING(字段,2))
RETURN CONCAT(
UPPER(LEFT(f_name, 1)), LOWER(SUBSTRING(f_name, 2)),
' ',
UPPER(LEFT(l_name, 1)), LOWER(SUBSTRING(l_name, 2))
);
END //
DELIMITER ;
-- 调用(在SELECT中使用)
SELECT customer_id, fn_format_name(first_name, last_name) AS formatted_name FROM customer;
-- 示例2:计算函数——根据订单金额和数量计算折扣后总价(满2000减300)
DELIMITER //
CREATE FUNCTION fn_calculate_discount(quantity INT, unit_price DECIMAL(10,2))
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
DECLARE total DECIMAL(10,2); -- 声明局部变量
SET total = quantity * unit_price;
-- 满减逻辑
IF total >= 2000 THEN
RETURN total - 300;
ELSEIF total >= 1000 THEN
RETURN total * 0.9;
ELSE
RETURN total;
END IF;
END //
DELIMITER ;
-- 调用(计算订单详情的折扣后金额)
SELECT
product_name,
quantity,
unit_price,
fn_calculate_discount(quantity, unit_price) AS discount_total
FROM order_detail;
6.3.3 函数管理与注意事项
-- 查看函数
SHOW FUNCTION STATUS;
-- 查看函数创建语句
SHOW CREATE FUNCTION fn_format_name;
-- 删除函数
DROP FUNCTION IF EXISTS fn_format_name;
-
函数必须返回值,且返回值类型与定义一致;
-
函数不能包含修改数据的SQL(如INSERT、UPDATE、DELETE),存储过程可以;
-
函数可嵌入SELECT语句,存储过程需用CALL单独调用。
6.4 触发器(Trigger)
触发器是与表关联的特殊存储过程,无需手动调用,当表发生特定事件(INSERT、UPDATE、DELETE)时自动执行,用于实现数据的自动校验、关联更新、日志记录等场景,保证数据完整性。
6.4.1 触发器核心要素
-
触发事件:INSERT(插入)、UPDATE(更新)、DELETE(删除);
-
触发时机:BEFORE(事件执行前)、AFTER(事件执行后);
-
触发对象:仅作用于特定表,每个表的同一事件-时机组合只能有一个触发器。
特殊变量:
-
NEW:触发事件后的数据(INSERT/UPDATE时有效,NEW.字段名获取新值);
-
OLD:触发事件前的数据(UPDATE/DELETE时有效,OLD.字段名获取旧值)。
6.4.2 触发器示例
-- 示例1:BEFORE INSERT触发器——校验订单金额不能为负
DELIMITER //
CREATE TRIGGER trg_check_order_amount BEFORE INSERT ON orders
FOR EACH ROW -- 行级触发器,每插入一行触发一次
BEGIN
IF NEW.order_amount <= 0 THEN
SIGNAL SQLSTATE '45000' -- 自定义错误状态码
SET MESSAGE_TEXT = '订单金额不能为负数或零'; -- 错误提示
END IF;
END //
DELIMITER ;
-- 测试:插入负数金额订单(会触发错误)
INSERT INTO orders (customer_id, order_amount) VALUES (1, -100);
-- 示例2:AFTER INSERT触发器——订单创建后更新客户最后下单时间
-- 先给customer表添加last_order_date字段
ALTER TABLE customer ADD COLUMN last_order_date DATETIME;
DELIMITER //
CREATE TRIGGER trg_update_customer_order_date AFTER INSERT ON orders
FOR EACH ROW
BEGIN
UPDATE customer
SET last_order_date = NEW.order_date
WHERE customer_id = NEW.customer_id;
END //
DELIMITER ;
-- 测试:插入订单后,客户表的last_order_date会自动更新
INSERT INTO orders (customer_id, order_date, order_amount) VALUES (1, NOW(), 1500);
-- 示例3:AFTER DELETE触发器——记录订单删除日志
-- 先创建订单日志表
CREATE TABLE IF NOT EXISTS order_log (
log_id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT,
operate_type VARCHAR(20),
operate_time DATETIME,
operate_desc VARCHAR(200)
);
DELIMITER //
CREATE TRIGGER trg_log_order_delete AFTER DELETE ON orders
FOR EACH ROW
BEGIN
INSERT INTO order_log (order_id, operate_type, operate_time, operate_desc)
VALUES (OLD.order_id, 'DELETE', NOW(), CONCAT('删除订单ID:', OLD.order_id, ',金额:', OLD.order_amount));
END //
DELIMITER ;
-- 测试:删除订单后,日志表会自动记录
DELETE FROM orders WHERE order_id = 1;
6.4.3 触发器管理与注意事项
-- 查看触发器
SHOW TRIGGERS;
-- 删除触发器
DROP TRIGGER IF EXISTS trg_check_order_amount;
-
触发器逻辑需简洁,避免复杂操作(如嵌套触发),否则会导致性能问题和调试困难;
-
触发器执行失败会导致原事件(如INSERT)也失败,需确保逻辑严谨;
-
避免在触发器中修改触发它的表(如在orders的INSERT触发器中再插入orders数据),会导致死循环;
-
MySQL的触发器不支持动态SQL(如PREPARE语句),复杂逻辑建议用存储过程替代。
七、SQL优化技巧
SQL优化的核心目标是提升查询效率、减少数据库资源占用,需从索引、查询语句、表结构等多维度入手,以下是高频实用优化方法。
7.1 合理使用索引
索引是提升查询速度的"加速器",通过构建有序数据结构(如B+树)快速定位数据,避免全表扫描,但会增加写入(INSERT/UPDATE/DELETE)的开销,需平衡读写场景。
7.1.1 索引类型与创建
-- 1. 主键索引(自动创建,无需手动创建)
-- 创建表时指定主键即可,如customer_id INT PRIMARY KEY
-- 2. 唯一索引(用于唯一约束字段,如邮箱)
CREATE UNIQUE INDEX idx_customer_email ON customer(email);
-- 3. 普通索引(用于频繁查询的条件字段,如订单日期)
CREATE INDEX idx_orders_date ON orders(order_date);
-- 4. 联合索引(用于多字段组合查询,遵循“最左匹配原则”)
-- 示例:频繁按“客户ID+订单日期”查询,创建联合索引
CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date);
-- 5. 删除索引
DROP INDEX idx_customer_email ON customer;
7.1.2 索引使用原则
-
优先给WHERE条件、JOIN关联字段、ORDER BY排序字段创建索引;
-
避免给"低基数字段"(如性别,只有男/女)创建索引,索引效果差;
-
联合索引需按"查询频率从高到低"排列字段,且查询时需包含左前缀字段才能命中索引;
-
避免在索引字段上使用函数(如DATE(order_date))或计算,会导致索引失效,需改在业务层处理;
-
定期维护索引:通过
EXPLAIN分析索引使用情况,删除长期未使用的冗余索引;对频繁更新的表,避免创建过多索引。
7.2 优化查询语句
-
避免SELECT *:只查询需要的字段,减少数据传输量和内存占用;
-
优化WHERE条件: 避免使用!=、<>、NOT IN、IS NOT NULL,会导致索引失效,可用其他方式替代(如NOT IN改LEFT JOIN);
-
模糊查询避免前缀模糊(如LIKE '%example'),会导致全表扫描,可改用后缀模糊(LIKE 'example%')或全文索引;
-
用BETWEEN替代多个OR,如WHERE age BETWEEN 18 AND 30 优于 WHERE age >=18 OR age <=30。
-
优化JOIN查询: 小表驱动大表(如LEFT JOIN时,左表为小表),减少循环次数;
-
JOIN条件必须使用索引字段(通常是主键和外键),避免笛卡尔积。
-
合理使用LIMIT :分页查询时搭配ORDER BY和索引,避免偏移量过大(如LIMIT 10000, 10),可改用"主键分页"优化:
-- 低效:偏移量10000,需扫描前10010条数据 SELECT * FROM customer ORDER BY customer_id LIMIT 10000, 10; -- 高效:利用主键有序性,直接定位起始位置SELECT * FROM customer WHERE customer_id > 10000 ORDER BY customer_id LIMIT 10;
7.3 表结构与存储优化
-
选择合适数据类型:如用TINYINT存储年龄(1-120),比INT更节省空间;用VARCHAR替代CHAR存储变长文本;
-
分表分库:应对大数据量(如千万级以上),可按时间(如订单表按月份分表)或用户ID(如客户表按ID哈希分表)拆分,减少单表数据量;
-
避免冗余字段:如"订单总价"可通过"数量*单价"计算,无需存储,避免数据不一致;
-
合理使用NULL:对可选字段(如备注)用NULL而非空字符串,节省存储空间;但查询时需注意用IS NULL判断;
-
添加注释 :通过
COMMENT给表和字段添加注释,增强可读性,示例:CREATE TABLE user (id INT COMMENT '用户ID');
八、常见问题与排查方法
8.1 语法错误
表现:执行SQL时提示"SQL syntax error",核心原因是关键字拼写错误、括号/引号不匹配、语法格式错误。
排查方法:
-
检查关键字是否小写(如select写成selec),数据库对关键字不区分大小写,但建议统一规范;
-
检查字符串是否用单引号包裹(MySQL不支持双引号),日期格式是否正确;
-
用数据库工具(如DataGrip)的语法提示功能,红色波浪线标记的即为错误位置;
-
查看错误日志:MySQL错误日志默认路径可通过
SHOW VARIABLES LIKE 'log_error';查询,日志中会详细记录语法错误位置和原因。
8.2 索引失效
表现:查询速度慢,执行计划显示"type: ALL"(全表扫描)。
排查方法:
-- 查看查询执行计划
EXPLAIN SELECT * FROM orders WHERE customer_id = 1 AND order_date > '2025-01-01';
通过执行计划的"key"列判断是否命中索引:key为NULL表示未命中,需检查是否违反索引使用原则(如字段用了函数、条件用了!=等)。
执行计划解读:执行计划的"type"列表示查询类型,性能从优到差为:system > const > eq_ref > ref > range > ALL(全表扫描),若出现ALL且数据量大,需优先优化。
8.3 事务冲突
表现:多个事务并发执行时出现"死锁"或"锁等待超时"。
解决方法:
-
统一事务中操作表的顺序(如两个事务都先操作customer表,再操作orders表),避免交叉锁;
-
缩短事务执行时间,避免在事务中执行查询以外的操作(如调用接口、等待用户输入);
-
设置合理的锁等待超时时间(如innodb_lock_wait_timeout = 50)。
九、总结
SQL是与关系型数据库交互的核心工具,需从"基础-进阶-优化"逐步掌握:
-
基础层:掌握DDL(定义结构)、DML(操作数据)、DQL(查询数据)的核心语法,理解数据库、表、主键、外键等核心概念;
-
进阶层:熟练使用JOIN关联查询、子查询、分组统计,掌握视图、存储过程、触发器等高级特性,实现复杂业务逻辑;
-
优化层:通过索引、查询语句优化、表结构设计提升性能,理解事务隔离性和锁机制,避免并发问题。