一篇文章带你入门SQL(上)

1. SQL 是什么?

SQL 是一种专门为关系型数据库设计的语言,用于处理结构化数据。关系型数据库以表格(Table)的形式存储数据,每个表格包含行(Row)和列(Column),SQL 提供了通过声明式语句与这些数据交互的方式。

  • 标准化:SQL 由 ANSI(美国国家标准协会)和 ISO(国际标准化组织)标准化,核心语法在不同数据库系统间保持一致,但各数据库系统可能有自己的扩展。
  • 声明式语言:用户只需指定“要做什么”(如查询哪些数据),而无需关心“如何做”(如底层的数据检索逻辑)。
  • 用途广泛:SQL 用于数据分析、后端开发、数据库管理、报表生成等领域。

2.SQL 语法规范

2.1 SQL通用语法

2.1.1 SQL 语句可以单行或多行书写,以分号结尾

  • 说明
    • SQL 语句可以根据需要写成单行或多行,数据库解析器会忽略换行符。
    • 以分号(;)结束语句是 ANSI SQL 标准的要求,用于明确一条语句的结束。
    • 在某些数据库客户端(如 MySQL 命令行)中,分号是必需的;但在某些 GUI 工具或特定数据库(如 SQL Server 的某些场景)中,分号可能是可选的。
  • 注意
    • 多条语句需要用分号分隔,否则可能导致语法错误。
    • 在存储过程或脚本中,分号尤为重要。
  • 示例
    • 单行:
SELECT * FROM employees WHERE department = 'HR';
    • 多行:
SELECT name, salary
FROM employees
WHERE department = 'HR'
ORDER BY salary DESC;

2.1.2 SQL 语句可以使用空格/缩进来增强语句的可读性

  • 说明
    • SQL 允许使用空格、制表符或换行来格式化代码,这些空白字符不影响语句的执行。
    • 合理使用缩进和空格可以使复杂查询(如多表连接、子查询)更易读,尤其在团队协作或维护时。
    • 常见的格式化习惯:
      • 每个主要子句(如 SELECTFROMWHERE)另起一行。
      • 子句内的条件或列对齐。
  • 示例
    • 未格式化(可读性差):
SELECT name,salary FROM employees WHERE department='HR' ORDER BY salary DESC;
    • 格式化后(可读性强):
SELECT name, salary
FROM employees
WHERE department = 'HR'
ORDER BY salary DESC;

2.1.3 MySQL 数据库的 SQL 语句不区分大小写,关键字建议使用大写

  • 说明
    • SQL 关键字(如 SELECTFROMWHERE)和数据库对象名称(如表名、列名)在 MySQL 中默认不区分大小写。例如,SELECTselect 等效。
    • 建议:将 SQL 关键字写成大写,表名和列名使用小写或下划线分隔(如 employee_name),以提高代码可读性和一致性。
    • 例外
      • 在某些数据库(如 PostgreSQL),对象名称可能区分大小写(需用双引号括起来)。
      • MySQL 在某些操作系统(如 Linux)上,表名可能对大小写敏感(取决于文件系统配置)。
    • 命名规范
      • 避免使用 SQL 保留关键字(如 TABLESELECT)作为表名或列名。
      • 使用描述性名称,如 employees 而不是 tbl1
  • 示例
    • 不区分大小写:
select * from EMPLOYEES; -- 等同于 SELECT * FROM employees;
    • 推荐风格:
SELECT name, salary
FROM employees
WHERE department = 'HR';

2.2. 注释

注释用于为 SQL 代码添加说明,便于理解和维护。SQL 支持单行注释和多行注释,格式在大多数数据库中通用。

(1) 单行注释
  • 格式
    • -- 注释内容S 注释内容`(MySQL 和大多数数据库支持)
    • # 注释内容(MySQL 特有,PostgreSQL 等部分数据库不支持)
  • 说明
    • 注释从 --# 开始,直到行末。
    • 用于短说明或临时禁用代码。
  • 示例
-- 查询 HR 部门员工
SELECT * FROM employees WHERE department = 'HR';
# 这是一个单行注释
(2) 多行注释
  • 格式
/* 注释内容
   可以跨多行 */
  • 说明
    • 注释从 /* 开始,到 */ 结束,可跨越多行。
    • 适合详细说明或注释大段代码。
  • 示例
/* 这是一个多行注释
   用于解释复杂的查询逻辑 */
SELECT name, salary
FROM employees
WHERE department = 'HR';

补充说明

  • 跨数据库兼容性
    • 上述规范(分号结尾、空格缩进、不区分大小写、注释)在 ANSI SQL 标准中定义,大多数数据库(如 MySQL、PostgreSQL、Oracle、SQL Server)都支持。
    • 例外
      • # 单行注释主要在 MySQL 中使用,PostgreSQL 和 Oracle 不支持。
      • 分号在某些数据库(如 SQL Server 的简单查询)中可以省略,但在脚本或存储过程中通常是必须的。
  • 最佳实践
    • 始终以分号结束语句,确保跨平台兼容性。
    • 遵循一致的格式化风格(如关键字大写、缩进对齐),便于团队协作。
    • 使用注释记录查询的目的或逻辑,特别是在复杂脚本中。
  • 工具支持
    • 使用代码格式化工具(如 SQL Formatter、DBeaver 的格式化功能)保持一致的缩进和风格。
    • 使用版本控制(如 Git)管理 SQL 脚本,确保注释清晰以便团队理解。

2.3 数据定义语言(DDL, Data Definition Language)

DDL 用于定义和管理数据库的结构,包括数据库、表、索引、视图等的创建、修改和删除。DDL 语句会直接影响数据库的模式(Schema),执行后通常会自动提交(即无法回滚,具体取决于数据库系统)。

2.3.1 数据库操作

1) 查询所有数据库

说明:查询数据库管理系统中当前用户有权限访问的所有数据库列表。

SQL命令

SHOW DATABASES;

操作步骤

  • 执行上述命令,数据库管理系统会返回所有数据库的名称列表。
  • 返回结果通常是一个单列的表格,每行显示一个数据库的名称。

示例

SHOW DATABASES;

输出示例

+---------------------+
| Database            |
+---------------------+
| information_schema  |
| mysql               |
| test_db             |
| my_app              |
+---------------------+

注意事项

  • 某些数据库(如information_schemamysql)是系统数据库,通常用于存储元数据或系统配置。
  • 用户需要有SHOW DATABASES权限才能执行此操作。

2) 查询当前数据库

说明:查询当前会话正在使用的数据库。

SQL命令


SELECT DATABASE();

操作步骤

  • 执行上述命令,数据库管理系统会返回当前会话所使用的数据库名称。
  • 如果当前没有选择任何数据库,命令将返回NULL或空值。

示例

SELECT DATABASE();

输出示例(假设当前使用的是test_db):

+------------+
| DATABASE() |
+------------+
| test_db    |
+------------+

注意事项

  • 如果尚未通过USE命令选择数据库,执行此命令会返回空结果。
  • 此命令无须特殊权限,仅返回当前会话的上下文信息。

3) 创建数据库

说明:创建新的数据库,可以使用默认字符集或指定特定的字符集和排序规则。

SQL命令

  • 使用默认字符集
CREATE DATABASE 数据库名;
  • 指定字符集和排序规则
CREATE DATABASE 数据库名
CHARACTER SET 字符集
[COLLATE 排序规则];

操作步骤

  1. 确定数据库名称(必须唯一,不能与现有数据库同名)。
  2. 选择是否指定字符集和排序规则:
    • 如果不指定,数据库将使用系统默认字符集(通常是utf8mb4或配置文件中定义的字符集)。
    • 如果指定字符集(如utf8mb4)和排序规则(如utf8mb4_unicode_ci),数据库将使用这些设置。
  1. 执行CREATE DATABASE命令。

示例

  • 创建一个名为my_app的数据库,使用默认字符集:
CREATE DATABASE my_app;
  • 创建一个名为my_app的数据库,指定字符集为utf8mb4和排序规则为utf8mb4_unicode_ci
CREATE DATABASE my_app
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

输出

Query OK, 1 row affected (0.01 sec)

常用字符集和排序规则

  • 字符集
    • utf8mb4:支持完整的Unicode字符,包括表情符号,推荐用于国际化应用。
    • utf8:较老的Unicode字符集,仅支持部分字符(不推荐)。
    • latin1:支持西欧语言字符。
  • 排序规则
    • utf8mb4_unicode_ci:基于Unicode标准排序,适用于多语言环境。
    • utf8mb4_general_ci:简化的排序规则,性能稍高但准确性稍低。
    • latin1_swedish_ci:适用于拉丁字符集的瑞典语排序规则。

注意事项

  • 数据库名称应遵循命名规则(如避免特殊字符,长度限制通常为64个字符)。
  • 用户需要CREATE权限才能创建数据库。
  • 字符集和排序规则的选择会影响数据的存储和比较行为(如大小写敏感性)。

4) 删除数据库

说明:删除指定的数据库及其所有内容(包括表、数据等),操作不可逆。

SQL命令

DROP DATABASE 数据库名;

操作步骤

  1. 确认要删除的数据库名称。
  2. 执行DROP DATABASE命令。
  3. 数据库及其所有内容将被永久删除。

示例

DROP DATABASE my_app;

输出

Query OK, 0 rows affected (0.02 sec)

注意事项

  • 删除操作不可恢复,执行前应备份重要数据。
  • 用户需要DROP权限才能删除数据库。
  • 如果数据库不存在,执行DROP DATABASE会报错,除非使用以下命令:
DROP DATABASE IF EXISTS 数据库名;

该命令会在数据库不存在时避免报错。


5) 切换数据库

说明:将当前会话的上下文切换到指定的数据库,以便后续操作(如查询表或插入数据)在此数据库中进行。

SQL命令

USE 数据库名;

操作步骤

  1. 确认目标数据库存在(可通过SHOW DATABASES查看)。
  2. 执行USE命令切换到指定数据库。
  3. 后续的SQL操作(如SELECTINSERT)将在该数据库中执行。

示例

USE my_app;

输出

Database changed

验证切换

  • 可通过SELECT DATABASE();确认当前数据库是否切换成功。

注意事项

  • 用户需要对目标数据库有访问权限。
  • 如果数据库不存在,执行USE命令会报错。
  • 切换数据库仅影响当前会话,其他会话或用户不受影响。

总结

以下是上述操作的SQL命令快速参考:

  1. 查询所有数据库:SHOW DATABASES;
  2. 查询当前数据库:SELECT DATABASE();
  3. 创建数据库:
    • 默认字符集:CREATE DATABASE 数据库名;
    • 指定字符集:CREATE DATABASE 数据库名 CHARACTER SET 字符集 COLLATE 排序规则;
  1. 删除数据库:DROP DATABASE 数据库名;
  2. 切换数据库:USE 数据库名;
补充说明
  • 权限管理:数据库操作需要相应的权限(如CREATEDROPSELECT等),具体权限由数据库管理员分配。
  • 字符集选择:建议使用utf8mb4作为默认字符集,以支持更广泛的字符和国际化需求。
  • 安全性:删除数据库或切换数据库时,务必确认操作目标,避免误操作。

2.3.2 修改或删除数据库对象

这一部分介绍 DDL 中用于修改或删除数据库对象的语句,主要包括 ALTERDROPTRUNCATE

1. ALTER TABLE - 修改表结构
  • 语法
ALTER TABLE table_name
{ ADD column_name datatype [constraints] |
  MODIFY column_name datatype [constraints] |
  DROP COLUMN column_name |
  ADD CONSTRAINT constraint_name constraint_definition |
  DROP CONSTRAINT constraint_name };
  • 说明
    • 用于添加、修改或删除列,或管理约束。
    • 具体语法可能因数据库系统略有差异(如 MySQL 用 MODIFY,Oracle 用 ALTER COLUMN)。
  • 示例
    • 添加列:
ALTER TABLE employees
ADD email VARCHAR(100);
    • 修改列类型:
ALTER TABLE employees
MODIFY name VARCHAR(100);
    • 删除列:
ALTER TABLE employees
DROP COLUMN email;
    • 添加外键约束:
ALTER TABLE employees
ADD CONSTRAINT fk_dept
FOREIGN KEY (department) REFERENCES departments(dept_id);
2. DROP - 删除数据库对象
  • 语法
DROP { DATABASE | TABLE | INDEX | VIEW } object_name;
  • 说明
    • 删除指定的数据库、表、索引或视图。
    • 操作不可逆,需谨慎使用。
    • 某些数据库支持 IF EXISTS 避免错误。
  • 示例
    • 删除表:
DROP TABLE employees;
    • 删除数据库:
DROP DATABASE company_db;
    • 删除索引:
DROP INDEX idx_salary ON employees;
    • 删除视图:
DROP VIEW hr_employees;
3. TRUNCATE - 清空表数据
  • 语法
TRUNCATE TABLE table_name;
  • 说明
    • 删除表中所有行,但保留表结构(包括索引、约束)。
    • DELETE 更快,因为不记录每行删除操作。
    • 不可回滚(在支持事务的数据库中可能例外)。
  • 示例
TRUNCATE TABLE employees;
    • 清空 employees 表的数据,但保留表结构。

通用语法规则与注意事项
  • 分号结束:DDL 语句以分号(;)结束,符合 ANSI SQL 标准。
  • 不可回滚:大多数数据库中,DDL 语句会自动提交(Auto-Commit),无法通过 ROLLBACK 撤销(PostgreSQL 除外,支持事务中的 DDL)。
  • 跨数据库兼容性
    • 核心 DDL 语句(CREATEALTERDROPTRUNCATE)在 MySQL、PostgreSQL、SQL Server、Oracle 等数据库中通用。
    • 细微差异:
      • MySQL 的 MODIFY 语法在 SQL Server 中可能是 ALTER COLUMN
      • 某些数据库(如 SQLite)对 ALTER TABLE 的支持有限(如不支持删除列)。
  • 命名规范
    • 表名、列名建议使用小写或下划线分隔(如 employee_name)。
    • 避免使用保留关键字(如 TABLEINDEX)。
  • 注释
    • 单行:-- 创建员工表# 创建员工表(MySQL)。
    • 多行:/* 表结构定义 */
  • 权限要求:执行 DDL 语句通常需要管理员权限(如 CREATE DATABASE)。

示例:综合 DDL 应用

以下是一个结合创建和修改的 DDL 示例,展示 2.3.1 和 2.3.2 的内容:

-- 2.3.1 创建数据库和表
CREATE DATABASE company_db;
CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    department VARCHAR(50),
    salary DECIMAL(10, 2)
);

-- 2.3.2 修改表结构
ALTER TABLE employees
ADD hire_date DATE;

-- 清空表数据
TRUNCATE TABLE employees;

-- 删除表
DROP TABLE employees;

总结
  • 2.3.1 创建数据库对象:使用 CREATE 语句创建数据库(DATABASE)、表(TABLE)、索引(INDEX)和视图(VIEW),定义数据库结构。
  • 2.3.2 修改或删除数据库对象:使用 ALTER 修改表结构,DROP 删除对象,TRUNCATE 清空表数据,管理数据库模式的变更。

2.3.3 表操作

2.3.2.1 表操作 - 查询创建
1) 查询当前数据库所有表

说明:列出当前数据库中所有表的名称。

SQL 命令

SHOW TABLES;

操作步骤

  • 确保已通过 USE 数据库名; 切换到目标数据库。
  • 执行 SHOW TABLES; 命令,数据库返回当前数据库中所有表的列表。

示例

USE my_app;
SHOW TABLES;

输出示例

+------------------+
| Tables_in_my_app |
+------------------+
| users            |
| orders           |
| products         |
+------------------+

深入分析

  • 结果显示的表名是当前数据库中的所有表,包含用户创建的表和某些系统表(视权限而定)。
  • 如果数据库中没有表,命令返回空结果。
  • 需要 SELECT 权限来查看表列表。

注意事项

  • 仅显示当前数据库的表,无法跨数据库查询(需切换数据库)。
  • 可通过 SHOW FULL TABLES; 查看更多信息(如表类型:BASE TABLEVIEW)。

2) 查看指定表结构

说明:查询指定表的字段名、数据类型、约束等结构信息。

SQL 命令

DESCRIBE 表名;

或:

SHOW COLUMNS FROM 表名;

操作步骤

  • 指定要查询的表名。
  • 执行 DESCRIBESHOW COLUMNS 命令,返回表的字段详细信息。

示例

DESCRIBE users;

输出示例

+----------+-------------+------+-----+---------+----------------+
| Field    | Type        | Null | Key | Default | Extra          |
+----------+-------------+------+-----+---------+----------------+
| id       | int         | NO   | PRI | NULL    | auto_increment |
| username | varchar(50) | NO   |     | NULL    |                |
| email    | varchar(100)| YES  |     | NULL    |                |
| created  | datetime    | YES  |     | NULL    |                |
+----------+-------------+------+-----+---------+----------------+

深入分析

  • 输出列解释:
    • Field:字段名。
    • Type:字段的数据类型。
    • Null:是否允许为空(YES 表示允许,NO 表示不允许)。
    • Key:索引类型(PRI 表示主键,UNI 表示唯一索引,MUL 表示普通索引)。
    • Default:默认值。
    • Extra:附加信息(如 auto_increment 表示自增)。
  • 该命令适用于快速了解表结构,调试或文档化。

注意事项

  • 表名必须存在,否则会报错。
  • 可通过 SHOW CREATE TABLE 表名; 查看更详细的建表语句(见下文)。

3) 查询指定表的建表语句

说明:查看创建指定表的完整 SQL 语句,包括字段定义、约束、索引等。

SQL 命令

SHOW CREATE TABLE 表名;

操作步骤

  • 指定要查询的表名。
  • 执行 SHOW CREATE TABLE 命令,返回创建表的完整 SQL 语句。

示例

SHOW CREATE TABLE users;

输出示例

+-------+------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                     |
+-------+------------------------------------------------------------------------------------------------------------------+
| users | CREATE TABLE `users` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `email` varchar(100) DEFAULT NULL,
  `created` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci |
+-------+------------------------------------------------------------------------------------------------------------------+

深入分析

  • 返回的语句包含:
    • 字段定义(包括数据类型、约束如 NOT NULL)。
    • 主键、唯一键等约束。
    • 表选项(如存储引擎 ENGINE=InnoDB、字符集 CHARSET=utf8mb4)。
  • 此命令对于备份表结构、迁移数据库或调试非常有用。

注意事项

  • 输出语句可直接用于重新创建表。
  • 某些数据库版本可能在语句中包含特定于系统的选项(如 MySQL 的 ENGINE)。

4) 创建表结构

说明:创建新表,定义字段、数据类型、约束等。

SQL 命令

CREATE TABLE 表名 (
  字段名1 数据类型 [约束],
  字段名2 数据类型 [约束],
  ...
  [表级约束]
) [表选项];

操作步骤

  1. 确定表名(唯一且符合命名规则)。
  2. 定义字段,包括:
    • 字段名。
    • 数据类型(见 2.3.2.2)。
    • 约束(如 NOT NULLPRIMARY KEYDEFAULT)。
  1. 可选:指定表级约束(如外键)或表选项(如字符集、存储引擎)。
  2. 执行 CREATE TABLE 命令。

示例
创建一个用户表 users,包含自增主键、用户名、邮箱和创建时间:

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50) NOT NULL,
  email VARCHAR(100) DEFAULT NULL,
  created DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

深入分析

  • 字段定义
    • id:整型,自增主键。
    • username:最大 50 个字符的字符串,不允许为空。
    • email:最大 100 个字符的字符串,允许为空。
    • created:日期时间类型,默认值为当前时间戳。
  • 表选项
    • ENGINE=InnoDB:使用 InnoDB 存储引擎,支持事务和外键。
    • DEFAULT CHARSET=utf8mb4:设置表字符集为 utf8mb4,支持 Unicode。

注意事项

  • 表名和字段名应避免关键字(如 SELECTTABLE)。
  • 选择合适的存储引擎(如 InnoDB 适合事务,MyISAM 适合只读场景)。
  • 字符集应与数据库一致,避免编码问题。

2.3.2.2 表操作 - 数据类型

以下是常见数据类型的详细说明,分为数值类型、字符串类型和日期时间类型。

1) 数值类型

数值类型用于存储整数或浮点数,常见类型包括:

类型

描述

范围(有符号)

存储空间

TINYINT

微型整数

-128 到 127

1 字节

SMALLINT

小整数

-32,768 到 32,767

2 字节

INT / INTEGER

标准整数

-231 到 231-1

4 字节

BIGINT

大整数

-263 到 263-1

8 字节

FLOAT

单精度浮点数

约 ±3.4E38

4 字节

DOUBLE

双精度浮点数

约 ±1.79E308

8 字节

DECIMAL(M,D)

定点数,精确小数(M 位数字,D 位小数)

取决于 M 和 D

变长

特点

  • 有符号 vs 无符号:可通过 UNSIGNED 关键字指定无符号(如 INT UNSIGNED 范围为 0 到 2^32-1)。
  • 精确性DECIMAL 适合金融数据,避免浮点数精度问题。
  • 空间优化:选择合适的类型(如 TINYINT 用于状态码)以节省存储空间。

示例

CREATE TABLE products (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  price DECIMAL(10,2),
  quantity SMALLINT
);

2) 字符串类型

字符串类型用于存储文本数据,常见类型包括:

类型

描述

最大长度

存储空间

CHAR(M)

定长字符串,固定长度 M 个字符

0-255 个字符

M 字节

VARCHAR(M)

变长字符串,最大长度 M 个字符

0-65,535 个字符(视字符集)

实际长度+1-2 字节

TEXT

变长文本,适合大文本

65,535 个字符

实际长度+2 字节

MEDIUMTEXT

中等文本

16,777,215 个字符

实际长度+3 字节

LONGTEXT

大文本

4,294,967,295 个字符

实际长度+4 字节

特点

  • CHAR vs VARCHAR
    • CHAR 适合固定长度数据(如状态码),存储效率高。
    • VARCHAR 适合长度可变的数据(如用户名),节省空间。
  • TEXT:适合存储大段文本(如文章内容),但不支持默认值。
  • 字符集影响utf8mb4 字符集下,每个字符可能占 1-4 字节。

示例

CREATE TABLE articles (
  id INT AUTO_INCREMENT PRIMARY KEY,
  title VARCHAR(100) NOT NULL,
  content TEXT
);

3) 日期时间类型

日期时间类型用于存储日期和时间,常见类型包括:

类型

描述

范围

存储空间

DATE

日期(年-月-日)

1000-01-01 到 9999-12-31

3 字节

TIME

时间(时:分:秒)

-838:59:59 到 838:59:59

3 字节

DATETIME

日期和时间

1000-01-01 00:00:00 到 9999-12-31 23:59:59

8 字节

TIMESTAMP

时间戳,存储 UTC 时间

1970-01-01 00:00:01 到 2038-01-19 03:14:07

4 字节

YEAR

年份

1901 到 2155

1 字节

特点

  • DATETIME vs TIMESTAMP
    • DATETIME:存储实际的日期时间,不受时区影响。
    • TIMESTAMP:存储 UTC 时间戳,自动转换时区,适合记录事件时间。
  • 默认值DATETIMETIMESTAMP 支持 CURRENT_TIMESTAMP 作为默认值。
  • 空间效率TIMESTAMPDATETIME 更节省空间,但范围较小。

示例

CREATE TABLE events (
  id INT AUTO_INCREMENT PRIMARY KEY,
  event_name VARCHAR(100),
  event_date DATETIME DEFAULT CURRENT_TIMESTAMP
);

2.3.3.3 表操作 - 案例

案例:设计一个简单的电商系统数据库表结构,包括用户表、商品表和订单表。

  1. 用户表(users)
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50) NOT NULL UNIQUE,
  email VARCHAR(100) NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  1. 商品表(products)
CREATE TABLE products (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  price DECIMAL(10,2) NOT NULL,
  stock SMALLINT UNSIGNED DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  1. 订单表(orders)
CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  product_id INT NOT NULL,
  quantity SMALLINT UNSIGNED NOT NULL,
  order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id),
  FOREIGN KEY (product_id) REFERENCES products(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

分析

  • 用户表:存储用户基本信息,主键自增,用户名唯一。
  • 商品表:存储商品信息,价格使用 DECIMAL 确保精度,库存为无符号整数。
  • 订单表:通过外键关联用户和商品,确保数据一致性。
  • 表选项:使用 InnoDB 引擎支持外键和事务,utf8mb4 字符集支持国际化。

应用场景

  • 查询用户信息:SELECT * FROM users WHERE id = 1;
  • 添加订单:INSERT INTO orders (user_id, product_id, quantity) VALUES (1, 1, 2);
  • 检查库存:SELECT stock FROM products WHERE id = 1;

2.3.3.4 表操作 - 修改

以下是修改表结构的常见操作,使用 ALTER TABLE 语句。

1) 添加字段

说明:向现有表中添加新字段。

SQL 命令

ALTER TABLE 表名 ADD 字段名 数据类型 [约束];

示例
users 表添加 phone 字段:

ALTER TABLE users ADD phone VARCHAR(20) DEFAULT NULL;

深入分析

  • 新字段默认添加到表结构末尾。
  • 可通过 AFTER 字段名 指定插入位置:
ALTER TABLE users ADD phone VARCHAR(20) AFTER username;

注意事项

  • 已有数据的新字段值将填充默认值(或 NULL)。
  • 确保新字段的类型和约束与现有数据兼容。

2) 修改数据类型

说明:更改现有字段的数据类型。

SQL 命令

ALTER TABLE 表名 MODIFY 字段名 新数据类型 [约束];

示例
users 表的 phone 字段类型从 VARCHAR(20) 改为 VARCHAR(30)

ALTER TABLE users MODIFY phone VARCHAR(30) DEFAULT NULL;

深入分析

  • 修改数据类型可能导致数据截断或转换错误(如从 INT 改为 TINYINT 时值超出范围)。
  • 需确保新类型兼容现有数据。

注意事项

  • 修改类型可能影响索引或外键,需谨慎操作。
  • 建议先备份数据。

3) 修改字段名和字段类型

说明:同时更改字段名和数据类型。

SQL 命令

ALTER TABLE 表名 CHANGE 旧字段名 新字段名 新数据类型 [约束];

示例
users 表的 phone 字段改为 contact_number 并将类型改为 VARCHAR(50)

ALTER TABLE users CHANGE phone contact_number VARCHAR(50) DEFAULT NULL;

深入分析

  • CHANGE 允许同时修改字段名和类型,功能比 MODIFY 更强大。
  • 需重新指定所有约束(如 NOT NULLDEFAULT)。

注意事项

  • 确保新字段名不与现有字段冲突。
  • 检查外键或索引是否受影响。

4) 删除字段

说明:从表中删除指定字段。

SQL 命令

ALTER TABLE 表名 DROP 字段名;

示例
删除 users 表的 contact_number 字段:

ALTER TABLE users DROP contact_number;

深入分析

  • 删除字段将永久移除该列及其数据,不可恢复。
  • 如果字段是主键或外键的一部分,需先删除相关约束。

注意事项

  • 谨慎操作,建议先备份表。
  • 检查是否有触发器或应用程序依赖该字段。

5) 修改表名

说明:更改表的名称。

SQL 命令

ALTER TABLE 旧表名 RENAME TO 新表名;

或:

RENAME TABLE 旧表名 TO 新表名;

示例
users 表重命名为 customers

ALTER TABLE users RENAME TO customers;

深入分析

  • 表名更改不影响表结构、数据或索引。
  • 需更新应用程序中的表名引用。

注意事项

  • 新表名必须唯一且符合命名规则。
  • 检查外键或视图是否引用旧表名。

2.3.3.5 表操作 - 删除
1) 删除表

说明:删除指定表及其所有数据和结构。

SQL 命令

DROP TABLE 表名;

示例
删除 customers 表:

DROP TABLE customers;

深入分析

  • 删除操作不可恢复,表结构和数据将永久丢失。
  • 如果表被外键引用,需先删除外键约束。

注意事项

  • 使用 DROP TABLE IF EXISTS 表名; 避免表不存在时的报错。
  • 建议备份重要数据。

2) 删除指定表并重新创建表

说明:先删除表(如果存在),然后重新创建具有相同或不同结构的表。

SQL 命令

DROP TABLE IF EXISTS 表名;
CREATE TABLE 表名 (
  字段定义
);

示例
删除并重新创建 users 表:

DROP TABLE IF EXISTS users;
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50) NOT NULL,
  email VARCHAR(100) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

深入分析

  • 这种操作常用于重置表结构或清空数据。
  • IF EXISTS 确保即使表不存在也不会报错。
  • 新表可以与原表结构不同,适合表结构调整。

注意事项

  • 所有数据将丢失,需谨慎操作。
  • 检查外键、触发器或视图是否受影响。

总结

2.3.2.1 查询创建

  • 查询所有表:SHOW TABLES;
  • 查看表结构:DESCRIBE 表名;
  • 查看建表语句:SHOW CREATE TABLE 表名;
  • 创建表:CREATE TABLE 表名 (字段定义) [表选项];

2.3.2.2 数据类型

  • 数值类型:TINYINTINTDECIMAL 等,适合精确或范围不同的场景。
  • 字符串类型:CHARVARCHARTEXT 等,适合定长或变长文本。
  • 日期时间类型:DATETIMETIMESTAMP 等,适合时间记录。

2.3.2.3 案例

  • 电商系统表设计展示了用户、商品、订单的典型结构,强调外键和数据类型选择。

2.3.2.4 修改

  • 添加字段:ALTER TABLE 表名 ADD 字段名 数据类型;
  • 修改数据类型:ALTER TABLE 表名 MODIFY 字段名 新数据类型;
  • 修改字段名和类型:ALTER TABLE 表名 CHANGE 旧字段名 新字段名 新数据类型;
  • 删除字段:ALTER TABLE 表名 DROP 字段名;
  • 修改表名:ALTER TABLE 旧表名 RENAME TO 新表名;

2.3.2.5 删除

  • 删除表:DROP TABLE 表名;
  • 删除并重新创建:DROP TABLE IF EXISTS 表名; CREATE TABLE 表名 (...);

深入补充
  1. 性能优化
    • 数据类型选择:选择最小合适的数据类型(如用 SMALLINT 代替 INT)可减少存储空间和提高查询效率。
    • 索引:创建表时可为常用查询字段添加索引(如 CREATE TABLE users (id INT PRIMARY KEY, username VARCHAR(50) UNIQUE);)。
    • 分区表:对于大表,可使用分区表优化查询性能。
  1. 事务支持
    • 使用 InnoDB 引擎时,表操作(如 CREATE TABLEALTER TABLE)可在事务中执行,允许回滚:
START TRANSACTION;
CREATE TABLE test (id INT);
ROLLBACK;
  1. 跨数据库差异
    • PostgreSQLSHOW TABLES 替换为 SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';
    • SQL ServerDESCRIBE 替换为 sp_help '表名';
    • 数据类型名称和约束语法可能略有不同,需参考具体数据库文档。
  1. 安全性和权限
    • 表操作需要相应权限(如 CREATE TABLE 需要 CREATE 权限,ALTER TABLE 需要 ALTER 权限)。
    • 建议限制普通用户的 DROP TABLE 权限,防止误操作。
  1. 实际应用场景
    • 动态表结构调整:在开发中,经常通过 ALTER TABLE 添加字段或调整类型以适应新需求。
    • 数据迁移:使用 SHOW CREATE TABLE 导出表结构,结合 INSERT INTO ... SELECT 迁移数据。
    • 临时表:使用 CREATE TEMPORARY TABLE 创建临时表,仅在当前会话有效,适合临时数据处理。

2.4 数据操纵语言(DML, Data Manipulation Language)

DML 是用于操作数据库中数据的 SQL 语句,主要包括 插入(INSERT)更新(UPDATE)删除(DELETE) 操作。与 DDL(数据定义语言,如创建表)不同,DML 专注于数据的增删改,而不涉及表结构或数据库对象的定义。

2.4.1 添加数据

添加数据使用 INSERT INTO 语句,将新记录插入到表中。以下详细说明三种插入方式:给指定字段添加数据、给全部字段添加数据以及批量添加数据。

1) 给指定字段添加数据

说明:仅为表的部分字段插入值,未指定的字段将使用默认值或 NULL

SQL 命令

INSERT INTO 表名 (字段1, 字段2, ...) VALUES (值1, 值2, ...);

操作步骤

  1. 确定目标表和需要插入的字段。
  2. 提供与字段顺序和类型匹配的值。
  3. 执行 INSERT INTO 语句。

示例
假设有一个 users 表:

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50) NOT NULL,
  email VARCHAR(100) DEFAULT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

插入一条记录,仅指定 usernameemail

INSERT INTO users (username, email) VALUES ('alice', '[email protected]');

执行后表内容

+----+----------+-------------------+---------------------+
| id | username | email             | created_at          |
+----+----------+-------------------+---------------------+
| 1  | alice    | [email protected] | 2025-04-25 10:00:00 |
+----+----------+-------------------+---------------------+

深入分析

  • 未指定的字段
    • id:由于是 AUTO_INCREMENT,自动生成值为 1
    • created_at:使用默认值 CURRENT_TIMESTAMP,记录插入时间。
  • 字段顺序VALUES 中的值必须与括号中字段的顺序一致。
  • 约束检查
    • usernameNOT NULL,必须提供值。
    • email 允许 NULL,可以省略(但示例中提供了值)。

注意事项

  • 确保必填字段(如 NOT NULL 且无默认值的字段)被赋值。
  • 值的数据类型必须与字段类型匹配(如 VARCHAR 字段需要字符串)。
  • 如果表有外键约束,插入的值必须符合外键引用的表中已有记录。

2) 给全部字段添加数据

说明:为表的所有字段插入值,字段顺序与表结构定义一致。

SQL 命令

INSERT INTO 表名 VALUES (值1, 值2, ...);

操作步骤

  1. 确认表的所有字段及其顺序(可通过 DESCRIBE 表名; 查看)。
  2. 提供与字段数量和类型匹配的值列表。
  3. 执行 INSERT INTO 语句。

示例
继续使用 users 表,插入一条完整记录:

INSERT INTO users VALUES (NULL, 'bob', '[email protected]', '2025-04-25 11:00:00');

执行后表内容

+----+----------+-------------------+---------------------+
| id | username | email             | created_at          |
+----+----------+-------------------+---------------------+
| 1  | alice    | [email protected] | 2025-04-25 10:00:00 |
| 2  | bob      | [email protected]   | 2025-04-25 11:00:00 |
+----+----------+-------------------+---------------------+

深入分析

  • 字段顺序:值必须严格按照表定义的字段顺序(id, username, email, created_at)。
  • 自增字段idAUTO_INCREMENT,插入 NULL 或省略(见下文)会自动生成递增的值。
  • 简化写法:对于自增主键,可以省略字段列表,但需确保值覆盖所有字段:
INSERT INTO users (username, email, created_at) VALUES ('bob', '[email protected]', '2025-04-25 11:00:00');

注意事项

  • 如果表结构发生变化(如添加新字段),直接使用 VALUES 的写法可能导致错误,建议显式指定字段名。
  • 所有 NOT NULL 字段必须有值,否则报错。
  • 确保值的数量和类型与表定义完全匹配。

3) 批量添加数据

说明:一次性插入多条记录,提高插入效率。

SQL 命令

INSERT INTO 表名 (字段1, 字段2, ...) VALUES 
  (值1_1, 值1_2, ...),
  (值2_1, 值2_2, ...),
  ...;

操作步骤

  1. 确定目标字段。
  2. 提供多组值,每组对应一条记录。
  3. 执行 INSERT INTO 语句。

示例
users 表批量插入三条记录:

INSERT INTO users (username, email) VALUES 
  ('charlie', '[email protected]'),
  ('dave', '[email protected]'),
  ('eve', '[email protected]');

执行后表内容

+----+----------+-------------------+---------------------+
| id | username | email             | created_at          |
+----+----------+-------------------+---------------------+
| 1  | alice    | [email protected] | 2025-04-25 10:00:00 |
| 2  | bob      | [email protected]   | 2025-04-25 11:00:00 |
| 3  | charlie  | [email protected] | 2025-04-25 12:00:00 |
| 4  | dave     | [email protected]  | 2025-04-25 12:00:00 |
| 5  | eve      | [email protected]   | 2025-04-25 12:00:00 |
+----+----------+-------------------+---------------------+

深入分析

  • 性能优势:批量插入比逐条插入效率更高,因为减少了数据库的解析和事务开销。
  • 事务支持:在 InnoDB 引擎中,批量插入可在事务中执行,确保数据一致性:
START TRANSACTION;
INSERT INTO users (username, email) VALUES ('frank', '[email protected]'), ('grace', '[email protected]');
COMMIT;
  • 错误处理:如果某条记录违反约束(如唯一键冲突),整个插入可能失败(视数据库配置)。

注意事项

  • 批量插入的记录数受限于数据库配置(如 MySQL 的 max_allowed_packet)。
  • 确保每组值的字段数量和类型一致。
  • 大规模批量插入时,建议分批执行(如每 1000 条一批)以避免性能问题。

高级用法

  • 从其他表插入:使用 INSERT INTO ... SELECT 从另一表复制数据:
INSERT INTO users (username, email) SELECT name, email FROM old_users;

2.4.2 修改数据

说明:使用 UPDATE 语句修改表中已有记录的字段值,通常结合 WHERE 条件指定要更新的记录。

SQL 命令

UPDATE 表名
SET 字段1 = 新值1, 字段2 = 新值2, ...
[WHERE 条件];

操作步骤

  1. 确定目标表和要修改的字段。
  2. 指定新值(可以是常量、表达式或其他字段的值)。
  3. 使用 WHERE 条件筛选需要更新的记录(若省略,将更新所有记录)。
  4. 执行 UPDATE 语句。

示例

  1. 更新单条记录:将 id = 1 的用户的邮箱改为 [email protected]
UPDATE users
SET email = '[email protected]'
WHERE id = 1;
  1. 更新多条记录:将所有 created_at 早于 2025-04-25 的用户的邮箱后缀改为 @updated.com
UPDATE users
SET email = CONCAT(SUBSTRING_INDEX(email, '@', 1), '@updated.com')
WHERE created_at < '2025-04-25';
  1. 使用表达式:将所有用户的 username 转为大写:
UPDATE users
SET username = UPPER(username);

执行后表内容(假设执行第一个示例)

+----+----------+---------------------+---------------------+
| id | username | email               | created_at          |
+----+----------+---------------------+---------------------+
| 1  | alice    | [email protected] | 2025-04-25 10:00:00 |
| 2  | bob      | [email protected]     | 2025-04-25 11:00:00 |
| 3  | charlie  | [email protected] | 2025-04-25 12:00:00 |
| 4  | dave     | [email protected]    | 2025-04-25 12:00:00 |
| 5  | eve      | [email protected]     | 2025-04-25 12:00:00 |
+----+----------+---------------------+---------------------+

深入分析

  • WHERE 条件
    • 精确条件(如 id = 1)确保只更新目标记录。
    • 复杂条件可使用逻辑运算符(如 ANDOR)或子查询:
UPDATE users
SET email = '[email protected]'
WHERE id IN (SELECT user_id FROM orders WHERE order_date < '2024-01-01');
  • SET 子句
    • 支持多个字段同时更新。
    • 可使用表达式(如 price = price * 1.1 提高价格 10%)。
  • 事务支持
    • InnoDB 引擎中,UPDATE 语句可在事务中执行,允许回滚:
START TRANSACTION;
UPDATE users SET email = '[email protected]' WHERE id = 1;
ROLLBACK;

注意事项

  • 无 WHERE 条件的风险:若省略 WHERE,所有记录都会被更新,可能导致数据损坏。
  • 约束检查:更新值必须符合字段约束(如 NOT NULL、外键、唯一键)。
  • 性能优化
    • WHERE 条件中的字段添加索引(如 id 上的主键索引)以提高查询效率。
    • 避免在大型表上执行全表更新,建议分批处理。
  • 备份数据:重要更新操作前建议备份表,防止误操作。

2.4.3 删除数据

说明:使用 DELETE 语句删除表中的记录,通常结合 WHERE 条件指定要删除的记录。

SQL 命令

DELETE FROM 表名
[WHERE 条件];

操作步骤

  1. 确定目标表。
  2. 使用 WHERE 条件筛选需要删除的记录(若省略,将删除所有记录)。
  3. 执行 DELETE 语句。

示例

  1. 删除单条记录:删除 id = 2 的用户:
DELETE FROM users WHERE id = 2;
  1. 删除多条记录:删除 created_at 早于 2025-04-25 的用户:
DELETE FROM users WHERE created_at < '2025-04-25';
  1. 删除所有记录:
DELETE FROM users;

执行后表内容(假设执行第一个示例)

+----+----------+---------------------+---------------------+
| id | username | email               | created_at          |
+----+----------+---------------------+---------------------+
| 1  | alice    | [email protected] | 2025-04-25 10:00:00 |
| 3  | charlie  | [email protected] | 2025-04-25 12:00:00 |
| 4  | dave     | [email protected]    | 2025-04-25 12:00:00 |
| 5  | eve      | [email protected]     | 2025-04-25 12:00:00 |
+----+----------+---------------------+---------------------+

深入分析

  • WHERE 条件
    • 精确条件(如 id = 2)确保只删除目标记录。
    • 支持复杂条件,如子查询:
DELETE FROM users
WHERE id NOT IN (SELECT user_id FROM orders);
  • DELETE vs TRUNCATE
    • DELETE:逐行删除记录,支持 WHERE 条件,触发触发器,保留表结构。
    • TRUNCATE:清空整个表,重置自增计数器,速度更快,但不可回滚:
TRUNCATE TABLE users;
  • 事务支持
    • InnoDB 引擎中,DELETE 语句可在事务中执行,允许回滚:
START TRANSACTION;
DELETE FROM users WHERE id = 3;
ROLLBACK;

注意事项

  • 无 WHERE 条件的风险:若省略 WHERE,将删除表中所有记录,需谨慎。
  • 外键约束:如果表被外键引用,删除记录可能失败,除非设置了 ON DELETE CASCADE
CREATE TABLE orders (
  id INT PRIMARY KEY,
  user_id INT,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

上述设置会在删除 users 表记录时自动删除对应的 orders 记录。

  • 性能优化
    • WHERE 条件中的字段添加索引以提高删除效率。
    • 大规模删除时,建议分批执行:
DELETE FROM users WHERE id <= 1000 LIMIT 1000;
  • 备份数据:重要删除操作前建议备份表。

总结

2.4.1 添加数据

  • 给指定字段添加数据:INSERT INTO 表名 (字段1, 字段2) VALUES (值1, 值2);
  • 给全部字段添加数据:INSERT INTO 表名 VALUES (值1, 值2, ...);
  • 批量添加数据:INSERT INTO 表名 (字段1, 字段2) VALUES (值1_1, 值1_2), (值2_1, 值2_2), ...;

2.4.2 修改数据

  • 修改记录:UPDATE 表名 SET 字段1 = 新值1, 字段2 = 新值2 WHERE 条件;

2.4.3 删除数据

  • 删除记录:DELETE FROM 表名 WHERE 条件;

深入补充

  1. 性能优化
    • 索引:为频繁用于 WHERE 条件的字段(如 idcreated_at)创建索引,加速 UPDATEDELETE
    • 批量操作:批量插入和分批删除/更新可显著提高性能,尤其在大表中。
    • 事务管理:在批量操作中使用事务,确保数据一致性并减少锁开销:
START TRANSACTION;
INSERT INTO users (username, email) VALUES ('user1', '[email protected]'), ('user2', '[email protected]');
UPDATE users SET email = '[email protected]' WHERE id > 10;
COMMIT;
  1. 错误处理
    • 唯一键冲突:插入数据时若违反唯一约束,可使用 INSERT IGNOREON DUPLICATE KEY UPDATE
INSERT INTO users (username, email) VALUES ('alice', '[email protected]')
ON DUPLICATE KEY UPDATE email = VALUES(email);
    • 外键约束:删除或更新记录时,需确保符合外键规则,或临时禁用外键检查:
SET FOREIGN_KEY_CHECKS = 0;
DELETE FROM users WHERE id = 1;
SET FOREIGN_KEY_CHECKS = 1;
  1. 跨数据库差异
    • PostgreSQL:语法类似,但 AUTO_INCREMENT 替换为 SERIAL,批量插入和 ON DUPLICATE KEY UPDATE 需使用 ON CONFLICT
    • SQL Server:不支持 ON DUPLICATE KEY UPDATE,需使用 MERGE 语句实现类似功能。
    • Oracle:批量插入使用 INSERT ALL 语法,UPDATEDELETE 支持更复杂的子查询。
  1. 安全性和权限
    • 插入、更新和删除操作需要对应的权限(INSERTUPDATEDELETE)。
    • 使用参数化查询(Prepared Statements)防止 SQL 注入:
PREPARE stmt FROM 'INSERT INTO users (username, email) VALUES (?, ?)';
SET @username = 'john', @email = '[email protected]';
EXECUTE stmt USING @username, @email;
    • 限制普通用户的 UPDATEDELETE 权限,避免误操作。
  1. 实际应用场景
    • 数据导入:批量插入用于从 CSV 文件或外部系统导入数据。
    • 状态更新UPDATE 用于批量更新订单状态、用户权限等。
    • 数据清理DELETE 用于定期清理过期日志或无效记录。
    • 日志记录:结合触发器,在插入/更新/删除时记录操作日志:
CREATE TABLE user_logs (
  log_id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT,
  action VARCHAR(50),
  log_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TRIGGER after_user_insert
AFTER INSERT ON users
FOR EACH ROW
INSERT INTO user_logs (user_id, action) VALUES (NEW.id, 'INSERT');

案例:电商系统 DML 操作

假设电商系统有以下表结构:

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50) NOT NULL,
  email VARCHAR(100)
);
CREATE TABLE products (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  price DECIMAL(10,2)
);
CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT,
  product_id INT,
  quantity INT,
  FOREIGN KEY (user_id) REFERENCES users(id),
  FOREIGN KEY (product_id) REFERENCES products(id)
);

操作示例

  1. 添加数据
    • 插入用户:
INSERT INTO users (username, email) VALUES 
  ('alice', '[email protected]'),
  ('bob', '[email protected]');
    • 插入商品:
INSERT INTO products (name, price) VALUES 
  ('Laptop', 999.99),
  ('Phone', 499.99);
    • 插入订单:
INSERT INTO orders (user_id, product_id, quantity) VALUES (1, 1, 2);
  1. 修改数据
    • 更新商品价格:
UPDATE products SET price = price * 1.1 WHERE id = 1;
    • 更新用户邮箱:
UPDATE users SET email = '[email protected]' WHERE username = 'alice';
  1. 删除数据
    • 删除无效订单:
DELETE FROM orders WHERE quantity = 0;
    • 删除特定用户及其订单(需注意外键):
DELETE FROM orders WHERE user_id = 2;
DELETE FROM users WHERE id = 2;

分析

  • 插入操作确保外键值(如 user_idproduct_id)在引用表中存在。
  • 更新操作使用精确的 WHERE 条件,避免影响无关记录。
  • 删除操作考虑外键依赖,需先删除子表记录(orders)。

2.5 数据查询语言(DQL, Data Query Language)

DQL 是用于查询数据库中数据的 SQL 语句,主要通过 SELECT 语句实现。DQL 不修改数据,仅返回查询结果,广泛用于数据分析、报表生成和应用程序数据检索。


2.5.1 基本语法

1) 查询多个字段

说明:从表中选择指定的字段返回数据。

SQL 命令

SELECT 字段1, 字段2, ... FROM 表名;

操作步骤

  1. 确定目标表和需要查询的字段。
  2. 使用 SELECT 语句列出字段名,字段间用逗号分隔。
  3. 执行查询,返回结果集。

示例
假设有一个 users 表:

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50) NOT NULL,
  email VARCHAR(100),
  created_at DATETIME
);

查询 usernameemail 字段:

SELECT username, email FROM users;

输出示例

+----------+-------------------+
| username | email             |
+----------+-------------------+
| alice    | [email protected] |
| bob      | [email protected]   |
| charlie  | [email protected] |
+----------+-------------------+

深入分析

  • 字段选择:可以选择表中的任意字段,顺序由 SELECT 语句决定。
  • 全字段查询:使用 SELECT * 查询所有字段,但不推荐用于生产环境(影响性能且不明确)。
  • 性能考虑:只选择需要的字段,减少数据传输和解析开销。

注意事项

  • 字段名需与表定义一致,区分大小写(视数据库配置)。
  • 如果字段不存在,查询会报错。

2) 字段设置别名

说明:为查询的字段或表达式指定别名,提高结果可读性或适应应用程序需求。

SQL 命令

SELECT 字段名 [AS] 别名, 字段名 [AS] 别名, ... FROM 表名;

操作步骤

  1. SELECT 语句中为字段或表达式添加别名。
  2. 别名可使用 AS 关键字(可选)。
  3. 执行查询,返回结果使用别名作为列名。

示例
查询 usernameemail,分别命名为 user_nameemail_address

SELECT username AS user_name, email AS email_address FROM users;

输出示例

+-----------+-------------------+
| user_name | email_address     |
+-----------+-------------------+
| alice     | [email protected] |
| bob       | [email protected]   |
| charlie   | [email protected] |
+-----------+-------------------+

深入分析

  • 别名用途
    • 提高结果可读性(如将 created_at 改为 registration_date)。
    • 适配前端或报表字段名称。
    • 用于表达式(如 SELECT price * 1.1 AS new_price FROM products;)。
  • 别名规则
    • 别名可以是任意字符串,但避免使用关键字或特殊字符。
    • 如果别名包含空格,需用引号(如 SELECT username AS "User Name")。

注意事项

  • 别名仅影响查询结果,不修改表结构。
  • 别名在同一查询中可用于 ORDER BY,但不能用于 WHERE(因执行顺序限制,详见 2.5.9)。

3) 去除重复记录

说明:使用 DISTINCT 关键字去除查询结果中的重复行。

SQL 命令

SELECT DISTINCT 字段1, 字段2, ... FROM 表名;

操作步骤

  1. SELECT 后添加 DISTINCT 关键字。
  2. 指定需要去重的字段。
  3. 执行查询,返回无重复的记录。

示例
假设 users 表中有重复的 email 值:

SELECT DISTINCT email FROM users;

输出示例(假设原始数据有重复):

+-------------------+
| email             |
+-------------------+
| [email protected] |
| [email protected]   |
| [email protected] |
+-------------------+

深入分析

  • 去重机制DISTINCT 基于指定字段的组合去重,检查整行是否相同。
  • 多字段去重SELECT DISTINCT field1, field2 去重基于 (field1, field2) 的唯一组合。
  • 性能影响
    • DISTINCT 涉及排序或哈希操作,可能降低查询性能。
    • 建议仅在必要时使用,或通过索引优化。

注意事项

  • DISTINCT 作用于整个结果集,不能单独应用于某个字段。
  • 避免对大数据量表使用 DISTINCT,可考虑 GROUP BY 替代。

2.5.2 基础查询

1) 语法

说明:基础查询是 DQL 的核心,使用 SELECT ... FROM 结构检索数据。

SQL 命令

SELECT [DISTINCT] 字段列表 | * 
FROM 表名
[WHERE 条件]
[ORDER BY 字段 [ASC | DESC]];

操作步骤

  1. 指定要查询的字段(或使用 *)。
  2. 指定数据来源表。
  3. 可选:添加 WHERE 条件筛选记录。
  4. 可选:使用 ORDER BY 排序结果。

示例
查询 users 表中所有字段:

SELECT * FROM users;

2) 条件

说明:通过 WHERE 子句添加条件,筛选符合条件的记录。

常见条件运算符

  • 比较运算符=, !=<>, >, <, >=, <=
  • 逻辑运算符AND, OR, NOT
  • 范围运算符BETWEEN ... AND ...
  • 集合运算符IN, NOT IN
  • 模糊查询LIKE
  • 空值判断IS NULL, IS NOT NULL

示例
查询 usernamealice 的用户:

SELECT * FROM users WHERE username = 'alice';

2.5.3 条件查询

1) 语法

SQL 命令

SELECT 字段列表 
FROM 表名 
WHERE 条件;

操作步骤

  1. 确定查询字段和表。
  2. 使用 WHERE 子句定义筛选条件。
  3. 执行查询,返回符合条件的记录。

2) 条件

详细说明

  • 比较运算符:如 price > 100created_at = '2025-04-25'
  • 逻辑运算符
    • AND:多条件都满足(如 price > 100 AND stock > 0)。
    • OR:任一条件满足(如 username = 'alice' OR username = 'bob')。
    • NOT:取反(如 NOT username = 'alice')。
  • BETWEEN ... AND ...:范围查询(如 price BETWEEN 100 AND 200)。
  • IN:值在集合中(如 id IN (1, 2, 3))。
  • LIKE:模糊匹配(% 表示任意字符,_ 表示单个字符)。
    • username LIKE 'a%' 匹配以 a 开头的用户名。
  • IS NULL:检查空值(如 email IS NULL)。

示例
查询 users 表中 email 不为空且 created_at 在 2025-04-25 之后的用户:

SELECT username, email 
FROM users 
WHERE email IS NOT NULL AND created_at > '2025-04-25';

案例

场景:电商系统的 products 表:

CREATE TABLE products (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  price DECIMAL(10,2),
  stock INT
);

查询需求

  1. 查询价格在 100 到 500 之间的商品:
SELECT name, price FROM products WHERE price BETWEEN 100 AND 500;
  1. 查询名称以 "Phone" 开头且库存大于 0 的商品:
SELECT name, price, stock FROM products WHERE name LIKE 'Phone%' AND stock > 0;
  1. 查询 ID 为 1、3、5 的商品:
SELECT * FROM products WHERE id IN (1, 3, 5);

输出示例(假设数据):

+---------+-------+
| name    | price |
+---------+-------+
| Phone X | 499.99|
| Phone Y | 399.99|
+---------+-------+

分析

  • 使用 BETWEENIN 提高查询可读性。
  • LIKE 适合模糊搜索,但对大数据量性能较低,建议结合索引。

2.5.4 聚合函数

1) 介绍

说明:聚合函数对一组数据进行计算,返回单个值,常用于统计分析。

特点

  • 作用于列数据,返回汇总结果(如总数、平均值)。
  • 常与 GROUP BY 结合使用。
  • 忽略 NULL 值(除 COUNT(*) 外)。

2) 常见的聚合函数

函数

描述

示例

COUNT()

统计记录数

COUNT(*)COUNT(column)

SUM()

计算数值列总和

SUM(price)

AVG()

计算数值列平均值

AVG(price)

MAX()

查找最大值

MAX(price)

MIN()

查找最小值

MIN(price)


3) 语法

SQL 命令

SELECT 聚合函数(字段) [AS 别名], ... 
FROM 表名 
[WHERE 条件];

操作步骤

  1. 选择聚合函数和目标字段。
  2. 可选:使用 WHERE 筛选数据。
  3. 执行查询,返回聚合结果。

案例

场景orders 表:

CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT,
  product_id INT,
  quantity INT,
  order_date DATE
);

查询需求

  1. 统计订单总数:
SELECT COUNT(*) AS total_orders FROM orders;
  1. 计算总订购数量:
SELECT SUM(quantity) AS total_quantity FROM orders;
  1. 查找最早订单日期:
SELECT MIN(order_date) AS first_order FROM orders;

输出示例

+--------------+
| total_orders |
+--------------+
| 100          |
+--------------+

分析

  • COUNT(*) 统计所有行,COUNT(column) 仅统计非 NULL 行。
  • 聚合函数常用于报表或仪表盘数据展示。

注意事项

  • 确保字段类型与聚合函数兼容(如 SUM 仅用于数值类型)。
  • 大数据量下,聚合查询性能依赖索引优化。

2.5.5 分组查询

1) 语法

说明:使用 GROUP BY 将数据按指定字段分组,结合聚合函数统计每组数据。

SQL 命令

SELECT 字段, 聚合函数(字段) 
FROM 表名 
[WHERE 条件] 
GROUP BY 字段 
[HAVING 分组后条件];

操作步骤

  1. 指定分组字段和聚合函数。
  2. 使用 WHERE 筛选原始数据(可选)。
  3. 使用 GROUP BY 分组。
  4. 使用 HAVING 筛选分组结果(可选)。
  5. 执行查询。

2) WHERE 与 HAVING 区别
  • WHERE
    • 作用于原始数据,筛选单行记录。
    • GROUP BY 之前执行。
    • 不能包含聚合函数。
  • HAVING
    • 作用于分组后的结果,筛选组数据。
    • GROUP BY 之后执行。
    • 可包含聚合函数。

示例
统计每个用户的订单数量,筛选订单数大于 2 的用户:

SELECT user_id, COUNT(*) AS order_count
FROM orders
WHERE order_date >= '2025-01-01'
GROUP BY user_id
HAVING order_count > 2;

输出示例

+---------+-------------+
| user_id | order_count |
+---------+-------------+
| 1       | 5           |
| 3       | 3           |
+---------+-------------+

案例

场景:统计每个商品的销售总量,筛选销售量大于 10 的商品:

SELECT product_id, SUM(quantity) AS total_sold
FROM orders
GROUP BY product_id
HAVING total_sold > 10;

分析

  • GROUP BY product_id 按商品分组。
  • SUM(quantity) 计算每组的总订购量。
  • HAVING 筛选销售量大于 10 的组。

注意事项

  • SELECT 中的非聚合字段必须出现在 GROUP BY 中(MySQL 宽松模式除外)。
  • 为分组字段添加索引可提高性能。

2.5.6 排序查询

1) 语法

说明:使用 ORDER BY 对查询结果按指定字段排序。

SQL 命令

SELECT 字段列表 
FROM 表名 
[WHERE 条件] 
ORDER BY 字段 [ASC | DESC], 字段 [ASC | DESC], ...;

操作步骤

  1. 指定排序字段和顺序(ASC 为升序,DESC 为降序)。
  2. 可按多个字段排序,优先级从左到右。
  3. 执行查询。

2) 排序方式
  • ASC:升序(默认),从小到大(数字)或 A 到 Z(字符串)。
  • DESC:降序,从大到小或 Z 到 A。

示例
price 降序查询商品:

SELECT name, price FROM products ORDER BY price DESC;

stock 升序、price 降序排序:

SELECT name, price, stock 
FROM products 
ORDER BY stock ASC, price DESC;

案例

场景:查询商品,按价格降序,库存升序:

SELECT name, price, stock 
FROM products 
WHERE stock > 0 
ORDER BY price DESC, stock ASC;

输出示例

+---------+--------+-------+
| name    | price  | stock |
+---------+--------+-------+
| Laptop  | 999.99 | 10    |
| Phone X | 499.99 | 5     |
| Phone Y | 399.99 | 8     |
+---------+--------+-------+

分析

  • 多字段排序适用于优先级不同的场景(如优先按价格排序,价格相同则按库存排序)。
  • 索引优化可显著提高排序性能。

注意事项

  • 排序字段可以是别名或表达式(如 ORDER BY price * 1.1 DESC)。
  • 避免对大数据量无索引字段排序,可能导致性能瓶颈。

2.5.7 分页查询

1) 语法

说明:使用 LIMITOFFSET(或等效语法)实现分页查询,限制返回的记录数和起始位置。

SQL 命令(MySQL):

SELECT 字段列表 
FROM 表名 
[WHERE 条件] 
[ORDER BY 字段] 
LIMIT 每页记录数 [OFFSET 起始位置];

操作步骤

  1. 确定每页记录数(LIMIT)。
  2. 计算起始位置(OFFSET = (页码 - 1) * 每页记录数)。
  3. 结合 ORDER BY 确保结果顺序一致。
  4. 执行查询。

案例

场景:分页显示商品,每页 2 条记录,按价格降序:

  • 第一页:
SELECT name, price FROM products 
ORDER BY price DESC 
LIMIT 2 OFFSET 0;
  • 第二页:
SELECT name, price FROM products 
ORDER BY price DESC 
LIMIT 2 OFFSET 2;

输出示例(第一页)

+---------+--------+
| name    | price  |
+---------+--------+
| Laptop  | 999.99 |
| Phone X | 499.99 |
+---------+--------+

分析

  • 公式OFFSET = (page_number - 1) * page_size
  • 性能:大数据量分页时,OFFSET 较大可能导致扫描过多记录,建议使用索引或基于主键分页:
SELECT name, price FROM products 
WHERE id > last_id 
ORDER BY id 
LIMIT 2;

注意事项

  • 确保 ORDER BY 字段有明确顺序,避免分页结果不一致。
  • 分页查询适合前端列表展示,但需优化后端性能。

2.5.8 案例

场景:电商系统,包含 usersproductsorders 表:

CREATE TABLE users (
  id INT PRIMARY KEY,
  username VARCHAR(50),
  email VARCHAR(100)
);
CREATE TABLE products (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  price DECIMAL(10,2),
  stock INT
);
CREATE TABLE orders (
  id INT PRIMARY KEY,
  user_id INT,
  product_id INT,
  quantity INT,
  order_date DATE,
  FOREIGN KEY (user_id) REFERENCES users(id),
  FOREIGN KEY (product_id) REFERENCES products(id)
);

查询需求

  1. 查询价格大于 500 且库存大于 10 的商品,按价格降序,分页显示(每页 3 条,第 2 页):
SELECT name, price, stock 
FROM products 
WHERE price > 500 AND stock > 10 
ORDER BY price DESC 
LIMIT 3 OFFSET 3;
  1. 统计每个用户的订单数量,筛选订单数大于 3 的用户:
SELECT user_id, COUNT(*) AS order_count 
FROM orders 
GROUP BY user_id 
HAVING order_count > 3 
ORDER BY order_count DESC;
  1. 查询 2025 年 1 月的订单,显示用户名和商品名:
SELECT u.username, p.name, o.quantity 
FROM orders o 
JOIN users u ON o.user_id = u.id 
JOIN products p ON o.product_id = p.id 
WHERE o.order_date LIKE '2025-01%';

分析

  • 综合运用了条件查询、分组查询、排序、分页和多表连接。
  • 实际应用中,需为 pricestockuser_id 等字段添加索引以优化性能。

2.5.9 执行顺序

说明:SQL 查询的逻辑执行顺序决定了子句的处理顺序,影响查询结果和性能。

执行顺序(MySQL 标准):

  1. FROM:确定数据来源表,执行表连接(如 JOIN)。
  2. WHERE:筛选单行记录,应用条件。
  3. GROUP BY:按指定字段分组。
  4. HAVING:筛选分组结果。
  5. SELECT:选择字段,计算表达式和别名。
  6. DISTINCT:去除重复行。
  7. ORDER BY:排序结果。
  8. LIMIT / OFFSET:限制返回记录数和位置。

示例解析

SELECT user_id, COUNT(*) AS order_count 
FROM orders 
WHERE order_date >= '2025-01-01' 
GROUP BY user_id 
HAVING order_count > 2 
ORDER BY order_count DESC 
LIMIT 5;

执行步骤

  1. orders 表获取数据。
  2. 应用 WHERE 条件,筛选 order_date >= '2025-01-01' 的记录。
  3. user_id 分组。
  4. 应用 HAVING,保留 COUNT(*) > 2 的组。
  5. 计算 SELECT 中的 user_idCOUNT(*),命名为 order_count
  6. order_count 降序排序。
  7. 返回前 5 条记录。

深入分析

  • 别名限制WHEREHAVING 不能直接使用 SELECT 中的别名(如 order_count),因为 SELECT 在它们之后执行。
  • 性能影响WHERE 尽早筛选数据可减少后续处理的数据量。
  • 索引优化:为 WHEREGROUP BYORDER BY 的字段创建索引。

总结

2.5.1 基本语法

  • 查询多个字段:SELECT 字段1, 字段2 FROM 表名;
  • 字段设置别名:SELECT 字段 AS 别名 FROM 表名;
  • 去除重复记录:SELECT DISTINCT 字段 FROM 表名;

2.5.2 基础查询

  • 语法:SELECT ... FROM ... [WHERE ...] [ORDER BY ...];
  • 条件:比较、逻辑、范围、集合、模糊查询等。

2.5.3 条件查询

  • 语法:SELECT ... FROM ... WHERE 条件;
  • 案例:筛选价格、库存、日期等。

2.5.4 聚合函数

  • 常见函数:COUNT, SUM, AVG, MAX, MIN
  • 语法:SELECT 聚合函数(字段) FROM ...;
  • 案例:统计订单数、总金额等。

2.5.5 分组查询

  • 语法:SELECT ... GROUP BY ... [HAVING ...];
  • WHERE vs HAVINGWHERE 筛选单行,HAVING 筛选组。
  • 案例:统计用户订单数。

2.5.6 排序查询

  • 语法:SELECT ... ORDER BY 字段 [ASC | DESC];
  • 案例:按价格、库存排序。

2.5.7 分页查询

  • 语法:SELECT ... LIMIT n OFFSET m;
  • 案例:分页显示商品。

2.5.8 案例

  • 综合查询:条件、分组、排序、分页、多表连接。

2.5.9 执行顺序

  • FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT。

深入补充

  1. 性能优化
    • 索引:为 WHEREGROUP BYORDER BYJOIN 的字段创建索引。
    • **避免 SELECT ***:明确指定字段,减少数据传输。
    • 分页优化:大数据量下使用主键或覆盖索引分页。
    • 查询缓存:MySQL 8.0 移除查询缓存,建议使用应用层缓存(如 Redis)。
  1. 跨数据库差异
    • PostgreSQL:分页使用 LIMIT n OFFSET m,语法相同,但更强调标准 SQL。
    • SQL Server:分页使用 TOP nOFFSET-FETCH
SELECT name, price 
FROM products 
ORDER BY price DESC 
OFFSET 3 ROWS FETCH NEXT 2 ROWS ONLY;
    • Oracle(旧版本):使用 ROWNUMFETCH FIRST
SELECT name, price 
FROM products 
ORDER BY price DESC 
FETCH FIRST 2 ROWS ONLY;
  1. 安全性和权限
    • 查询需要 SELECT 权限,限制用户访问敏感字段。
    • 使用参数化查询防止 SQL 注入:
PREPARE stmt FROM 'SELECT * FROM users WHERE username = ?';
SET @username = 'alice';
EXECUTE stmt USING @username;
  1. 实际应用场景
    • 报表生成:聚合查询统计销售数据。
    • 列表展示:分页查询显示商品或订单。
    • 数据分析:分组查询分析用户行为。
    • 搜索功能:条件查询结合 LIKE 实现模糊搜索。

2.6 数据控制语言(DCL,Data Control Language)

DCL 是用于定义数据库访问权限和安全策略的 SQL 语句,主要包括 GRANT(授予权限)、REVOKE(撤销权限)和用户管理相关的操作(如创建、修改、删除用户)。DCL 确保数据库的安全性,防止未经授权的访问或操作,广泛应用于多用户环境下的权限管理和安全控制。


2.6.1 管理用户

用户管理涉及创建、查询、修改和删除数据库用户账户,以下详细说明查询用户、修改用户密码和删除用户的操作(创建用户在案例中补充)。

1) 查询用户

说明:查询数据库系统中所有用户账户或特定用户信息。

SQL 命令(MySQL):

SELECT user, host FROM mysql.user;

操作步骤

  1. 访问系统数据库 mysql,其中 mysql.user 表存储用户信息。
  2. 使用 SELECT 语句查询 user(用户名)和 host(允许连接的主机)。
  3. 执行查询,返回用户列表。

示例
查询所有用户:

SELECT user, host FROM mysql.user;

输出示例

+------------------+-----------+
| user             | host      |
+------------------+-----------+
| root             | localhost |
| mysql.sys        | localhost |
| app_user         | %         |
| readonly_user    | 127.0.0.1 |
+------------------+-----------+

深入分析

  • 字段说明
    • user:用户名。
    • host:允许连接的主机(% 表示任意主机,localhost 表示本地连接)。
  • 系统用户:如 mysql.sys 是 MySQL 内部用户,用于系统功能,不应修改。
  • 权限要求:查询 mysql.user 需要 SELECT 权限,通常只有管理员(如 root 用户)有权执行。
  • 替代命令:MySQL 提供便捷命令查看用户:
SHOW GRANTS FOR CURRENT_USER;

或:

SELECT CURRENT_USER();

注意事项

  • 直接查询 mysql.user 可能返回敏感信息,建议限制普通用户访问。
  • 用户名和主机组合唯一,同一用户名可对应不同主机(如 app_user@%app_user@localhost 是不同账户)。

3) 修改用户密码

说明:更改指定用户的密码,用于更新登录凭证或增强安全性。

SQL 命令(MySQL 8.0+):

ALTER USER '用户名'@'主机' IDENTIFIED BY '新密码';

操作步骤

  1. 确定目标用户(用户名和主机)。
  2. 指定新密码,遵循密码策略(如长度、复杂性)。
  3. 执行 ALTER USER 语句。

示例
为用户 app_user@% 设置新密码 NewPass123

ALTER USER 'app_user'@'%' IDENTIFIED BY 'NewPass123';

深入分析

  • 密码策略
    • MySQL 的密码策略由 validate_password 插件控制,可能要求密码包含大小写字母、数字和特殊字符。
    • 可通过 SHOW VARIABLES LIKE 'validate_password%'; 查看策略。
  • 旧语法(MySQL 5.7 及更早版本):
SET PASSWORD FOR 'app_user'@'%' = PASSWORD('NewPass123');

或:

UPDATE mysql.user SET authentication_string = PASSWORD('NewPass123') WHERE user = 'app_user' AND host = '%';
FLUSH PRIVILEGES;
  • 安全性
    • 密码以加密形式存储在 mysql.user 表的 authentication_string 列。
    • 避免使用弱密码,定期更新密码。
  • 权限要求:需要 ALTER USER 权限或对 mysql.user 表的 UPDATE 权限。

注意事项

  • 修改密码后,需通知相关用户或更新应用程序配置(如连接字符串)。
  • 执行后立即生效,无需重启数据库。
  • 如果用户被锁定(ACCOUNT LOCK),需先解锁:
ALTER USER 'app_user'@'%' ACCOUNT UNLOCK;

4) 删除用户

说明:删除指定的用户账户,移除其所有权限和访问能力。

SQL 命令

DROP USER '用户名'@'主机';

操作步骤

  1. 确定要删除的用户(用户名和主机)。
  2. 执行 DROP USER 语句。
  3. 可选:使用 IF EXISTS 避免用户不存在时的错误。

示例
删除用户 app_user@%

DROP USER 'app_user'@'%';

深入分析

  • 效果
    • 删除用户后,其所有权限和会话立即失效。
    • 不影响用户创建的数据库或表(需单独删除)。
  • 批量删除
DROP USER 'user1'@'localhost', 'user2'@'%';
  • 权限要求:需要 DROP USER 权限,通常由管理员执行。
  • 替代方法(不推荐):
DELETE FROM mysql.user WHERE user = 'app_user' AND host = '%';
FLUSH PRIVILEGES;

注意事项

  • 删除用户前,确认其不再需要访问数据库,避免误操作。
  • 如果用户有活跃连接,需先断开:
KILL (SELECT processlist_id FROM information_schema.processlist WHERE user = 'app_user');
  • 删除用户不可恢复,建议备份权限信息(通过 SHOW GRANTS)。

案例:用户管理

场景:管理一个电商系统数据库的用户。

  1. 创建用户
    为应用程序创建一个用户 ecommerce_app@%
CREATE USER 'ecommerce_app'@'%' IDENTIFIED BY 'AppPass2025';
  1. 查询用户
    确认用户创建成功:
SELECT user, host, authentication_string FROM mysql.user WHERE user = 'ecommerce_app';
  1. 修改密码
    更新用户密码为 SecurePass2025
ALTER USER 'ecommerce_app'@'%' IDENTIFIED BY 'SecurePass2025';
  1. 删除用户
    删除不再需要的测试用户 test_user@%
DROP USER IF EXISTS 'test_user'@'%';

输出示例(查询用户):

+--------------+------+-----------------------+
| user         | host | authentication_string |
+--------------+------+-----------------------+
| ecommerce_app| %    | *encrypted_password*  |
+--------------+------+-----------------------+

分析

  • 创建用户时,指定强密码并限制主机范围(如 %localhost)。
  • 修改密码和删除用户需记录操作日志,确保可追溯。
  • 定期审查用户列表,移除不必要的账户。

2.6.2 权限控制

权限控制通过授予和撤销权限管理用户对数据库对象的访问和操作能力。

1) 查询权限

说明:查看用户拥有的权限或数据库对象的权限配置。

SQL 命令

  • 查看指定用户的权限:
SHOW GRANTS FOR '用户名'@'主机';
  • 查看当前用户的权限:
SHOW GRANTS;

操作步骤

  1. 指定目标用户或使用当前用户。
  2. 执行 SHOW GRANTS 语句,返回权限列表。

示例
查看用户 ecommerce_app@% 的权限:

SHOW GRANTS FOR 'ecommerce_app'@'%';

输出示例

+-------------------------------------------------------------+
| Grants for ecommerce_app@%                                   |
+-------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'ecommerce_app'@'%'                   |
| GRANT SELECT, INSERT, UPDATE ON `ecommerce`.* TO 'ecommerce_app'@'%' |
+-------------------------------------------------------------+

深入分析

  • 输出解释
    • GRANT USAGE ON *.*:默认权限,表示用户可以登录但无具体操作权限。
    • GRANT SELECT, INSERT, UPDATE ON ecommerce.*:用户对 ecommerce 数据库的所有表有 SELECTINSERTUPDATE 权限。
  • 权限级别
    • 全局*.*(所有数据库和表)。
    • 数据库数据库名.*(某数据库的所有表)。
    • 数据库名.表名
    • :特定字段(如 GRANT SELECT (column_name) ON 表名)。
  • 权限表:权限信息存储在 mysql.usermysql.dbmysql.tables_priv 等表中。
  • 权限要求:查询权限需要 SELECT 权限或管理员权限。

注意事项

  • SHOW GRANTS 显示的是授予的权限,可能不包括通过角色继承的权限(MySQL 8.0+ 支持角色)。
  • 定期审计权限,确保符合最小权限原则。

2) 授予权限

说明:使用 GRANT 语句为用户分配特定权限。

SQL 命令

GRANT 权限列表 ON 数据库.对象 TO '用户名'@'主机' [WITH GRANT OPTION];

操作步骤

  1. 确定权限类型(如 SELECTINSERTALL PRIVILEGES)。
  2. 指定权限作用的对象(如 *.*数据库名.*数据库名.表名)。
  3. 指定目标用户。
  4. 可选:添加 WITH GRANT OPTION 允许用户将权限授予他人。
  5. 执行 GRANT 语句。

常见权限

  • SELECT:查询数据。
  • INSERT:插入数据。
  • UPDATE:更新数据。
  • DELETE:删除数据。
  • CREATE:创建数据库或表。
  • DROP:删除数据库或表。
  • ALL PRIVILEGES:所有权限(不包括 GRANT OPTION)。

示例

  1. 授予用户 ecommerce_app@%ecommerce 数据库所有表的 SELECTINSERT 权限:
GRANT SELECT, INSERT ON ecommerce.* TO 'ecommerce_app'@'%';
  1. 授予用户 admin_user@localhost 全局所有权限,并允许其授予权限:
GRANT ALL PRIVILEGES ON *.* TO 'admin_user'@'localhost' WITH GRANT OPTION;

深入分析

  • 权限范围
    • 数据库级别:ecommerce.* 适用于所有表。
    • 表级别:ecommerce.users 仅适用于指定表。
  • WITH GRANT OPTION
    • 允许用户将自己的权限授予其他用户。
    • 需谨慎使用,可能导致权限扩散。
  • 生效GRANT 语句执行后立即生效,无需 FLUSH PRIVILEGES(除非直接修改权限表)。
  • 权限叠加:多次授予权限会合并,不会覆盖。

注意事项

  • 遵循最小权限原则,仅授予用户所需权限。
  • 避免对 *.* 授予过多权限,降低安全风险。
  • 权限授予后,需测试用户是否能正常操作。

3) 撤销权限

说明:使用 REVOKE 语句移除用户的特定权限。

SQL 命令

REVOKE 权限列表 ON 数据库.对象 FROM '用户名'@'主机';

操作步骤

  1. 确定要撤销的权限和对象。
  2. 指定目标用户。
  3. 执行 REVOKE 语句。

示例

  1. 撤销用户 ecommerce_app@%ecommerce 数据库的 INSERT 权限:
REVOKE INSERT ON ecommerce.* FROM 'ecommerce_app'@'%';
  1. 撤销用户 admin_user@localhost 的全局权限:
REVOKE ALL PRIVILEGES ON *.* FROM 'admin_user'@'localhost';

深入分析

  • 效果
    • 撤销权限后,用户立即失去对应操作能力。
    • 不影响用户已执行的操作(如已插入的数据)。
  • 级联撤销
    • 如果用户有 WITH GRANT OPTION 并授予了其他用户权限,撤销其权限会级联影响其他用户。
    • 使用 REVOKE GRANT OPTION FOR ... 仅撤销授予权限的能力:
REVOKE GRANT OPTION FOR ALL PRIVILEGES ON *.* FROM 'admin_user'@'localhost';
  • 权限要求:需要 GRANT OPTION 或管理员权限。

注意事项

  • 撤销权限前,确认不会影响应用程序正常运行。
  • 如果用户无指定权限,REVOKE 不会报错。
  • 撤销后,需重新执行 SHOW GRANTS 确认。

案例:权限控制

场景:为电商系统配置用户权限。

  1. 创建用户
    创建只读用户 readonly_user@% 和应用程序用户 app_user@%
CREATE USER 'readonly_user'@'%' IDENTIFIED BY 'ReadPass2025';
CREATE USER 'app_user'@'%' IDENTIFIED BY 'AppPass2025';
  1. 授予权限
  • readonly_user 授予 ecommerce 数据库的 SELECT 权限:
GRANT SELECT ON ecommerce.* TO 'readonly_user'@'%';
  • app_user 授予 ecommerce 数据库的 SELECT, INSERT, UPDATE 权限:
GRANT SELECT, INSERT, UPDATE ON ecommerce.* TO 'app_user'@'%';
  1. 查询权限
    确认权限分配:
SHOW GRANTS FOR 'readonly_user'@'%';
SHOW GRANTS FOR 'app_user'@'%';
  1. 撤销权限
    撤销 app_userUPDATE 权限:
REVOKE UPDATE ON ecommerce.* FROM 'app_user'@'%';

输出示例(查询权限):

+----------------------------------------------------+
| Grants for readonly_user@%                         |
+----------------------------------------------------+
| GRANT USAGE ON *.* TO 'readonly_user'@'%'         |
| GRANT SELECT ON `ecommerce`.* TO 'readonly_user'@'%' |
+----------------------------------------------------+
+-------------------------------------------------------------+
| Grants for app_user@%                                       |
+-------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'app_user'@'%'                       |
| GRANT SELECT, INSERT ON `ecommerce`.* TO 'app_user'@'%'     |
+-------------------------------------------------------------+

分析

  • 最小权限原则readonly_user 仅能查询,app_user 能执行必要操作。
  • 权限分离:不同用户有不同权限,降低安全风险。
  • 权限管理流程
    • 创建用户 → 授予权限 → 定期审计 → 调整或撤销权限。

总结

2.6.1 管理用户

  • 查询用户:SELECT user, host FROM mysql.user;
  • 修改用户密码:ALTER USER '用户名'@'主机' IDENTIFIED BY '新密码';
  • 删除用户:DROP USER '用户名'@'主机';
  • 案例:创建、查询、修改和删除电商系统用户。

2.6.2 权限控制

  • 查询权限:SHOW GRANTS FOR '用户名'@'主机';
  • 授予权限:GRANT 权限列表 ON 数据库.对象 TO '用户名'@'主机';
  • 撤销权限:REVOKE 权限列表 ON 数据库.对象 FROM '用户名'@'主机';
  • 案例:为电商系统配置只读和应用程序用户权限。

深入补充

  1. 安全性最佳实践
    • 强密码:强制使用复杂密码,启用 validate_password 插件。
    • 最小权限:仅授予用户所需权限,避免使用 ALL PRIVILEGES ON *.*
    • 主机限制:将主机限定为具体 IP 或 localhost,避免 % 通配符。
    • 定期审计
      • 使用 SELECT user, host, create_time FROM mysql.user; 检查用户创建时间。
      • 定期执行 SHOW GRANTS 审查权限。
    • 加密连接:启用 SSL/TLS 确保用户连接安全:
ALTER USER 'app_user'@'%' REQUIRE SSL;
  1. 性能影响
    • 用户管理和权限操作(如 GRANT, REVOKE)通常不影响查询性能,但频繁修改权限表可能导致锁竞争。
    • 大量用户或复杂权限配置可能增加认证开销,建议优化用户数量和权限粒度。
  1. 跨数据库差异
    • PostgreSQL
      • 查询用户:SELECT rolname FROM pg_roles;
      • 修改密码:ALTER ROLE 用户名 WITH PASSWORD '新密码';
      • 删除用户:DROP ROLE 用户名;
      • 授予权限:GRANT SELECT ON 表名 TO 用户名;
      • 撤销权限:REVOKE SELECT ON 表名 FROM 用户名;
    • SQL Server
      • 查询用户:SELECT name FROM sys.database_principals WHERE type = 'S';
      • 修改密码:ALTER LOGIN 用户名 WITH PASSWORD = '新密码';
      • 删除用户:DROP USER 用户名;
      • 授予权限:GRANT SELECT ON 表名 TO 用户名;
      • 撤销权限:REVOKE SELECT ON 表名 FROM 用户名;
    • Oracle
      • 查询用户:SELECT username FROM dba_users;
      • 修改密码:ALTER USER 用户名 IDENTIFIED BY 新密码;
      • 删除用户:DROP USER 用户名 CASCADE;
      • 授予权限:GRANT SELECT ON 表名 TO 用户名;
      • 撤销权限:REVOKE SELECT ON 表名 FROM 用户名;
  1. 角色管理(MySQL 8.0+)
    • MySQL 支持角色,简化权限管理:
CREATE ROLE 'read_only_role';
GRANT SELECT ON ecommerce.* TO 'read_only_role';
GRANT 'read_only_role' TO 'readonly_user'@'%';
SET DEFAULT ROLE 'read_only_role' FOR 'readonly_user'@'%';
    • 查询角色权限:
SHOW GRANTS FOR 'read_only_role';
    • 角色便于批量管理权限,适合大型系统。
  1. 实际应用场景
    • 应用程序用户:为 Web 应用创建专用用户,授予 SELECT, INSERT, UPDATE 等权限。
    • 只读用户:为报表系统或审计人员创建只读用户,仅授予 SELECT 权限。
    • 管理员用户:为 DBA 创建具有 ALL PRIVILEGES 的用户,限制主机为 localhost
    • 权限调整:根据需求动态授予或撤销权限,如临时授予 DELETE 权限清理数据。
  1. 审计与监控
    • 启用 MySQL 审计插件或日志记录权限变更:
SET GLOBAL general_log = 'ON';
    • 使用第三方工具(如 MySQL Enterprise Audit)监控用户操作。
    • 定期检查活跃连接:
SELECT user, host, db, command FROM information_schema.processlist;

以下是对 3. 函数 的深刻且详细解释,基于常见的数据库管理系统(以 MySQL 为例),涵盖 字符串函数数值函数日期函数流程函数,包括每个函数的定义、语法、演示示例、实际应用场景及案例分析。内容将深入探讨函数的用途、性能影响、注意事项及跨数据库差异,力求全面且清晰。


3. 函数

数据库函数是内置的工具,用于处理和转换数据,常用于查询(DQL)、数据操作(DML)和存储过程等场景。MySQL 提供多种类型的函数,包括字符串函数、数值函数、日期函数和流程函数,以下逐一讲解。


3.1 字符串函数

说明:字符串函数用于处理和操作文本数据,如拼接、截取、转换大小写等,常用于数据清洗、格式化输出和条件查询。

常见字符串函数

函数

描述

语法示例

CONCAT(str1, str2, ...)

拼接多个字符串

CONCAT('Hello', ' ', 'World')

LENGTH(str)

返回字符串的字节长度

LENGTH('Hello')

CHAR_LENGTH(str)

返回字符串的字符长度

CHAR_LENGTH('Hello')

UPPER(str)

转换为大写

UPPER('hello')

LOWER En(str)

转换为小写

LOWER('HELLO')

SUBSTRING(str, pos, len)

截取子字符串(从 pos 开始,长度 len)

SUBSTRING('Hello', 2, 3)

TRIM(str)

去除首尾空格

TRIM(' Hello ')

LEFT(str, len)

返回左侧 len 个字符

LEFT('Hello', 2)

RIGHT(str, len)

返回右侧 len 个字符

RIGHT('Hello', 2)

REPLACE(str, from_str, to_str)

替换字符串

REPLACE('Hello', 'l', 'x')

演示如下
假设有一个 users 表:

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50),
  email VARCHAR(100)
);
INSERT INTO users (username, email) VALUES 
  ('Alice', '[email protected]'),
  ('Bob Smith', '[email protected]'),
  ('Charlie', '[email protected]');
  1. CONCAT:拼接用户名和邮箱:
SELECT CONCAT(username, ' - ', email) AS user_info FROM users;

输出

+------------------------------+
| user_info                    |
+------------------------------+
| Alice - [email protected]    |
| Bob Smith - [email protected] |
| Charlie - [email protected] |
+------------------------------+
  1. LENGTH vs CHAR_LENGTH:检查邮箱长度:
SELECT username, LENGTH(email) AS byte_length, CHAR_LENGTH(email) AS char_length 
FROM users;

输出

+-----------+-------------+-------------+
| username  | byte_length | char_length |
+-----------+-------------+-------------+
| Alice     | 17          | 17          |
| Bob Smith | 20          | 20          |
| Charlie   | 20          | 20          |
+-----------+-------------+-------------+

分析LENGTH 返回字节长度(UTF-8 字符可能占 1-4 字节),CHAR_LENGTH 返回字符数。

  1. UPPER / LOWER:统一邮箱大小写:
SELECT username, LOWER(email) AS normalized_email FROM users;

输出

+-----------+----------------------+
| username  | normalized_email      |
+-----------+----------------------+
| Alice     | [email protected]    |
| Bob Smith | [email protected] |
| Charlie   | [email protected]  |
+-----------+----------------------+
  1. SUBSTRING / LEFT / RIGHT:提取邮箱域名:
SELECT username, SUBSTRING(email, LOCATE('@', email) + 1) AS domain 
FROM users;

输出

+-----------+----------------+
| username  | domain         |
+-----------+----------------+
| Alice     | example.com    |
| Bob Smith | example.com    |
| Charlie   | example.com    |
+-----------+----------------+
  1. TRIM / REPLACE:清理用户名并替换空格:
SELECT TRIM(username) AS trimmed, REPLACE(username, ' ', '_') AS replaced 
FROM users;

输出

+-----------+-------------+
| trimmed   | replaced    |
+-----------+-------------+
| Alice     | Alice       |
| Bob Smith | Bob_Smith   |
| Charlie   | Charlie     |
+-----------+-------------+

深入分析

  • 字符集影响LENGTHCHAR_LENGTH 在多字节字符(如中文、表情符号)下结果不同,需根据字符集(如 utf8mb4)选择。
  • 性能:字符串函数(如 SUBSTRING, REPLACE)对大数据量可能影响性能,建议结合索引或预处理数据。
  • 安全性:在动态 SQL 中使用字符串函数时,防止注入(如拼接用户输入)。

注意事项

  • 字符串函数对 NULL 返回 NULL(如 CONCAT(NULL, 'text') 返回 NULL)。
  • 某些函数(如 UPPER, LOWER)受字符集和排序规则影响。
  • 避免在 WHERE 中对字段应用复杂字符串函数,可能导致索引失效。

3.2 数值函数

说明:数值函数用于处理数字数据,如四舍五入、绝对值、随机数等,常用于计算、统计和数据转换。

常见数值函数

函数

描述

语法示例

ABS(x)

返回绝对值

ABS(-5)

ROUND(x, d)

四舍五入到 d 位小数

ROUND(3.14159, 2)

CEIL(x) / CEILING(x)

向上取整

CEIL(3.2)

FLOOR(x)

向下取整

FLOOR(3.7)

MOD(x, y)

取模(余数)

MOD(10, 3)

RAND()

生成 0 到 1 的随机数

RAND()

POWER(x, y) / POW(x, y)

x 的 y 次幂

POWER(2, 3)

演示如下
假设有一个 products 表:

CREATE TABLE products (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100),
  price DECIMAL(10,2),
  stock INT
);
INSERT INTO products (name, price, stock) VALUES 
  ('Laptop', 999.99, 10),
  ('Phone', 499.50, 25),
  ('Tablet', -150.75, 5);
  1. ABS:处理负价格:
SELECT name, ABS(price) AS abs_price FROM products;

输出

+--------+-----------+
| name   | abs_price |
+--------+-----------+
| Laptop | 999.99    |
| Phone  | 499.50    |
| Tablet | 150.75    |
+--------+-----------+
  1. ROUND:四舍五入价格:
SELECT name, ROUND(price, 1) AS rounded_price FROM products;

输出

+--------+---------------+
| name   | rounded_price |
+--------+---------------+
| Laptop | 1000.0        |
| Phone  | 499.5         |
| Tablet | -150.8        |
+--------+---------------+
  1. CEIL / FLOOR:调整库存:
SELECT name, CEIL(stock / 5.0) AS ceil_stock, FLOOR(stock / 5.0) AS floor_stock 
FROM products;

输出

+--------+------------+-------------+
| name   | ceil_stock | floor_stock |
+--------+------------+-------------+
| Laptop | 2          | 2           |
| Phone  | 5          | 5           |
| Tablet | 1          | 1           |
+--------+------------+-------------+
  1. MOD / RAND:随机分配和取模:
SELECT name, MOD(id, 2) AS mod_id, RAND() AS random_value 
FROM products;

输出RAND() 结果随机):

+--------+--------+--------------+
| name   | mod_id | random_value |
+--------+--------+--------------+
| Laptop | 1      | 0.123456     |
| Phone  | 0      | 0.789012     |
| Tablet | 1      | 0.456789     |
+--------+--------+--------------+
  1. POWER:计算折扣后价格:
SELECT name, price * POWER(0.9, 2) AS discounted_price 
FROM products;

输出

+--------+-----------------+
| name   | discounted_price |
+--------+-----------------+
| Laptop | 809.9919        |
| Phone  | 404.5950        |
| Tablet | -122.1075       |
+--------+-----------------+

深入分析

  • 精度DECIMAL 类型适合金融计算,ROUND 可控制小数位数。
  • 随机性RAND() 用于随机排序或抽样,但不适合加密场景。
  • 性能:数值函数通常高效,但避免在 WHERE 条件中对字段应用(如 WHERE ROUND(price) = 100),可能导致全表扫描。

注意事项

  • 数值函数对 NULL 返回 NULL
  • 确保输入数据类型正确(如 POWER('a', 2) 会报错)。
  • 大数据量下,复杂计算可能增加查询时间,建议预计算或缓存结果。

3.3 日期函数

说明:日期函数用于处理日期和时间数据,如提取日期部分、计算时间差、格式化输出等,常用于时间分析和报表。

常见日期函数

函数

描述

语法示例

NOW() / CURRENT_TIMESTAMP

返回当前日期和时间

NOW()

CURDATE() / CURRENT_DATE

返回当前日期

CURbitaDATE()

CURTIME() / CURRENT_TIME

返回当前时间

CURTIME()

DATE_ADD(date, INTERVAL expr unit)

日期加法

DATE_ADD(NOW(), INTERVAL 1 DAY)

DATE_SUB(date, INTERVAL expr unit)

日期减法

DATE_SUB(NOW(), INTERVAL 1 MONTH)

DATEDIFF(date1, date2)

计算日期差(天)

DATEDIFF('2025-04-25', '2025-04-20')

DATE_FORMAT(date, format)

格式化日期

DATE_FORMAT(NOW(), '%Y-%m-%d')

YEAR(date) / MONTH(date) / DAY(date)

提取年/月/日

YEAR(NOW())

演示如下
假设有一个 orders 表:

CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT,
  order_date DATETIME
);
INSERT INTO orders (user_id, order_date) VALUES 
  (1, '2025-04-20 10:00:00'),
  (2, '2025-04-22 15:30:00'),
  (1, '2025-04-25 09:00:00');
  1. NOW / CURDATE / CURTIME:获取当前时间:
SELECT NOW() AS current_time, CURDATE() AS current_date, CURTIME() AS current_time_only;

输出

+---------------------+---------------------+------------------+
| current_time        | current_date        | current_time_only |
+---------------------+---------------------+------------------+
| 2025-04-25 10:00:00 | 2025-04-25          | 10:00:00         |
+---------------------+---------------------+------------------+
  1. DATE_ADD / DATE_SUB:计算订单截止日期:
SELECT order_date, 
       DATE_ADD(order_date, INTERVAL 7 DAY) AS due_date,
       DATE_SUB(order_date, INTERVAL 1 DAY) AS prev_day 
FROM orders;

输出

+---------------------+---------------------+---------------------+
| order_date          | due_date            | prev_day            |
+---------------------+---------------------+---------------------+
| 2025-04-20 10:00:00 | 2025-04-27 10:00:00 | 2025-04-19 10:00:00 |
| 2025-04-22 15:30:00 | 2025-04-29 15:30:00 | 2025-04-21 15:30:00 |
| 2025-04-25 09:00:00 | 2025-05-02 09:00:00 | 2025-04-24 09:00:00 |
+---------------------+---------------------+---------------------+
  1. DATEDIFF:计算订单间隔天数:
SELECT user_id, order_date, 
       DATEDIFF(NOW(), order_date) AS days_since_order 
FROM orders;

输出

+---------+---------------------+------------------+
| user_id | order_date          | days_since_order |
+---------+---------------------+------------------+
| 1       | 2025-04-20 10:00:00 | 5                |
| 2       | 2025-04-22 15:30:00 | 3                |
| 1       | 2025-04-25 09:00:00 | 0                |
+---------+---------------------+------------------+
  1. DATE_FORMAT / YEAR / MONTH:格式化订单日期:
SELECT order_date, 
       DATE_FORMAT(order_date, '%Y-%m') AS year_month,
       YEAR(order_date) AS order_year,
       MONTH(order_date) AS order_month 
FROM orders;

输出

+---------------------+------------+------------+-------------+
| order_date          | year_month | order_year | order_month |
+---------------------+------------+------------+-------------+
| 2025-04-20 10:00:00 | 2025-04    | 2025       | 4           |
| 2025-04-22 15:30:00 | 2025-04    | 2025       | 4           |
| 2025-04-25 09:00:00 | 2025-04    | 2025       | 4           |
+---------------------+------------+------------+-------------+

深入 analysis

  • 时间精度DATETIMETIMESTAMP 类型的字段支持日期函数,TIMESTAMP 受时区影响。
  • 格式化DATE_FORMAT 支持多种格式(如 %Y 表示四位年,%m 表示两位月)。
  • 性能:日期函数在 WHERE 条件中可能导致索引失效(如 WHERE YEAR(order_date) = 2025),建议改为范围查询(如 WHERE order_date >= '2025-01-01' AND order_date < '2026-01-01')。

注意事项

  • 日期函数对 NULL 返回 NULL
  • 确保字段类型为 DATE, DATETIMETIMESTAMP
  • 时区设置(SET time_zone = '+8:00';)可能影响 NOW() 等函数。

3.4 流程函数

说明:流程函数(或控制流函数)用于条件判断和逻辑控制,返回不同值基于输入条件,常用于动态查询和数据转换。

常见流程函数

函数

描述

语法示例

IF(expr, true_value, false_value)

如果 expr 为真,返回 true_value,否则返回 false_value

IF(stock > 0, 'In Stock', 'Out of Stock')

IFNULL(expr, default_value)

如果 expr 为 NULL,返回 default_value

IFNULL(email, 'No Email')

NULLIF(expr1, expr2)

如果 expr1 = expr2,返回 NULL,否则返回 expr1

NULLIF(price, 0)

CASE WHEN ... THEN ... ELSE ... END

多条件分支判断

CASE WHEN price > 500 THEN 'Expensive' ELSE 'Affordable' END

演示如下
使用 productsusers 表。

  1. IF:判断库存状态:
SELECT name, stock, IF(stock > 0, 'In Stock', 'Out of Stock') AS stock_status 
FROM products;

输出

+--------+-------+----------------+
| name   | stock | stock_status   |
+--------+-------+----------------+
| Laptop | 10    | In Stock       |
| Phone  | 25    | In Stock       |
| Tablet | 5     | In Stock       |
+--------+-------+----------------+
  1. IFNULL:处理空邮箱:
SELECT username, IFNULL(email, 'No Email') AS email_status 
FROM users;

输出

+-----------+----------------------+
| username  | email_status         |
+-----------+----------------------+
| Alice     | [email protected]    |
| Bob Smith | [email protected] |
| Charlie   | [email protected]  |
+-----------+----------------------+
  1. NULLIF:将负价格转换为 NULL:
SELECT name, price, NULLIF(price, -150.75) AS adjusted_price 
FROM products;

输出

+--------+---------+----------------+
| name   | price   | adjusted_price |
+--------+---------+----------------+
| Laptop | 999.99  | 999.99         |
| Phone  | 499.50  | 499.50         |
| Tablet | -150.75 | NULL           |
+--------+---------+----------------+
  1. CASE:分类价格:
SELECT name, price, 
       CASE 
         WHEN price > 500 THEN 'Expensive' 
         WHEN price > 100 THEN 'Moderate' 
         ELSE 'Cheap' 
       END AS price_category 
FROM products;

输出

+--------+---------+----------------+
| name   | price   | price_category |
+--------+---------+----------------+
| Laptop | 999.99  | Expensive      |
| Phone  | 499.50  | Moderate       |
| Tablet | -150.75 | Cheap          |
+--------+---------+----------------+

深入分析

  • 灵活性CASE 支持多条件分支,适合复杂逻辑;IF 更简单,适合二元判断。
  • NULL 处理IFNULLNULLIF 是处理空值的利器,常用于数据清洗。
  • 性能:流程函数在 SELECT 中高效,但在 WHERE 中可能影响优化,建议简化条件。

注意事项

  • 流程函数对 NULL 的处理需明确(如 IF(NULL, 1, 0) 返回 0)。
  • 避免嵌套过多 CASEIF,可能降低可读性和性能。
  • 某些数据库(如 PostgreSQL)支持更复杂的控制流结构。

案例

场景:电商系统,包含 usersproductsorders 表:

CREATE TABLE users (
  id INT PRIMARY KEY,
  username VARCHAR(50),
  email VARCHAR(100)
);
CREATE TABLE products (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  price DECIMAL(10,2),
  stock INT
);
CREATE TABLE orders (
  id INT PRIMARY KEY,
  user_id INT,
  product_id INT,
  order_date DATETIME,
  quantity INT
);
INSERT INTO users VALUES 
  (1, 'Alice', '[email protected]'),
  (2, 'Bob', NULL);
INSERT INTO products VALUES 
  (1, 'Laptop', 999.99, 10),
  (2, 'Phone', 499.50, 0),
  (3, 'Tablet', 299.99, 5);
INSERT INTO orders VALUES 
  (1, 1, 1, '2025-04-20 10:00:00', 2),
  (2, 1, 2, '2025-04-22 15:30:00', 1),
  (3, 2, 3, '2025-04-25 09:00:00', 3);

案例需求

  1. 字符串函数:生成用户报告,包含格式化的用户名和邮箱域名:
SELECT 
  CONCAT(UPPER(LEFT(username, 1)), LOWER(SUBSTRING(username, 2))) AS formatted_name,
  SUBSTRING(email,abel
  SUBSTRING(email, LOCATE('@', email) + 1) AS email_domain 
FROM users;

输出

+----------------+--------------+
| formatted_name | email_domain |
+----------------+--------------+
| Alice          | example.com  |
| Bob            | NULL         |
+----------------+--------------+

分析:格式化用户名首字母大写,提取邮箱域名,处理空值。

  1. 数值函数:计算折扣价格并分类库存:
SELECT name, 
       ROUND(price * 0.9, 2) AS discounted_price,
       IF(stock > 0, CEIL(stock / 5.0), 0) AS stock_batches 
FROM products;

输出

+--------+--------------------+---------------+
| name   | discounted_price | stock_batches |
+--------+--------------------+---------------+
| Laptop | 899.99            | 2             |
| Phone  | 449.55            | 0             |
| Tablet | 269.99            | 1             |
+--------+--------------------+---------------+

分析:应用折扣并按批次分组库存,便于库存管理。

  1. 日期函数:分析订单时间,计算订单间隔:
SELECT 
  user_id, 
  order_date, 
  DATE_FORMAT(order_date, '%Y-%m-%d %H:%i') AS formatted_date,
  DATEDIFF(NOW(), order_date) AS days_since_order 
FROM orders;

输出

+---------+---------------------+------------------+------------------+
| user_id | order_date          | formatted_date    | days_since_order |
+---------+---------------------+------------------+------------------+
| 1       | 2025-04-20 10:00:00 | 2025-04-20 10:00 | 5                |
| 1       | 2025-04-22 15:30:00 | 2025-04-22 15:30 | 3                |
| 2       | 2025-04-25 09:00:00 | 2025-04-25 09:00 | 0                |
+---------+---------------------+------------------+------------------+

分析:格式化日期并计算时间差,用于订单跟踪。

  1. 流程函数:分类订单状态和处理空值:
SELECT 
  o.id, 
  u.username, 
  p.name, 
  CASE 
    WHEN o.quantity > 5 THEN 'Large Order' 
    WHEN o.quantity > 1 THEN 'Medium Order' 
    ELSE 'Small Order' 
  END AS order_size,
  IFNULL(u.email, 'No Email') AS email_status 
FROM orders o 
JOIN users u ON o.user_id = u.id 
JOIN products p ON o.product_id = p.id;

输出

+----+----------+--------+-------------+--------------+
| id | username | name   | order_size  | email_status |
+----+----------+--------+-------------+--------------+
| 1  | Alice    | Laptop | Medium Order| [email protected] |
| 2  | Alice    | Phone  | Small Order | [email protected] |
| 3  | Bob      | Tablet | Medium Order| No Email     |
+----+----------+--------+-------------+--------------+

分析:分类订单规模,处理空邮箱,提升报表可读性。


总结

3.1 字符串函数

  • 常见函数:CONCAT, LENGTH, UPPER, SUBSTRING, TRIM, etc.
  • 演示:拼接、截取、格式化字符串。
  • 用途:数据清洗、格式化输出。

3.2 数值函数

  • 常见函数:ABS, ROUND, CEIL, MOD, RAND, etc.
  • 演示:计算折扣、取整、随机数。
  • 用途:金融计算、数据转换。

3.3 日期函数

  • 常见函数:NOW, DATE_ADD, DATEDIFF, DATE_FORMAT, etc.
  • 演示:时间计算、格式化。
  • 用途:时间分析、报表。

3.4 流程函数

  • 常见函数:IF, IFNULL, NULLIF, CASE.
  • 演示:条件判断、NULL 处理。
  • 用途:动态逻辑、数据分类。

案例

  • 综合应用字符串、数值、日期和流程函数,处理电商系统数据,生成用户报告、折扣价格、订单分析等。

深入补充

  1. 性能优化
    • 索引:避免在 WHEREJOIN 中对字段应用函数(如 WHERE UPPER(username) = 'ALICE'),会导致索引失效。
    • 预计算:将频繁使用的函数结果存储为计算列或物化视图:
ALTER TABLE users ADD normalized_email VARCHAR(100) GENERATED ALWAYS AS (LOWER(email)) STORED;
    • 批量处理:大数据量下,结合临时表或存储过程优化函数处理。
  1. 跨数据库差异
    • PostgreSQL
      • 字符串:|| 代替 CONCATLENGTH 类似。
      • 数值:ROUND, ABS 相同,RANDOM() 代替 RAND()
      • 日期:NOW(), AGE() 代替 DATEDIFF
      • 流程:COALESCE 代替 IFNULLCASE 相同。
    • SQL Server
      • 字符串:+ 拼接,LEN 代替 CHAR_LENGTH
      • 数值:ROUND, ABS 类似,RAND() 相同。
      • 日期:GETDATE() 代替 NOW(), DATEDIFF 类似。
      • 流程:ISNULL 代替 IFNULL, CASE 相同。
    • Oracle
      • 字符串:|| 拼接,SUBSTR 代替 SUBSTRING
      • 数值:ROUND, ABS 类似,DBMS_RANDOM.VALUE 代替 RAND()
      • 日期:SYSDATE 代替 NOW(), MONTHS_BETWEEN 代替 DATEDIFF
      • 流程:NVL 代替 IFNULL, CASE 相同。
  1. 安全性
    • 避免在动态 SQL 中直接拼接用户输入的字符串,防止 SQL 注入:
PREPARE stmt FROM 'SELECT * FROM users WHERE username = ?';
SET @username = 'Alice';
EXECUTE stmt USING @username;
    • 使用函数处理敏感数据(如邮箱)时,确保遵守数据隐私法规(如 GDPR)。
  1. 实际应用场景
    • 数据清洗:使用 TRIM, REPLACE, IFNULL 清理用户输入。
    • 报表生成:结合 DATE_FORMAT, ROUND, CASE 格式化统计数据。
    • 动态逻辑:使用 IF, CASE 根据条件生成不同输出。
    • 随机抽样:使用 RAND() 实现数据采样或 A/B 测试。
  1. 高级用法
    • 自定义函数:创建用户定义函数(UDF)扩展功能:
DELIMITER //
CREATE FUNCTION format_price(price DECIMAL(10,2)) RETURNS VARCHAR(20)
DETERMINISTIC
BEGIN
  RETURN CONCAT('$', ROUND(price, 2));
END //
DELIMITER ;
SELECT name, format_price(price) FROM products;
    • 窗口函数(结合日期/数值函数):分析时间序列或排名:
SELECT name, price, 
       RANK() OVER (ORDER BY price DESC) AS price_rank 
FROM products;

4. 约束

4.1 概述

定义:约束(Constraints)是数据库用来确保数据完整性和一致性的规则,应用于表中的列或表本身。约束在数据插入、更新或删除时自动检查,防止不符合规则的操作,确保数据库中的数据符合业务逻辑和数据模型的要求。

作用

  1. 数据完整性
    • 实体完整性:确保每行数据唯一(如主键约束)。
    • 参照完整性:确保外键值与主表匹配(如外键约束)。
    • 域完整性:限制字段值的范围(如非空约束、检查约束)。
  1. 一致性:维护表间或表内数据关系的一致性。
  2. 防止错误:阻止不符合规则的数据操作(如插入无效外键值)。
  3. 规范化:支持数据库规范化设计,减少冗余和异常。

常见约束类型

约束类型

描述

示例

NOT NULL

字段不允许为空

username VARCHAR(50) NOT NULL

UNIQUE

字段值必须唯一(允许 NULL)

email VARCHAR(100) UNIQUE

PRIMARY KEY

唯一标识每行,结合 NOT NULLUNIQUE

id INT PRIMARY KEY

FOREIGN KEY

确保字段值引用主表的主键或唯一键

FOREIGN KEY (user_id) REFERENCES users(id)

CHECK

限制字段值满足特定条件

CHECK (age >= 18)

DEFAULT

为字段设置默认值

created_at DATETIME DEFAULT CURRENT_TIMESTAMP

深入分析

  • 强制性:约束由数据库引擎强制执行,任何违反约束的操作都会被拒绝(如抛出错误)。
  • 性能:约束(如外键)会增加插入、更新、删除的检查开销,但保证数据可靠性。
  • 适用场景:适用于需要高数据一致性的业务系统(如金融、电商),但在高性能场景(如日志系统)可能禁用部分约束以提升速度。

注意事项

  • 约束定义时需考虑业务需求,避免过于严格导致操作受限。
  • 某些数据库(如 MySQL 的 MyISAM 引擎)不支持外键或检查约束。
  • 约束错误需妥善处理,应用程序应捕获并提示用户。

4.2 约束演示

以下通过示例演示各种约束的定义和效果。

示例表结构

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY, -- 主键约束
  username VARCHAR(50) NOT NULL,    -- 非空约束
  email VARCHAR(100) UNIQUE,        -- 唯一约束
  age INT CHECK (age >= 18),        -- 检查约束(MySQL 8.0+)
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP -- 默认值约束
) ENGINE=InnoDB;

CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  amount DECIMAL(10,2) DEFAULT 0.00,
  order_date DATE NOT NULL
) ENGINE=InnoDB;

演示

  1. NOT NULL
    • 插入数据缺少 username
INSERT INTO users (email, age) VALUES ('[email protected]', 20);

结果:报错 ERROR 1364: Field 'username' doesn't have a default value

  1. UNIQUE
    • 插入重复的 email
INSERT INTO users (username, email, age) VALUES ('Alice', '[email protected]', 20);
INSERT INTO users (username, email, age) VALUES ('Bob', '[email protected]', 25);

结果:第二次插入报错 ERROR 1062: Duplicate entry '[email protected]' for key 'email'

  1. PRIMARY KEY
    • 尝试插入重复的 id
INSERT INTO users (id, username, email, age) VALUES (1, 'Alice', '[email protected]', 20);
INSERT INTO users (id, username, email, age) VALUES (1, 'Bob', '[email protected]', 25);

结果:报错 ERROR 1062: Duplicate entry '1' for key 'PRIMARY'

  1. CHECK
    • 插入无效年龄:
INSERT INTO users (username, email, age) VALUES ('Charlie', '[email protected]', 16);

结果:报错 ERROR 3819: Check constraint 'users_chk_1' is violated(MySQL 8.0+)。

  1. DEFAULT
    • 插入数据省略 created_at
INSERT INTO users (username, email, age) VALUES ('Alice', '[email protected]', 20);
SELECT username, created_at FROM users;

输出

+----------+---------------------+
| username | created_at          |
+----------+---------------------+
| Alice    | 2025-04-25 10:00:00 |
+----------+---------------------+

深入分析

  • 组合约束:主键约束是 NOT NULLUNIQUE 的结合,适合唯一标识记录。
  • CHECK 限制:MySQL 5.7 及更早版本不支持 CHECK,需在应用层验证。
  • 默认值:提高数据插入效率,减少手动指定常用值。
  • 引擎依赖:外键和检查约束需使用 InnoDB 引擎,MyISAM 不支持。

注意事项

  • 约束过多可能增加维护成本,需平衡完整性和灵活性。
  • 测试约束效果时,建议在开发环境中模拟各种场景(如插入无效数据)。

4.3 外键约束

4.3.1 介绍

定义:外键约束(Foreign Key Constraint)用于建立和强化两个表之间的参照关系,确保子表中的外键值必须存在于主表的主键或唯一键中,或者为 NULL(如果允许)。外键约束维护 参照完整性,防止无效数据破坏表间关系。

作用

  1. 数据一致性:确保子表引用的值在主表中存在。
  2. 级联操作:支持自动更新或删除关联记录(如 ON DELETE CASCADE)。
  3. 规范化:支持数据库规范化设计,减少数据冗余。

特点

  • 外键字段的数据类型必须与主表的主键/唯一键匹配。
  • 主表必须有 PRIMARY KEYUNIQUE 约束。
  • 外键约束需使用 InnoDB 引擎(MySQL 中 MyISAM 不支持)。

准备数据
创建 usersorders 表,orders 表中的 user_id 引用 users 表的 id

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  amount DECIMAL(10,2),
  FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB;

INSERT INTO users (username) VALUES ('Alice'), ('Bob');
INSERT INTO orders (user_id, amount) VALUES (1, 199.99), (2, 299.99);

测试

  1. 插入无效外键
INSERT INTO orders (user_id, amount) VALUES (3, 99.99);

结果:报错 ERROR 1452: Cannot add or update a child row: a foreign key constraint fails

  1. 删除主表记录
DELETE FROM users WHERE id = 1;

结果:报错 ERROR 1451: Cannot delete or update a parent row: a foreign key constraint fails,因为 orders 表中存在引用 user_id = 1 的记录。

深入分析

  • 参照完整性:外键约束确保 orders.user_id 的值始终在 users.id 中存在。
  • 性能开销:外键检查增加插入、更新、删除的开销,特别是在高并发场景。
  • 灵活性:外键限制可能导致操作复杂,需根据业务场景权衡使用。

注意事项

  • 外键字段通常为 NOT NULL,除非业务允许空值。
  • 主表和子表必须使用相同的存储引擎(通常为 InnoDB)。
  • 外键名称需唯一(MySQL 自动生成名称,或手动指定)。

4.3.2 语法

1) 添加外键

说明:在创建表或修改表时添加外键约束。

SQL 命令

  • 建表时添加
CREATE TABLE 表名 (
  字段定义,
  [CONSTRAINT 外键名] FOREIGN KEY (外键字段) REFERENCES 主表(主表字段)
  [ON DELETE 行为] [ON UPDATE 行为]
);
  • 修改表添加
ALTER TABLE 表名 
ADD [CONSTRAINT 外键名] FOREIGN KEY (外键字段) REFERENCES 主表(主表字段)
[ON DELETE 行为] [ON UPDATE 行为];

案例

  1. 建表时添加外键
CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  amount DECIMAL(10,2),
  CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB;
  1. 修改表添加外键
    假设 orders 表未定义外键,添加外键:
ALTER TABLE orders
ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id);

深入分析

  • 外键名:建议手动指定(如 fk_user),便于后续管理;否则 MySQL 自动生成(如 orders_ibfk_1)。
  • 数据检查:添加外键时,现有数据必须符合约束,否则报错:
ERROR 1452: Cannot add or update a child row: a foreign key constraint fails
  • 引擎要求:外键仅在 InnoDB 引擎中生效。

注意事项

  • 确保主表字段有 PRIMARY KEYUNIQUE 约束。
  • 添加外键前,清理子表中无效的外键值。
  • 外键字段和主表字段的类型和长度必须一致。
2) 删除外键

说明:移除表中的外键约束,保留字段但不再检查参照完整性。

SQL 命令

ALTER TABLE 表名 DROP FOREIGN KEY 外键名;

案例
删除 orders 表中的外键 fk_user

ALTER TABLE orders DROP FOREIGN KEY fk_user;

步骤

  1. 查询外键名称(如果未知):
SHOW CREATE TABLE orders;

或:

SELECT CONSTRAINT_NAME 
FROM information_schema.TABLE_CONSTRAINTS 
WHERE TABLE_NAME = 'orders' AND CONSTRAINT_TYPE = 'FOREIGN KEY';
  1. 执行 DROP FOREIGN KEY 语句。

深入分析

  • 效果:删除外键后,user_id 仍保留为普通字段,但不再检查是否引用 users.id
  • 数据保留:现有数据不受影响,但后续插入/更新不再受外键约束。
  • 性能提升:移除外键可减少检查开销,适合高性能场景。

注意事项

  • 删除外键前,确认业务不再需要参照完整性。
  • 外键名称需准确,错误名称会导致报错。
  • 删除后可能需要应用层逻辑确保数据一致性。

4.3.3 删除/更新行为

说明:外键约束支持定义主表记录删除或更新时子表的处理行为,通过 ON DELETEON UPDATE 子句指定。这些行为确保表间关系在操作时保持一致。

常见行为

行为

描述

应用场景

RESTRICT

阻止主表记录的删除/更新(默认)

严格保护数据一致性

CASCADE

主表记录删除/更新时,子表记录同步删除/更新

自动清理或同步数据

SET NULL

主表记录删除/更新时,子表外键字段设为 NULL

解除关系但保留记录

NO ACTION

类似 RESTRICT,但延迟检查(MySQL 中等同于 RESTRICT

事务场景

SET DEFAULT

主表记录删除/更新时,子表外键设为默认值

自定义默认值(MySQL 不支持)

演示如下
重新创建表,定义不同外键行为:

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT,
  amount DECIMAL(10,2),
  CONSTRAINT fk_user_cascade FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;

CREATE TABLE reviews (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT,
  comment TEXT,
  CONSTRAINT fk_user_set_null FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE SET NULL ON UPDATE SET NULL
) ENGINE=InnoDB;

INSERT INTO users (username) VALUES ('Alice'), ('Bob');
INSERT INTO orders (user_id, amount) VALUES (1, 199.99), (2, 299.99);
INSERT INTO reviews (user_id, comment) VALUES (1, 'Great product'), (2, 'Fast delivery');
  1. ON DELETE CASCADE
    • 删除 users 表中的用户:
DELETE FROM users WHERE id = 1;
SELECT * FROM orders;

输出

+----+---------+--------+
| id | user_id | amount |
+----+---------+--------+
| 2  | 2       | 299.99 |
+----+---------+--------+

分析:用户 id = 1 删除后,orders 表中对应的记录(user_id = 1)也被删除。

  1. ON UPDATE CASCADE
    • 更新 users 表中的 id
UPDATE users SET id = 10 WHERE id = 2;
SELECT * FROM orders;

输出

+----+---------+--------+
| id | user_id | amount |
+----+---------+--------+
| 2  | 10      | 299.99 |
+----+---------+--------+

分析:主表 id 从 2 更新为 10,orders 表中的 user_id 同步更新为 10。

  1. ON DELETE SET NULL
    • 删除 users 表中的用户:
DELETE FROM users WHERE id = 10;
SELECT * FROM reviews;

输出

+----+---------+---------------+
| id | user_id | comment       |
+----+---------+---------------+
| 1  | NULL    | Great product |
| 2  | NULL    | Fast delivery |
+----+---------+---------------+

分析:用户 id = 10 删除后,reviews 表中的 user_id 设为 NULL

  1. RESTRICT(默认)
    • 创建表测试 RESTRICT
CREATE TABLE payments (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT,
  amount DECIMAL(10,2),
  CONSTRAINT fk_user_restrict FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE RESTRICT
) ENGINE=InnoDB;
INSERT INTO payments (user_id, amount) VALUES (1, 50.00);
DELETE FROM users WHERE id = 1;

结果:报错 ERROR 1451: Cannot delete or update a parent row: a foreign key constraint fails

深入分析

  • CASCADE
    • 适合需要自动清理子表数据的场景(如删除用户时删除其订单)。
    • 需谨慎,可能导致数据意外丢失。
  • SET NULL
    • 适合解除关系但保留子表记录的场景(如用户删除后保留评论)。
    • 要求外键字段允许 NULL
  • RESTRICT
    • 严格保护数据,适合高一致性需求的场景(如金融系统)。
    • 可能增加操作复杂性,需手动清理子表数据。
  • 性能
    • CASCADESET NULL 增加触发操作开销。
    • RESTRICT 仅需检查约束,性能较优。
  • 事务支持:外键操作在事务中可回滚,确保一致性:
START TRANSACTION;
DELETE FROM orders WHERE user_id = 1;
DELETE FROM users WHERE id = 1;
COMMIT;

注意事项

  • 确保外键行为与业务逻辑一致(如 SET NULL 需确认子表记录仍有效)。
  • 修改外键行为需先删除外键再重新添加:
ALTER TABLE orders DROP FOREIGN KEY fk_user_cascade;
ALTER TABLE orders ADD CONSTRAINT fk_user_cascade FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;
  • 大量级联操作可能导致性能瓶颈,建议分批处理。

总结

4.1 概述

  • 约束确保数据完整性和一致 一致性,包括 NOT NULL, UNIQUE, PRIMARY KEY, FOREIGN KEY, CHECK, DEFAULT
  • 作用:维护实体、参照和域完整性。

4.2 约束演示

  • 展示了 NOT NULL, UNIQUE, PRIMARY KEY, CHECK, DEFAULT 的定义和效果。
  • 强调约束的强制性和测试方法。

4.3 外键约束

  • 4.3.1 介绍:外键维护参照完整性,测试展示了插入和删除的约束检查。
  • 4.3.2 语法
    • 添加外键:建表或 ALTER TABLE 添加,案例展示命名和添加流程。
    • 删除外键:DROP FOREIGN KEY,案例展示查询和删除。
  • 4.3.3 删除/更新行为
    • 演示了 CASCADE, SET NULL, RESTRICT 的效果。
    • 分析了每种行为的适用场景和性能影响。

深入补充

  1. 性能优化
    • 索引:外键字段和主表主键应有索引以加速约束检查:
CREATE INDEX idx_user_id ON orders(user_id);
    • 批量操作:大批量插入/删除时,临时禁用外键检查(谨慎使用):
SET FOREIGN_KEY_CHECKS = 0;
-- 批量操作
SET FOREIGN_KEY_CHECKS = 1;
    • 分区表:外键可能影响分区表性能,需测试兼容性。
  1. 跨数据库差异
    • PostgreSQL
      • 支持所有约束,CHECK 更强大,支持复杂表达式。
      • 外键行为:CASCADE, SET NULL, RESTRICT, NO ACTION, SET DEFAULT
      • 添加外键:ALTER TABLE orders ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id);
      • 删除外键:ALTER TABLE orders DROP CONSTRAINT fk_user;
    • SQL Server
      • 类似 MySQL,支持所有外键行为。
      • 检查约束支持复杂逻辑。
      • 外键管理类似,但语法稍有不同。
    • Oracle
      • 支持所有约束,外键行为全面。
      • 提供 DEFERRABLE 选项,允许延迟检查:
CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id) DEFERRABLE INITIALLY DEFERRED;
      • 删除外键:ALTER TABLE orders DROP CONSTRAINT fk_user;
  1. 安全性
    • 权限管理:约束定义和修改需 ALTER TABLE 权限,限制非管理员操作。
    • 错误处理:应用程序应捕获约束违反的错误(如 ERROR 1452),提供用户友好提示。
    • 数据清理:添加外键前,运行以下查询检查数据一致性:
SELECT o.user_id 
FROM orders o 
LEFT JOIN users u ON o.user_id = u.id 
WHERE u.id IS NULL;
  1. 实际应用场景
    • 电商系统:外键确保订单和用户、产品的一致性,ON DELETE CASCADE 清理无效订单。
    • 金融系统RESTRICT 防止误删账户记录,保护交易完整性。
    • 博客系统SET NULL 允许删除用户但保留匿名评论。
    • 数据迁移:迁移前检查外键约束,确保目标数据库支持相同行为。
  1. 高级用法
    • 复合外键:支持多列外键,适用于复杂关系:
CREATE TABLE order_details (
  order_id INT,
  product_id INT,
  quantity INT,
  PRIMARY KEY (order_id, product_id),
  FOREIGN KEY (order_id, product_id) REFERENCES order_products(order_id, product_id)
);
    • 触发器替代:在不支持外键的引擎(如 MyISAM同步 issues(如 MyISAM)中,可以使用触发器模拟外键逻辑:
CREATE TRIGGER check_user_id
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
  IF NOT EXISTS (SELECT 1 FROM users WHERE id = NEW.user_id) THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid user_id';
  END IF;
END;
    • 约束命名:为约束指定有意义的名字,便于管理和文档化。
  1. 监控与维护
    • 约束验证:定期运行脚本检查数据是否符合约束:
SELECT * FROM orders WHERE user_id NOT IN (SELECT id FROM users);
    • 日志记录:记录约束违反的日志,便于调试和审计:
SET GLOBAL log_error_suppression_list = '1451,1452';

5. 多表查询

多表查询是指从多个表中检索数据的操作,通常通过表之间的关系(如外键)将数据关联起来。MySQL 支持多种多表查询方式,包括连接查询(内连接、外连接、自连接)和子查询,适用于复杂的数据分析和业务逻辑实现。


5.1 多表关系

多表关系定义了表之间的逻辑关联,决定了如何通过键字段连接数据。常见的多表关系包括 一对多多对多一对一

5.1.1 一对多

说明:一个主表记录对应子表中的多个记录,通常通过外键实现。

示例

  • 场景:一个用户(users 表)可以有多个订单(orders 表)。
  • 表结构
CREATE TABLE users (
  id INT PRIMARY KEY,
  username VARCHAR(50)
);
CREATE TABLE orders (
  id INT PRIMARY KEY,
  user_id INT,
  amount DECIMAL(10,2),
  FOREIGN KEY (user_id) REFERENCES users(id)
);
  • 关系users.id(主表主键)与 orders.user_id(子表外键)形成一对多关系。

特点

  • 主表记录唯一,子表可有多个匹配记录。
  • 常见于用户-订单、部门-员工等场景。
  • 外键约束确保 orders.user_id 存在于 users.id 中。

深入分析

  • 查询效率:一对多查询可能返回大量子表记录,需优化索引(如 orders.user_id)。
  • 级联操作:外键可设置 ON DELETE CASCADE 自动删除子表记录。

5.1.2 多对多

说明:多个主表记录与多个子表记录相关联,通过中间表(关联表)实现。

示例

  • 场景:学生(students 表)可以选修多门课程(courses 表),每门课程可被多个学生选修。
  • 表结构
CREATE TABLE students (
  id INT PRIMARY KEY,
  name VARCHAR(50)
);
CREATE TABLE courses (
  id INT PRIMARY KEY,
  title VARCHAR(100)
);
CREATE TABLE student_courses (
  student_id INT,
  course_id INT,
  PRIMARY KEY (student_id, course_id),
  FOREIGN KEY (student_id) REFERENCES students(id),
  FOREIGN KEY (course_id) REFERENCES courses(id)
);
  • 关系student_courses 表通过 student_idcourse_id 关联 studentscourses

特点

  • 需要中间表存储关系,中间表通常包含两个外键。
  • 常见于学生-课程、用户-角色等场景。
  • 支持灵活的关联管理。

深入分析

  • 查询复杂性:多对多查询需连接三表,性能依赖中间表的索引。
  • 数据维护:插入/删除关系需操作中间表,需确保一致性。

5.1.3 一对一

说明:一个主表记录对应一个子表记录,通常用于拆分表结构或扩展信息。

示例

  • 场景:用户信息(users 表)与用户详细信息(user_details 表)一对一。
  • 表结构
CREATE TABLE users (
  id INT PRIMARY KEY,
  username VARCHAR(50)
);
CREATE TABLE user_details (
  user_id INT PRIMARY KEY,
  phone VARCHAR(20),
  FOREIGN KEY (user_id) REFERENCES users(id)
);
  • 关系user_details.user_id 是主键且为外键,引用 users.id

特点

  • 每行数据在两个表中一一对应。
  • 常见于用户基本信息-扩展信息、员工-档案等场景。
  • 可提高查询效率(分离热点数据)或支持模块化设计。

深入分析

  • 性能:一对一查询通常简单,但需确保外键唯一性。
  • 设计权衡:一对一可能增加表管理复杂性,需评估是否必要。

注意事项

  • 确保表关系清晰,避免误用一对多或多对多。
  • 为外键字段创建索引以优化连接查询。
  • 考虑业务需求选择合适的存储引擎(如 InnoDB 支持外键)。

5.2 多表查询概述

5.2.1 数据准备

为后续演示准备数据,创建 usersordersproducts 表,模拟电商系统的一对多关系:

CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50) NOT NULL
) ENGINE=InnoDB;
CREATE TABLE products (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  price DECIMAL(10,2)
) ENGINE=InnoDB;
CREATE TABLE orders (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT,
  product_id INT,
  quantity INT,
  order_date DATE,
  FOREIGN KEY (user_id) REFERENCES users(id),
  FOREIGN KEY (product_id) REFERENCES products(id)
) ENGINE=InnoDB;

INSERT INTO users (username) VALUES ('Alice'), ('Bob'), ('Charlie');
INSERT INTO products (name, price) VALUES ('Laptop', 999.99), ('Phone', 499.99), ('Tablet', 299.99);
INSERT INTO orders (user_id, product_id, quantity, order_date) VALUES 
  (1, 1, 2, '2025-04-20'),
  (1, 2, 1, '2025-04-22'),
  (2, 3, 3, '2025-04-25'),
  (NULL, 1, 1, '2025-04-26');

数据说明

  • users:3 个用户。
  • products:3 个商品。
  • orders:4 个订单,包含一个无用户(user_id = NULL)的订单。

5.2.2 概述

定义:多表查询通过关联多个表检索数据,通常基于表间的关系(如外键)或条件组合结果。MySQL 支持多种多表查询方式,包括连接查询和子查询。

作用

  • 整合分散在多个表中的数据,生成综合结果。
  • 支持复杂业务逻辑(如统计用户订单总额、查找未下单用户)。
  • 提高数据分析和报表生成能力。

挑战

  • 性能:多表查询可能涉及大量数据,需优化索引和查询结构。
  • 复杂性:表关系和查询逻辑可能复杂,需清晰设计。
  • 数据完整性:需确保表间关系正确,防止无效关联。

5.2.3 分类

多表查询主要分为以下两类:

  1. 连接查询
    • 内连接:返回满足连接条件的记录。
    • 外连接:返回主表所有记录,辅表匹配或填充 NULL
    • 自连接:同一表自我连接。
    • 联合查询:合并多个查询结果。
  1. 子查询
    • 标量子查询:返回单一值。
    • 列子查询:返回一列值。
    • 行子查询:返回一行记录。
    • 表子查询:返回一个结果集。

深入分析

  • 连接 vs 子查询
    • 连接查询适合明确表关系的场景,性能通常优于子查询。
    • 子查询适合分步逻辑或动态条件,灵活但可能性能较低。
  • 优化策略:优先使用连接查询,子查询需谨慎,尤其避免嵌套过多。

5.3 内连接

内连接(Inner Join)返回两个表中满足连接条件的记录,排除不匹配的行。

1) 隐式内连接

说明:通过 WHERE 子句指定连接条件,不显式使用 JOIN 关键字。

SQL 命令

SELECT 字段列表 
FROM 表1, 表2 
WHERE 表1.字段 = 表2.字段;

示例
查询用户和其订单信息:

SELECT u.username, o.id AS order_id, o.quantity 
FROM users u, orders o 
WHERE u.id = o.user_id;

输出

+----------+----------+----------+
| username | order_id | quantity |
+----------+----------+----------+
| Alice    | 1        | 2        |
| Alice    | 2        | 1        |
| Bob      | 3        | 3        |
+----------+----------+----------+

分析

  • 只返回 users.id = orders.user_id 的匹配记录。
  • Charlie(无订单)和 orders.user_id = NULL 的记录被排除。

2) 显式内连接

说明:使用 INNER JOIN 关键字明确指定连接条件,可读性更高。

SQL 命令

SELECT 字段列表 
FROM 表1 INNER JOIN 表2 
ON 表1.字段 = 表2.字段;

示例三表连接
查询用户、订单和商品信息:

SELECT u.username, p.name AS product_name, o.quantity 
FROM users u 
INNER JOIN orders o ON u.id = o.user_id 
INNER JOIN products p ON o.product_id = p.id;

输出

+----------+--------------+----------+
| username | product_name | quantity |
+----------+--------------+----------+
| Alice    | Laptop       | 2        |
| Alice    | Phone        | 1        |
| Bob      | Tablet       | 3        |
+----------+--------------+----------+

案例
统计每个用户的订单总金额:

SELECT u.username, SUM(o.quantity * p.price) AS total_amount 
FROM users u 
INNER JOIN orders o ON u.id = o.user_id 
INNER JOIN products p ON o.product_id = p.id 
GROUP BY u.id, u.username;

输出

+----------+--------------+
| username | total_amount |
+----------+--------------+
| Alice    | 1499.97      |
| Bob      | 899.97       |
+----------+--------------+

深入分析

  • 隐式 vs 显式
    • 隐式内连接语法简单,但可读性差,易混淆条件。
    • 显式内连接更清晰,推荐用于生产环境。
  • 性能
    • 内连接效率高,因只返回匹配记录。
    • 为连接字段(如 user_id, product_id)创建索引可加速查询。
  • 局限性:内连接不包含无匹配的记录,需使用外连接解决。

注意事项

  • 明确指定表别名(如 u, o),提高可读性和避免歧义。
  • 确保连接条件正确,防止笛卡尔积(全交叉连接)。
  • 优先使用主键或索引字段作为连接条件。

5.4 外连接

外连接(Outer Join)返回主表的所有记录,辅表匹配记录填充数据,未匹配的填充 NULL

1) 左外连接

说明:返回左表所有记录,右表匹配记录,未匹配的右表字段为 NULL

SQL 命令

SELECT 字段列表 
FROM 表1 LEFT OUTER JOIN 表2 
ON 表1.字段 = 表2.字段;

示例
查询所有用户及其订单(包括无订单用户):

SELECT u.username, o.id AS order_id, o.quantity 
FROM users u 
LEFT OUTER JOIN orders o ON u.id = o.user_id;

输出

+----------+----------+----------+
| username | order_id | quantity |
+----------+----------+----------+
| Alice    | 1        | 2        |
| Alice    | 2        | 1        |
| Bob      | 3        | 3        |
| Charlie  | NULL     | NULL     |
+----------+----------+----------+

2) 右外连接

说明:返回右表所有记录,左表匹配记录,未匹配的左表字段为 NULL

SQL 命令

SELECT 字段列表 
FROM 表1 RIGHT OUTER JOIN 表2 
ON 表1.字段 = 表2.字段;

示例
查询所有订单及其用户信息(包括无用户订单):

SELECT u.username, o.id AS order_id, o.quantity 
FROM users u 
RIGHT OUTER JOIN orders o ON u.id = o.user_id;

输出

+----------+----------+----------+
| username | order_id | quantity |
+----------+----------+----------+
| Alice    | 1        | 2        |
| Alice    | 2        | 1        |
| Bob      | 3        | 3        |
| NULL     | 4        | 1        |
+----------+----------+----------+

案例
查找未下单的用户:

SELECT u.username 
FROM users u 
LEFT JOIN orders o ON u.id = o.user_id 
WHERE o.id IS NULL;

输出

+----------+
| username |
+----------+
| Charlie  |
+----------+

深入分析

  • 左 vs 右
    • 左外连接以左表为主,适合查询主表全量数据。
    • 右外连接以右表为主,较少使用(可通过左外连接改写)。
  • 性能
    • 外连接比内连接开销更大,因需处理未匹配记录。
    • 优化依赖连接字段索引和合理选择主表。
  • 应用
    • 左外连接常用于查找缺失关联的记录(如未下单用户)。
    • 右外连接适用于子表数据优先的场景(如订单优先于用户)。

注意事项

  • 明确主表和辅表,避免逻辑错误。
  • 处理 NULL 值时,使用 IS NULLCOALESCE
  • MySQL 不支持全外连接(FULL OUTER JOIN),需通过 UNION 实现。

5.5 自连接

5.5.1 自连接查询

说明:自连接是同一表与自身连接,通常用于层次结构或比较同一表中的记录。

SQL 命令

SELECT 字段列表 
FROM 表 AS 别名1 
INNER JOIN 表 AS 别名2 
ON 别名1.字段 = 别名2.字段;

案例
假设 employees 表存储员工和其上级信息:

CREATE TABLE employees (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  manager_id INT
);
INSERT INTO employees (id, name, manager_id) VALUES 
  (1, 'Alice', NULL),
  (2, 'Bob', 1),
  (3, 'Charlie', 1),
  (4, 'Dave', 2);

SELECT e1.name AS employee, e2.name AS manager 
FROM employees e1 
LEFT JOIN employees e2 ON e1.manager_id = e2.id;

输出

+----------+---------+
| employee | manager |
+----------+---------+
| Alice    | NULL    |
| Bob      | Alice   |
| Charlie  | Alice   |
| Dave     | Bob     |
+----------+---------+

分析

  • 使用左外连接确保顶级员工(manager_id = NULL)也显示。
  • 自连接适合树形结构(如组织架构)或比较记录(如同类产品)。

5.5.2 联合查询

说明:联合查询(UNION)合并多个查询结果,去除重复记录(UNION ALL 保留重复)。

SQL 命令

SELECT 字段列表 FROM 表1 WHERE 条件1
UNION [ALL]
SELECT 字段列表 FROM 表2 WHERE 条件2;

案例
合并用户和产品名称:

SELECT username AS name FROM users
UNION
SELECT name FROM products;

输出

+----------+
| name     |
+----------+
| Alice    |
| Bob      |
| Charlie  |
| Laptop   |
| Phone    |
| Tablet   |
+----------+

分析

  • UNION 自动去重,UNION ALL 性能更高(不排序)。
  • 要求每个查询的列数和类型匹配。

深入分析

  • 自连接
    • 需为表指定不同别名(如 e1, e2)。
    • 常用于递归关系,性能依赖索引。
  • 联合查询
    • 适合合并异构数据(如不同表的名称)。
    • 大数据量下,UNION ALL 更高效。

注意事项

  • 自连接需避免无限循环(如循环引用)。
  • UNION 列名以第一个查询为准。
  • 为连接字段创建索引以优化性能。

5.6 子查询

5.6.1 概述

1) 概念

定义:子查询是嵌套在主查询中的查询,返回结果供主查询使用。子查询通常出现在 WHERE, FROMSELECT 中,用于分步处理复杂逻辑。

特点

  • 灵活,支持动态条件和分步计算。
  • 可读性较连接查询低,性能可能较差。
  • 支持多层嵌套,但需控制深度以避免复杂性。
2) 分类

类型

描述

返回结果

示例

标量子查询

返回单一值(一行一列)

单值

SELECT * FROM users WHERE id = (SELECT MAX(id) FROM users);

列子查询

返回一列值

单列

SELECT * FROM orders WHERE user_id IN (SELECT id FROM users WHERE username LIKE 'A%');

行子查询

返回一行记录

单行多列

SELECT * FROM products WHERE (price, stock) = (SELECT MAX(price), MIN(stock) FROM products);

表子查询

返回结果集

多行多列

SELECT * FROM (SELECT user_id, COUNT(*) AS order_count FROM orders GROUP BY user_id) t WHERE order_count > 1;


5.6.2 标量子查询

说明:返回单一值,常用于比较或赋值。

案例
查找订单金额高于平均金额的订单:

SELECT id, amount 
FROM orders 
WHERE amount > (SELECT AVG(amount) FROM orders);

输出(假设平均金额为 224.99):

+----+--------+
| id | amount |
+----+--------+
| 2  | 299.99 |
+----+--------+

分析

  • 子查询 (SELECT AVG(amount) FROM orders) 返回单一值。
  • 主查询使用该值进行比较。

5.6.3 列子查询

说明:返回一列值,常与 IN, NOT IN 使用。

案例
查找有订单的用户的用户名:

SELECT username 
FROM users 
WHERE id IN (SELECT user_id FROM orders WHERE user_id IS NOT NULL);

输出

+----------+
| username |
+----------+
| Alice    |
| Bob      |
+----------+

分析

  • 子查询返回 orders.user_id 列表。
  • 主查询筛选 users.id 在该列表中的记录。

5.6.4 行子查询

说明:返回一行多列,常用于多字段比较。

案例
查找价格最高且库存最低的商品:

SELECT name, price, stock 
FROM products 
WHERE (price, stock) = (SELECT MAX(price), MIN(stock) FROM products);

输出(假设数据符合):

+--------+--------+-------+
| name   | price  | stock |
+--------+--------+-------+
| Laptop | 999.99 | 0     |
+--------+--------+-------+

分析

  • 子查询返回 (最大价格, 最小库存)
  • 主查询匹配整行记录。

5.6.5 表子查询

说明:返回结果集,作为临时表供主查询使用。

案例
统计订单数大于 1 的用户:

SELECT t.username, t.order_count 
FROM (
  SELECT u.username, COUNT(o.id) AS order_count 
  FROM users u 
  LEFT JOIN orders o ON u.id = o.user_id 
  GROUP BY u.id, u.username
) t 
WHERE t.order_count > 1;

输出

+----------+-------------+
| username | order_count |
+----------+-------------+
| Alice    | 2           |
+----------+-------------+

分析

  • 子查询生成每个用户的订单数。
  • 主查询筛选订单数大于 1 的用户。

深入分析

  • 子查询性能
    • 标量子查询和列子查询通常较快,但嵌套过多可能导致性能下降。
    • 表子查询可改写为连接查询以优化:
SELECT u.username, COUNT(o.id) AS order_count 
FROM users u 
LEFT JOIN orders o ON u.id = o.user_id 
GROUP BY u.id, u.username 
HAVING order_count > 1;
  • 适用场景
    • 子查询适合动态条件或分步逻辑。
    • 连接查询更适合明确关系和大规模数据。

注意事项

  • 子查询需返回匹配主查询的列数和类型。
  • 避免在大数据量下使用复杂子查询,优先考虑连接。
  • 为子查询中的字段创建索引以提高效率。

5.7 多表查询案例

场景:电商系统,分析用户、订单和商品数据。

需求

  1. 查询每个用户的订单详情,包括用户名、商品名、订单数量和总金额。
  2. 查找无订单的用户。
  3. 统计每个商品的销售总量,包含未销售的商品。
  4. 查找订单金额高于平均金额的订单。

实现

  1. 订单详情(内连接)
SELECT u.username, p.name AS product_name, o.quantity, o.quantity * p.price AS total_price 
FROM users u 
INNER JOIN orders o ON u.id = o.user_id 
INNER JOIN products p ON o.product_id = p.id;

输出

+----------+--------------+----------+-------------+
| username | product_name | quantity | total_price |
+----------+--------------+----------+-------------+
| Alice    | Laptop       | 2        | 1999.98     |
| Alice    | Phone        | 1        | 499.99      |
| Bob      | Tablet       | 3        | 899.97      |
+----------+--------------+----------+-------------+
  1. 无订单用户(左外连接)
SELECT u.username 
FROM users u 
LEFT JOIN orders o ON u.id = o.user_id 
WHERE o.id IS NULL;

输出

+----------+
| username |
+----------+
| Charlie  |
+----------+
  1. 商品销售总量(左外连接)
SELECT p.name, COALESCE(SUM(o.quantity), 0) AS total_sold 
FROM products p 
LEFT JOIN orders o ON p.id = o.product_id 
GROUP BY p.id, p.name;

输出

+--------+------------+
| name   | total_sold |
+--------+------------+
| Laptop | 3          |
| Phone  | 1          |
| Tablet | 3          |
+--------+------------+
  1. 高额订单(标量子查询)
SELECT o.id, u.username, o.quantity * p.price AS total_price 
FROM orders o 
INNER JOIN users u ON o.user_id = u.id 
INNER JOIN products p ON o.product_id = p.id 
WHERE o.quantity * p.price > (SELECT AVG(quantity * price) FROM orders o2 JOIN products p2 ON o2.product_id = p2.id);

输出

+----+----------+-------------+
| id | username | total_price |
+----+----------+-------------+
| 1  | Alice    | 1999.98     |
+----+----------+-------------+

分析

  • 综合运用内连接、外连接和子查询,满足不同业务需求。
  • 使用 COALESCE 处理 NULL 值,增强结果可靠性。
  • 优化查询通过索引(如 orders.user_id, orders.product_id)和避免复杂子查询。

总结

5.1 多表关系

  • 一对多:主表记录对应多个子表记录(如用户-订单)。
  • 多对多:通过中间表实现(如学生-课程)。
  • 一对一:记录一一对应(如用户-详细信息)。

5.2 多表查询概述

  • 数据准备:创建 users, orders, products 表。
  • 分类:连接查询(内连接、外连接、自连接、联合查询)和子查询。

5.3 内连接

  • 隐式:FROM 表1, 表2 WHERE 条件
  • 显式:INNER JOIN ... ON ...
  • 案例:查询用户订单详情。

5.4 外连接

  • 左外连接:返回左表全量记录。
  • 右外连接:返回右表全量记录。
  • 案例:查找无订单用户。

5.5 自连接

  • 自连接查询:表自我连接,处理层次结构。
  • 联合查询:合并查询结果。
  • 案例:员工-上级关系、名称合并。

5.6 子查询

  • 标量子查询:返回单值。
  • 列子查询:返回一列。
  • 行子查询:返回一行。
  • 表子查询:返回结果集。
  • 案例:高额订单、无订单用户。

5.7 多表查询案例

  • 综合案例展示了订单详情、无订单用户、销售统计和高额订单的查询。

深入补充

  1. 性能优化
    • 索引:为连接字段(如 user_id, product_id)和 WHERE 条件字段创建索引:
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_product_id ON orders(product_id);
    • 查询计划:使用 EXPLAIN 分析查询性能:
EXPLAIN SELECT u.username, o.id FROM users u INNER JOIN orders o ON u.id = o.user_id;
    • 避免笛卡尔积:确保连接条件正确,防止表全交叉。
    • 子查询优化:将子查询改写为连接查询以减少嵌套:
-- 子查询
SELECT username FROM users WHERE id IN (SELECT user_id FROM orders);
-- 连接查询
SELECT DISTINCT u.username FROM users u INNER JOIN orders o ON u.id = o.user_id;
  1. 跨数据库差异
    • PostgreSQL
      • 连接查询语法类似,性能优化更强。
      • 子查询支持 LATERAL 提高灵活性:
SELECT u.username, o.*
FROM users u
LEFT JOIN LATERAL (SELECT * FROM orders WHERE user_id = u.id LIMIT 1) o ON true;
      • 支持 FULL OUTER JOIN
    • SQL Server
      • 连接和子查询语法一致,但 TOP 替代 LIMIT
      • 支持 CROSS APPLY 类似 LATERAL
    • Oracle
      • 旧版本使用 (+) 表示外连接:
SELECT u.username, o.id FROM users u, orders o WHERE u.id = o.user_id(+);
      • 新版本支持标准 JOIN 语法。
      • 子查询支持 WITH(CTE)提高可读性:
WITH order_counts AS (
  SELECT user_id, COUNT(*) AS order_count 
  FROM orders 
  GROUP BY user_id
)
SELECT u.username, oc.order_count 
FROM users u 
LEFT JOIN order_counts oc ON u.id = oc.user_id;
  1. 安全性
    • SQL 注入:使用参数化查询防止用户输入影响多表查询:
PREPARE stmt FROM 'SELECT u.username FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE u.username = ?';
SET @username = 'Alice';
EXECUTE stmt USING @username;
    • 权限控制:限制用户对表的 SELECT 权限,防止未授权访问:
GRANT SELECT ON users TO 'app_user'@'%';
GRANT SELECT ON orders TO 'app_user'@'%';
  1. 实际应用场景
    • 电商报表:统计用户订单总额、销售排名。
    • 社交平台:查询用户及其关注者(自连接)。
    • 数据分析:结合子查询分析用户行为(如高消费用户)。
    • 库存管理:外连接查找未销售商品。
  1. 高级用法
    • 公共表表达式(CTE)
WITH user_orders AS (
  SELECT u.id, u.username, COUNT(o.id) AS order_count 
  FROM users u 
  LEFT JOIN orders o ON u.id = o.user_id 
  GROUP BY u.id, u.username
)
SELECT username, order_count 
FROM user_orders 
WHERE order_count > 1;
    • 窗口函数:结合多表查询分析排名:
SELECT u.username, p.name, o.quantity, 
       RANK() OVER (PARTITION BY u.id ORDER BY o.quantity * p.price DESC) AS price_rank 
FROM users u 
INNER JOIN orders o ON u.id = o.user_id 
INNER JOIN products p ON o.product_id = p.id;
    • 递归查询(处理层次结构):
WITH RECURSIVE emp_hierarchy AS (
  SELECT id, name, manager_id, 1 AS level 
  FROM employees 
  WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.name, e.manager_id, h.level + 1 
  FROM employees e 
  INNER JOIN emp_hierarchy h ON e.manager_id = h.id
)
SELECT name, level FROM emp_hierarchy;
  1. 监控与维护
    • 慢查询日志:启用慢查询日志分析多表查询性能:
SET GLOBAL slow_query_log = 'ON';
    • 索引监控:定期检查索引使用情况:
SELECT * FROM information_schema.STATISTICS WHERE TABLE_NAME IN ('users', 'orders');
    • 查询重构:定期优化复杂查询,拆分或改写为存储过程。

6. 事务

6.1 事务简介

定义:事务(Transaction)是一组数据库操作(通常是 DML 语句,如 INSERTUPDATEDELETE),这些操作作为一个整体执行,要么全部成功提交(commit),要么全部失败回滚(rollback),确保数据库状态的一致性和完整性。

作用

  1. 保证数据一致性:事务确保多步操作要么全部完成,要么全部撤销,防止部分操作导致数据不一致。
  2. 支持并发控制:事务隔离并发操作,防止多个用户同时修改数据引发冲突。
  3. 错误恢复:通过回滚撤销失败操作,保护数据免受异常影响。
  4. 业务逻辑支持:实现复杂业务逻辑(如转账、订单处理),确保操作的原子性。

基本概念

  • 事务开始:通过 START TRANSACTION 或第一条 DML 语句(在自动提交关闭时)启动。
  • 事务结束
    • 提交COMMIT):将操作永久保存到数据库。
    • 回滚ROLLBACK):撤销所有操作,恢复到事务开始前的状态。
  • 事务状态:事务执行期间,操作对其他会话可能不可见(取决于隔离级别)。

适用场景

  • 金融系统:转账操作(扣款和存款必须同时成功)。
  • 电商系统:订单创建(更新库存、记录订单、扣减余额)。
  • 数据迁移:批量插入/更新数据,确保一致性。

深入分析

  • 存储引擎:MySQL 的 InnoDB 支持事务(通过日志和锁机制),而 MyISAM 不支持。
  • 性能:事务增加日志记录(redo logundo log)和锁管理的开销,需权衡性能与一致性。
  • 并发性:事务隔离级别(如 READ COMMITTED, REPEATABLE READ)影响并发性能和数据可见性。

注意事项

  • 事务应尽量简短,避免长时间占用资源导致锁冲突。
  • 非必要时避免在事务中执行复杂查询(如全表扫描)。
  • 确保应用程序正确处理事务异常(如回滚失败)。

6.2 事务操作

6.2.1 数据准备

为演示事务操作,创建以下表结构,模拟电商系统的用户余额和订单场景:

CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50) NOT NULL,
  balance DECIMAL(10,2) NOT NULL DEFAULT 0.00
) ENGINE=InnoDB;

CREATE TABLE orders (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT NOT NULL,
  amount DECIMAL(10,2) NOT NULL,
  order_date DATE NOT NULL,
  FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB;

INSERT INTO users (username, balance) VALUES 
  ('Alice', 1000.00),
  ('Bob', 500.00);

数据说明

  • users:存储用户及其余额。
  • orders:存储订单信息,user_id 引用 users.id
  • 初始数据:Alice 有 1000 元,Bob 有 500 元。

6.2.1 未控制事务

说明:未控制事务指未显式使用 START TRANSACTIONCOMMITROLLBACK,操作受数据库的自动提交模式(autocommit)控制。默认情况下,MySQL 的 InnoDB 引擎启用 autocommit = 1,每条 DML 语句自动提交。

1) 测试正常情况

场景:Alice 下单,扣除余额并记录订单。

SQL 操作

-- 检查初始余额
SELECT username, balance FROM users WHERE username = 'Alice';
-- 扣除余额
UPDATE users SET balance = balance - 200.00 WHERE username = 'Alice';
-- 插入订单
INSERT INTO orders (user_id, amount, order_date) 
VALUES (1, 200.00, '2025-04-25');
-- 检查结果
SELECT username, balance FROM users WHERE username = 'Alice';
SELECT id, user_id, amount FROM orders;

输出

  • 初始:
+----------+---------+
| username | balance |
+----------+---------+
| Alice    | 1000.00 |
+----------+---------+
  • 结果:
+----------+---------+
| username | balance |
+----------+---------+
| Alice    | 800.00  |
+----------+---------+

+----+---------+--------+
| id | user_id | amount |
+----+---------+--------+
| 1  | 1       | 200.00 |
+----+---------+--------+

分析

  • 每条语句独立提交,操作成功且数据一致。
  • 自动提交适合简单操作,但无法处理异常。

2) 测试异常情况

场景:Alice 下单,余额不足导致插入订单失败。

SQL 操作

-- 检查初始余额
SELECT username, balance FROM users WHERE username = 'Alice';
-- 扣除余额(假设余额足够)
UPDATE users SET balance = balance - 1000.00 WHERE username = 'Alice';
-- 插入订单(外键约束失败,user_id 不存在)
INSERT INTO orders (user_id, amount, order_date) 
VALUES (999, 1000.00, '2025-04-25');
-- 检查结果
SELECT username, balance FROM users WHERE username = 'Alice';
SELECT id, user_id, amount FROM orders;

输出

  • 初始:
+----------+---------+
| username | balance |
+----------+---------+
| Alice    | 800.00  |
+----------+---------+
  • 结果:
+----------+---------+
| username | balance |
+----------+---------+
| Alice    | -200.00 |
+----------+---------+

+----+---------+--------+
| id | user_id | amount |
+----+---------+--------+
| 1  | 1       | 200.00 |
+----+---------+--------+
  • 错误:ERROR 1452: Cannot add or update a child row: a foreign key constraint fails.

分析

  • 余额扣除成功(自动提交),但订单插入失败,导致数据不一致(余额为负)。
  • 未控制事务无法保证操作的原子性,需显式事务管理。

深入分析

  • 问题:自动提交模式下,每条语句独立执行,异常无法回滚。
  • 解决方案:使用显式事务(START TRANSACTION, COMMIT, ROLLBACK)控制操作。
  • 性能:自动提交简单但不适合复杂业务逻辑。

注意事项

  • 检查 autocommit 状态:
SELECT @@autocommit;
  • 业务逻辑需验证数据(如余额充足性)以减少异常。

6.2.2 控制事务一

说明:通过设置事务提交方式(autocommit)控制事务行为,显式使用 COMMITROLLBACK 管理事务。

1) 查看/设置事务提交方式

SQL 命令

  • 查看 autocommit
SELECT @@autocommit;
  • 禁用自动提交:
SET autocommit = 0;
  • 启用自动提交:
SET autocommit = 1;

示例

-- 查看当前设置
SELECT @@autocommit;
-- 禁用自动提交
SET autocommit = 0;

输出

+--------------+
| @@autocommit |
+--------------+
| 1            |
+--------------+

分析

  • autocommit = 0 时,每条 DML 语句不立即提交,需显式 COMMITROLLBACK
  • 适合需要手动控制事务的场景。

2) 提交事务

SQL 命令

COMMIT;

示例:Alice 下单,扣除余额并记录订单:

SET autocommit = 0;
-- 扣除余额
UPDATE users SET balance = balance - 200.00 WHERE username = 'Alice';
-- 插入订单
INSERT INTO orders (user_id, amount, order_date) 
VALUES (1, 200.00, '2025-04-25');
-- 提交事务
COMMIT;
-- 检查结果
SELECT username, balance FROM users WHERE username = 'Alice';
SELECT id, user_id, amount FROM orders WHERE user_id = 1;

输出

+----------+---------+
| username | balance |
+----------+---------+
| Alice    | 600.00  |
+----------+---------+

+----+---------+--------+
| id | user_id | amount |
+----+---------+--------+
| 1  | 1       | 200.00 |
| 2  | 1       | 200.00 |
+----+---------+--------+

分析

  • 所有操作在 COMMIT 后永久保存。
  • 事务确保扣除余额和插入订单同时成功。

3) 回滚事务

SQL 命令

ROLLBACK;

示例:Alice 下单,余额不足导致失败:

SET autocommit = 0;
-- 检查初始余额
SELECT username, balance FROM users WHERE username = 'Alice';
-- 扣除余额
UPDATE users SET balance = balance - 1000.00 WHERE username = 'Alice';
-- 插入订单(外键失败)
INSERT INTO orders (user_id, amount, order_date) 
VALUES (999, 1000.00, '2025-04-25');
-- 回滚事务
ROLLBACK;
-- 检查结果
SELECT username, balance FROM users WHERE username = 'Alice';

输出

  • 初始:
+----------+---------+
| username | balance |
+----------+---------+
| Alice    | 600.00  |
+----------+---------+
  • 结果:
+----------+---------+
| username | balance |
+----------+---------+
| Alice    | 600.00  |
+----------+---------+
  • 错误:ERROR 1452: Cannot add or update a child row.

分析

  • 外键约束失败后,ROLLBACK 撤销所有操作,余额恢复。
  • 事务保证数据一致性,避免负余额。

深入分析

  • 提交 vs 回滚
    • COMMITredo log 写入磁盘,确保持久化。
    • ROLLBACK 使用 undo log 恢复数据。
  • 性能
    • 禁用 autocommit 增加事务管理开销,但确保一致性。
    • 事务日志记录增加 I/O 负担,需优化事务范围。
  • 适用性
    • 适合需要手动控制的场景(如批量操作)。
    • 需确保应用程序正确调用 COMMITROLLBACK

注意事项

  • 事务未提交/回滚前,操作对其他会话不可见(取决于隔离级别)。
  • 禁用 autocommit 后,需显式提交或回滚,否则可能导致资源锁定。
  • 检查事务状态:
SELECT @@in_transaction;

6.2.3 控制事务二

说明:使用显式事务语句(START TRANSACTION)控制事务,适合明确事务边界的场景。

1) 开启事务

SQL 命令

START TRANSACTION;

示例

START TRANSACTION;
-- 检查余额
SELECT username, balance FROM users WHERE username = 'Bob';

分析

  • START TRANSACTION 显式开始事务,禁用自动提交。
  • 所有后续 DML 语句成为事务的一部分。

2) 提交事务

示例:Bob 下单,扣除余额并记录订单:

START TRANSACTION;
UPDATE users SET balance = balance - 300.00 WHERE username = 'Bob';
INSERT INTO orders (user_id, amount, order_date) 
VALUES (2, 300.00, '2025-04-25');
COMMIT;
-- 检查结果
SELECT username, balance FROM users WHERE username = 'Bob';
SELECT id, user_id, amount FROM orders WHERE user_id = 2;

输出

+----------+---------+
| username | balance |
+----------+---------+
| Bob      | 200.00  |
+----------+---------+

+----+---------+--------+
| id | user_id | amount |
+----+---------+--------+
| 3  | 2       | 300.00 |
+----+---------+--------+

分析

  • 事务确保扣除余额和插入订单原子性。
  • COMMIT 使操作持久化。

3) 回滚事务

示例:Bob 下单,余额不足:

START TRANSACTION;
UPDATE users SET balance = balance - 500.00 WHERE username = 'Bob';
-- 模拟余额不足检查
SELECT balance FROM users WHERE username = 'Bob';
-- 回滚事务
ROLLBACK;
-- 检查结果
SELECT username, balance FROM users WHERE username = 'Bob';

输出

  • 中间检查(假设未提交):
+---------+
| balance |
+---------+
| -300.00 |
+---------+
  • 结果:
+----------+---------+
| username | balance |
+----------+---------+
| Bob      | 200.00  |
+----------+---------+

分析

  • 余额不足(负值)触发回滚,恢复原始状态。
  • 事务保护数据一致性。

深入分析

  • 显式事务
    • START TRANSACTION 清晰定义事务边界,适合复杂逻辑。
    • 可嵌套保存点(Savepoint):
START TRANSACTION;
UPDATE users SET balance = balance - 100.00 WHERE username = 'Bob';
SAVEPOINT savepoint1;
UPDATE users SET balance = balance - 200.00 WHERE username = 'Bob';
ROLLBACK TO savepoint1;
COMMIT;
  • 性能
    • 显式事务增加日志和锁管理开销,需控制事务持续时间。
    • 保存点支持部分回滚,但增加复杂性。
  • 并发
    • 事务锁定资源(如行锁),可能导致死锁:
SHOW ENGINE INNODB STATUS;

注意事项

  • 确保事务结束(COMMITROLLBACK),否则可能导致连接挂起。
  • 避免在事务中执行长时间查询,减少锁冲突。
  • 使用 SET TRANSACTION 设置隔离级别:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;

6.3 事务四大特性

事务的四大特性(ACID)是数据库事务的核心原则,确保数据操作的可靠性和一致性。

  1. 原子性(Atomicity)
    • 定义:事务中的所有操作要么全部成功执行并提交,要么全部失败并回滚,事务不可分割。
    • 实现:通过 undo log 记录操作前状态,回滚时恢复。
    • 示例:转账事务(扣款和存款)必须全部完成,否则全部撤销。
    • 意义:防止部分操作导致数据不一致(如扣款成功但存款失败)。
  1. 一致性(Consistency)
    • 定义:事务执行前后,数据库从一个一致状态转换到另一个一致状态,满足所有约束(如外键、检查约束)。
    • 实现:通过约束检查、触发器和事务日志确保。
    • 示例:订单插入时,外键 user_id 必须存在于 users 表。
    • 意义:保证数据符合业务规则和完整性要求。
  1. 隔离性(Isolation)
    • 定义:多个事务并发执行时,彼此隔离,互不干扰,防止数据冲突。
    • 实现:通过锁机制(行锁、表锁)和多版本并发控制(MVCC)实现。
    • 隔离级别(MySQL 默认 REPEATABLE READ):
      • 读未提交(Read Uncommitted):可能出现脏读。
      • 读已提交(Read Committed):避免脏读,可能出现不可重复读。
      • 可重复读(Repeatable Read):避免不可重复读,可能出现幻读。
      • 串行化(Serializable):完全隔离,性能最低。
    • 示例:事务 A 更新余额时,事务 B 看不到中间状态。
    • 意义:提高并发安全性,防止并发问题(如脏读、幻读)。
  1. 持久性(Durability)
    • 定义:事务提交后,数据永久保存,即使系统崩溃也能恢复。
    • 实现:通过 redo log 记录提交操作,崩溃后恢复。
    • 示例:订单提交后,即使数据库重启,数据依然存在。
    • 意义:确保数据可靠性,防止丢失。

深入分析

  • ACID 实现
    • 原子性InnoDB 使用 undo log 回滚未提交操作。
    • 一致性:结合约束、外键和触发器,确保规则遵守。
    • 隔离性:MVCC(多版本并发控制)维护数据快照,锁机制防止冲突。
    • 持久性redo log 和双写缓冲(doublewrite buffer)确保数据写入磁盘。
  • 性能权衡
    • 高隔离级别(如 SERIALIZABLE)增加锁开销,降低并发性能。
    • 持久性依赖频繁的日志写入,影响 I/O 性能。
  • 异常处理
    • 应用程序需捕获事务失败(如死锁、约束违反):
ERROR 1213: Deadlock found when trying to get lock; try restarting transaction

注意事项

  • 选择合适的隔离级别,平衡一致性和性能:
SELECT @@transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
  • 监控事务日志空间,避免溢出:
SHOW VARIABLES LIKE 'innodb_log_file_size';
  • 测试事务在并发场景下的行为,确保无死锁或性能瓶颈。

总结

6.1 事务简介

  • 事务是一组原子操作,确保数据一致性和完整性。
  • 适用于金融、电商等需要高一致性的场景。

6.2 事务操作

  • 数据准备:创建 usersorders 表,模拟电商场景。
  • 未控制事务
    • 正常情况:自动提交成功。
    • 异常情况:导致数据不一致,需显式事务。
  • 控制事务一
    • 设置 autocommit = 0,显式 COMMITROLLBACK
    • 确保操作原子性。
  • 控制事务二
    • 使用 START TRANSACTION 显式控制事务。
    • 支持保存点和复杂逻辑。

6.3 事务四大特性

  • 原子性:操作不可分割。
  • 一致性:保持数据规则。
  • 隔离性:并发隔离。
  • 持久性:提交永久保存。

深入补充

  1. 性能优化
    • 短事务:最小化事务操作,减少锁持有时间:
START TRANSACTION;
UPDATE users SET balance = balance - 100.00 WHERE id = 1;
COMMIT;
    • 批量处理:将大事务拆分为小事务:
START TRANSACTION;
INSERT INTO orders (user_id, amount) VALUES (1, 100.00);
COMMIT;
START TRANSACTION;
INSERT INTO orders (user_id, amount) VALUES (1, 200.00);
COMMIT;
    • 索引优化:为事务涉及的字段(如 users.id, orders.user_id)创建索引。
    • 日志管理:调整 innodb_log_file_size 提高日志性能。
  1. 跨数据库差异
    • PostgreSQL
      • 默认隔离级别为 READ COMMITTED,支持 SERIALIZABLE
      • 事务语法类似,但支持更复杂的保存点:
SAVEPOINT my_savepoint;
RELEASE SAVEPOINT my_savepoint;
      • 异常处理更灵活,支持 EXCEPTION 块。
    • SQL Server
      • 默认隔离级别为 READ COMMITTED
      • 事务语法使用 BEGIN TRANSACTION
BEGIN TRANSACTION;
UPDATE users SET balance = balance - 100.00 WHERE id = 1;
COMMIT TRANSACTION;
      • 支持事务嵌套,但需注意计数:
SELECT @@TRANCOUNT;
    • Oracle
      • 默认隐式事务,DML 语句自动开启事务。
      • 提交使用 COMMIT,回滚使用 ROLLBACK
      • 支持 SET CONSTRAINTS DEFERRED 延迟约束检查。
  1. 安全性
    • 权限控制:限制事务操作权限,防止未授权修改:
GRANT UPDATE, INSERT ON users TO 'app_user'@'%';
    • 错误处理:应用程序需捕获事务错误(如死锁)并重试:
ERROR 1205: Lock wait timeout exceeded; try restarting transaction
    • 审计日志:记录事务操作,便于追踪:
SET GLOBAL general_log = 'ON';
  1. 实际应用场景
    • 金融转账:确保扣款和存款同时成功:
START TRANSACTION;
UPDATE accounts SET balance = balance - 100.00 WHERE id = 1;
UPDATE accounts SET balance = balance + 100.00 WHERE id = 2;
COMMIT;
    • 订单处理:同步更新库存、订单和余额:
START TRANSACTION;
UPDATE products SET stock = stock - 1 WHERE id = 1;
INSERT INTO orders (user_id, product_id, quantity) VALUES (1, 1, 1);
UPDATE users SET balance = balance - 100.00 WHERE id = 1;
COMMIT;
    • 数据迁移:批量插入数据,失败时回滚:
START TRANSACTION;
INSERT INTO new_table SELECT * FROM old_table;
COMMIT;
  1. 高级用法
    • 分布式事务:MySQL 支持 XA 事务,用于跨数据库事务:
XA START 'xid';
UPDATE users SET balance = balance - 100.00 WHERE id = 1;
XA END 'xid';
XA PREPARE 'xid';
XA COMMIT 'xid';
    • 事务嵌套:通过保存点实现部分回滚:
START TRANSACTION;
INSERT INTO users (username, balance) VALUES ('Charlie', 300.00);
SAVEPOINT savepoint1;
UPDATE users SET balance = balance - 200.00 WHERE username = 'Charlie';
ROLLBACK TO savepoint1;
COMMIT;
    • 事务监控:检查活跃事务和锁状态:
SELECT * FROM information_schema.INNODB_TRX;
SELECT * FROM information_schema.INNODB_LOCKS;
  1. 监控与维护
    • 死锁检测:启用 innodb_deadlock_detect 自动处理死锁:
SHOW VARIABLES LIKE 'innodb_deadlock_detect';
    • 事务超时:设置锁等待超时:
SET SESSION innodb_lock_wait_timeout = 50;
    • 日志清理:监控事务日志使用情况:
SHOW STATUS LIKE 'Innodb_log%';

以下是对 MySQL 存储引擎 InnoDB 的深刻且详细介绍,涵盖其定义、特性、实现机制、优势、局限性、应用场景、性能优化、与其他存储引擎的对比,以及在实际使用中的注意事项。内容基于 MySQL 的上下文(以最新版本如 8.0+ 为例),力求全面、清晰,并结合实际案例和深入分析。


补充:什么是 InnoDB?

相信仔细阅读上面内容的朋友可以发现,一些语句之后为什么加上InnoDB,下面简单补充一下相关知识,我也将在sql文章下部详细介绍它。

定义:InnoDB 是 MySQL 的默认存储引擎(自 MySQL 5.5.5 起),是一个支持事务、行级锁、外键约束和高并发的高性能存储引擎,适用于需要数据一致性和可靠性的应用。InnoDB 通过 ENGINE=InnoDB 语句在创建表时指定。

核心目标

  • 提供事务支持,确保数据一致性(ACID 特性)。
  • 支持高并发,通过行级锁和多版本并发控制(MVCC)。
  • 保证数据可靠性,通过日志机制(redo log 和 undo log)实现崩溃恢复。
  • 支持复杂关系,通过外键约束维护参照完整性。

语法示例

CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50) NOT NULL
) ENGINE=InnoDB;

2. InnoDB 的核心特性

InnoDB 提供了丰富的特性,使其成为 MySQL 中最常用的存储引擎。以下是其主要特性及其详细说明:

2.1 事务支持

  • 说明:InnoDB 支持事务(Transaction),确保一组操作要么全部成功(COMMIT),要么全部失败(ROLLBACK),符合 ACID 特性(原子性、一致性、隔离性、持久性)。
  • 实现
    • Redo Log:记录提交操作,确保持久性(即使崩溃也能恢复)。
    • Undo Log:记录操作前状态,支持回滚和 MVCC。
  • 示例
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
INSERT INTO orders (user_id, amount) VALUES (1, 100);
COMMIT;
  • 优势:适合金融、电商等需要高一致性的场景。

2.2 行级锁

  • 说明:InnoDB 使用行级锁(Row-Level Locking),仅锁定操作涉及的行,允许其他行并发访问,提高并发性能。
  • 锁类型
    • 共享锁(S Lock):用于读取(如 SELECT ... FOR SHARE)。
    • 排他锁(X Lock):用于修改(如 UPDATE, DELETE)。
  • 实现:通过锁索引记录实现,结合 MVCC 减少锁冲突。
  • 示例
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1; -- 锁定 id=1 的行
COMMIT;
  • 优势:相比表级锁(如 MyISAM),行级锁适合高并发写操作。

2.3 多版本并发控制(MVCC)

  • 说明:MVCC 通过维护数据快照(版本)实现事务隔离,允许多个事务并发读写而互不干扰。
  • 实现
    • 每行记录存储版本信息(DB_TRX_ID, DB_ROLL_PTR)。
    • 读操作访问快照(通过 undo log),写操作创建新版本。
  • 隔离级别
    • 默认:REPEATABLE READ,避免脏读和不可重复读。
    • 其他:READ COMMITTED, READ UNCOMMITTED, SERIALIZABLE
  • 示例
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT balance FROM users WHERE id = 1; -- 读取快照
COMMIT;
  • 优势:提高读写并发性能,减少锁等待。

2.4 外键约束

  • 说明:InnoDB 支持外键(Foreign Key),确保子表字段值引用主表的主键或唯一键,维护参照完整性。
  • 实现:通过索引和触发机制检查外键约束。
  • 示例
CREATE TABLE orders (
  id INT PRIMARY KEY,
  user_id INT,
  FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB;
  • 行为:支持 ON DELETE CASCADE, ON UPDATE SET NULL 等。
  • 优势:适合关系型数据模型(如用户-订单)。

2.5 崩溃恢复

  • 说明:InnoDB 通过日志机制实现崩溃恢复(Crash Recovery),确保提交的数据不丢失,未提交的数据回滚。
  • 实现
    • Redo Log:记录已提交的物理更改,崩溃后重放。
    • Undo Log:记录未提交的更改,崩溃后撤销。
    • Doublewrite Buffer:防止数据页写入中断导致损坏。
  • 示例
    如果 MySQL 崩溃,重启后:
SHOW ENGINE INNODB STATUS;

会显示恢复过程。

  • 优势:提高数据可靠性,适合关键业务系统。

2.6 聚集索引(Clustered Index)

  • 说明:InnoDB 表基于主键组织数据(B+ 树结构),主键值与行数据存储在一起,称为聚集索引。
  • 实现
    • 主键索引直接存储数据行。
    • 非主键索引(二级索引)存储主键值,需回表查询。
  • 示例
CREATE TABLE users (
  id INT PRIMARY KEY,
  username VARCHAR(50)
) ENGINE=InnoDB;

id 是聚集索引,数据按 id 排序存储。

  • 优势:主键查询效率高,适合范围查询。

2.7 数据压缩

  • 说明:InnoDB 支持表和索引的数据压缩,减少存储空间。
  • 实现:通过 ROW_FORMAT=COMPRESSEDKEY_BLOCK_SIZE 配置。
  • 示例
CREATE TABLE logs (
  id INT PRIMARY KEY,
  message TEXT
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;
  • 优势:降低磁盘 I/O,适合大文本或归档数据。

2.8 全文索引

  • 说明:InnoDB 支持全文索引(Full-Text Index,MySQL 5.6+),用于文本搜索。
  • 示例
CREATE TABLE articles (
  id INT PRIMARY KEY,
  content TEXT,
  FULLTEXT (content)
) ENGINE=InnoDB;
SELECT * FROM articles WHERE MATCH(content) AGAINST('keyword');
  • 优势:支持高效文本搜索,替代外部搜索引擎的部分功能。

3. InnoDB 的优势

  1. 高一致性
    • 事务和外键支持确保数据完整性。
    • 适合需要强一致性的业务(如金融、电商)。
  1. 高并发
    • 行级锁和 MVCC 提高读写并发性能。
    • 适合高并发场景(如 Web 应用)。
  1. 可靠性
    • 崩溃恢复机制确保数据不丢失。
    • 适合关键任务系统。
  1. 灵活性
    • 支持多种索引类型(主键、唯一、全文)。
    • 支持复杂查询和关系模型。
  1. 默认引擎
    • MySQL 默认使用 InnoDB,无需额外配置。
    • 广泛支持社区和工具生态。

4. InnoDB 的局限性

  1. 性能开销
    • 事务、锁和日志机制增加 I/O 和 CPU 开销。
    • 相比 MyISAM,写操作和复杂查询可能较慢。
  1. 存储空间
    • 聚集索引和日志文件占用更多空间。
    • 数据压缩配置复杂,可能影响性能。
  1. 复杂性
    • 外键和事务管理增加开发和维护成本。
    • 调试死锁或性能问题需专业知识。
  1. 全文搜索
    • 虽然支持全文索引,但性能和功能不如专用搜索引擎(如 Elasticsearch)。
  1. 不适合简单场景
    • 对于只读或简单插入的场景(如日志记录),MyISAM 或 Memory 引擎可能更高效。

5. InnoDB 的实现机制

5.1 存储结构

  • 表空间(Tablespace)
    • InnoDB 数据存储在表空间中,默认共享表空间(ibdata1)或独立表空间(.ibd 文件)。
    • 独立表空间(innodb_file_per_table = 1)便于管理:
SHOW VARIABLES LIKE 'innodb_file_per_table';
  • 数据页:最小存储单位(默认 16KB),存储行数据和索引。
  • B+ 树:用于聚集索引和二级索引,支持高效查找和范围查询。

5.2 日志系统

  • Redo Log
    • 记录物理更改(数据页更新),确保持久性。
    • 存储在 ib_logfile0, ib_logfile1 等文件中。
    • 配置:innodb_log_file_size, innodb_log_files_in_group
  • Undo Log
    • 记录逻辑更改(操作前状态),支持回滚和 MVCC。
    • 存储在表空间或单独的回滚段中。
  • Binary Log(非 InnoDB 专属):
    • 记录 SQL 语句或行更改,用于复制和恢复。

5.3 锁机制

  • 行锁:锁定特定行(如 SELECT ... FOR UPDATE)。
  • 间隙锁(Gap Lock):锁定索引范围,防止幻读(REPEATABLE READ 级别)。
  • 表锁:某些 DDL 操作(如 ALTER TABLE)触发。
  • 死锁检测:自动检测并回滚一个事务:
SHOW ENGINE INNODB STATUS;

5.4 MVCC

  • 每行记录包含隐藏字段(DB_TRX_ID, DB_ROLL_PTR),跟踪事务版本。
  • 快照读(Snapshot Read)访问旧版本,当前读(Current Read)访问最新版本。
  • 配置隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

5.5 Buffer Pool

  • 说明:InnoDB 使用缓冲池(Buffer Pool)缓存数据页和索引页,减少磁盘 I/O。
  • 配置innodb_buffer_pool_size(建议占服务器内存 60-80%)。
  • 示例
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
  • 优势:提高查询和更新性能。

6. 与其他存储引擎的对比

特性

InnoDB

MyISAM

Memory

Archive

事务支持

锁粒度

行级锁

表级锁

表级锁

表级锁

外键

全文索引

是(5.6+)

崩溃恢复

存储方式

表空间(.ibd)

文件(.MYD, .MYI)

内存

压缩文件

适用场景

高并发、事务

只读、简单插入

临时表

归档数据

对比分析

  • InnoDB vs MyISAM
    • InnoDB 适合写密集和事务场景,MyISAM 适合读密集和简单插入。
    • MyISAM 不支持外键和事务,文件存储更轻量。
  • InnoDB vs Memory
    • Memory 引擎存储在内存,速度快但数据易失,适合临时表。
    • InnoDB 持久化数据,适合长期存储。
  • InnoDB vs Archive
    • Archive 引擎压缩存储,适合归档数据,查询性能低。
    • InnoDB 适合实时操作和复杂查询。

7. 性能优化

  1. Buffer Pool 优化
    • 调整 innodb_buffer_pool_size(如 4GB 服务器设为 2-3GB):
SET GLOBAL innodb_buffer_pool_size = 2147483648; -- 2GB
    • 监控缓冲池命中率:
SHOW STATUS LIKE 'Innodb_buffer_pool%';
  1. 日志优化
    • 增加 innodb_log_file_size(如 512MB)以减少日志切换:
SET GLOBAL innodb_log_file_size = 536870912;
    • 调整 innodb_flush_log_at_trx_commit
      • 1(默认):每次提交写入磁盘,最高可靠性。
      • 02:延迟写入,提高性能但降低持久性。
  1. 索引优化
    • 为主键、外键和查询字段创建索引:
CREATE INDEX idx_user_id ON orders(user_id);
    • 避免冗余索引,定期检查:
SELECT * FROM information_schema.STATISTICS WHERE TABLE_NAME = 'users';
  1. 事务优化
    • 保持事务简短,避免长时间锁定:
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;
    • 使用适当隔离级别(如 READ COMMITTED 降低锁冲突)。
  1. 表空间管理
    • 启用独立表空间(innodb_file_per_table = 1):
SET GLOBAL innodb_file_per_table = 1;
    • 定期优化表空间:
OPTIMIZE TABLE users;
  1. 并发优化
    • 调整 innodb_thread_concurrency(如 0 表示无限制):
SET GLOBAL innodb_thread_concurrency = 0;
    • 监控锁等待和死锁:
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

8. 应用场景

  1. 金融系统
    • 事务支持确保转账操作一致性。
    • 外键维护账户和交易关系。
    • 示例:银行转账:
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
  1. 电商系统
    • 行级锁支持高并发订单处理。
    • 外键确保订单和用户、商品关系。
    • 示例:订单创建:
START TRANSACTION;
INSERT INTO orders (user_id, amount) VALUES (1, 200);
UPDATE users SET balance = balance - 200 WHERE id = 1;
COMMIT;
  1. 企业应用
    • MVCC 支持多用户并发查询和更新。
    • 崩溃恢复保护关键数据。
    • 示例:员工管理:
CREATE TABLE employees (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  department_id INT,
  FOREIGN KEY (department_id) REFERENCES departments(id)
) ENGINE=InnoDB;
  1. 数据仓库
    • 数据压缩减少存储成本。
    • 全文索引支持搜索。
    • 示例:日志分析:
CREATE TABLE logs (
  id INT PRIMARY KEY,
  message TEXT,
  FULLTEXT (message)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;

9. 注意事项

  1. 存储引擎选择
    • 确认表是否使用 InnoDB:
SHOW TABLE STATUS WHERE Name = 'users';
    • 转换引擎(需谨慎,备份数据):
ALTER TABLE users ENGINE=InnoDB;
  1. 外键管理
    • 外键增加检查开销,禁用时需应用层验证:
SET FOREIGN_KEY_CHECKS = 0;
-- 操作
SET FOREIGN_KEY_CHECKS = 1;
    • 确保外键字段有索引。
  1. 事务管理
    • 避免长事务,减少锁冲突:
SHOW ENGINE INNODB STATUS;
    • 显式提交或回滚:
START TRANSACTION;
-- 操作
COMMIT;
  1. 备份与恢复
    • 使用 mysqldumpxtrabackup 备份 InnoDB 表:
mysqldump -u root -p --single-transaction database_name > backup.sql
    • 确保 innodb_force_recovery 配置正确以恢复损坏表。
  1. 监控与调试
    • 监控性能指标:
SHOW GLOBAL STATUS LIKE 'Innodb%';
    • 检查死锁和锁等待:
SELECT * FROM information_schema.INNODB_TRX;

10. 跨数据库差异

  1. PostgreSQL
    • 默认支持事务、行级锁、外键,无需指定引擎。
    • MVCC 实现类似,日志机制更灵活。
    • 表空间管理更强大,支持自定义存储位置。
  1. SQL Server
    • 使用页面级锁(可升级为行锁),事务支持强大。
    • 类似 InnoDB 的日志和缓冲池机制。
    • 外键和约束管理更严格。
  1. Oracle
    • 默认支持事务和 MVCC,类似 InnoDB。
    • 提供更复杂的表空间和分区管理。
    • 外键支持延迟检查(DEFERRABLE)。

对比分析

  • InnoDB 是 MySQL 的事务引擎,功能接近 PostgreSQL 和 Oracle 的默认行为。
  • 相比 SQL Server,InnoDB 的锁粒度更细(行级锁优先)。
  • 非事务引擎(如 MyISAM)无法与 PostgreSQL 等数据库竞争。

11. 实际案例

案例:电商订单处理

  • 需求:用户下单时,扣除余额、插入订单、更新库存,确保一致性。
  • 表结构
CREATE TABLE users (
  id INT PRIMARY KEY,
  username VARCHAR(50),
  balance DECIMAL(10,2)
) ENGINE=InnoDB;
CREATE TABLE products (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  stock INT
) ENGINE=InnoDB;
CREATE TABLE orders (
  id INT PRIMARY KEY,
  user_id INT,
  product_id INT,
  amount DECIMAL(10,2),
  FOREIGN KEY (user_id) REFERENCES users(id),
  FOREIGN KEY (product_id) REFERENCES products(id)
) ENGINE=InnoDB;
INSERT INTO users VALUES (1, 'Alice', 1000.00);
INSERT INTO products VALUES (1, 'Laptop', 10);
  • 事务操作
START TRANSACTION;
UPDATE users SET balance = balance - 200.00 WHERE id = 1;
UPDATE products SET stock = stock - 1 WHERE id = 1;
INSERT INTO orders (user_id, product_id, amount) VALUES (1, 1, 200.00);
COMMIT;
  • 分析
    • 事务确保三步操作原子性。
    • 外键维护用户和商品关系。
    • 行级锁支持并发下单。

优化

  • user_idproduct_id 创建索引:
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_product_id ON orders(product_id);
  • 调整缓冲池大小:
SET GLOBAL innodb_buffer_pool_size = 4294967296; -- 4GB

12. 总结

InnoDB 核心特性

  • 事务支持(ACID)、行级锁、MVCC、外键约束、崩溃恢复。
  • 聚集索引、数据压缩、全文索引。

优势

  • 高一致性、高并发、可靠性。
  • 默认引擎,广泛支持。

局限性

  • 性能开销、存储空间、复杂性。
  • 全文搜索不如专用引擎。

应用场景

  • 金融、电商、企业应用、数据仓库。

优化建议

  • 调整缓冲池、日志、索引。
  • 控制事务范围,监控锁和死锁。

注意事项

  • 确保正确配置表空间和日志。
  • 定期监控性能和备份数据。

你可能感兴趣的:(java,sql)