MySQL :实用函数、约束、多表查询与事务隔离

三、函数

函数是指一段可以直接被另一段程序调用的程序或代码。

(一)字符串函数

(1)MySQL 字符串函数

函数名 功能描述 示例 结果
LENGTH(str) 返回字符串的字节长度(注意 UTF-8 中文是 3 字节) SELECT LENGTH('abc'); 3
CHAR_LENGTH(str) / LENGTH(str)/CHAR_LENGTH(str) 返回字符串的字符数(与字符集无关) SELECT CHAR_LENGTH('你好'); 2
CONCAT(str1, str2, ...) 拼接多个字符串 SELECT CONCAT('My', 'SQL'); MySQL
CONCAT_WS(sep, str1, str2, ...) 使用分隔符拼接字符串(WS = With Separator) SELECT CONCAT_WS('-', '2025', '11', '13'); 2025-11-13
UPPER(str) / UCASE(str) 转换为大写 SELECT UPPER('mysql'); MYSQL
LOWER(str) / LCASE(str) 转换为小写 SELECT LOWER('MySQL'); mysql
LEFT(str, len) 返回左边的 len 个字符 SELECT LEFT('abcdef', 3); abc
RIGHT(str, len) 返回右边的 len 个字符 SELECT RIGHT('abcdef', 2); ef
SUBSTRING(str, pos, len) / MID(str, pos, len) 从指定位置开始截取子串 SELECT SUBSTRING('abcdef', 2, 3); bcd
LOCATE(substr, str) / POSITION(substr IN str) 返回子串第一次出现的位置(找不到返回 0) SELECT LOCATE('b', 'abcdb'); 2
INSTR(str, substr) 与 LOCATE 类似,返回子串位置 SELECT INSTR('abcdb', 'b'); 2
REPLACE(str, from_str, to_str) 字符串替换 SELECT REPLACE('abcabc', 'a', 'x'); xbcxbc
REVERSE(str) 反转字符串 SELECT REVERSE('abc'); cba
LPAD(str, len, padstr) 在字符串左侧填充至指定长度 SELECT LPAD('abc', 6, '*'); ***abc
RPAD(str, len, padstr) 在字符串右侧填充至指定长度 SELECT RPAD('abc', 6, '*'); abc***
LTRIM(str) 去掉左边空格 SELECT LTRIM(' abc'); abc
RTRIM(str) 去掉右边空格 SELECT RTRIM('abc '); abc
TRIM(str) 去掉两端空格 SELECT TRIM(' abc '); abc
TRIM([BOTH | LEADING | TRAILING] remstr FROM str) 去除字符串两端(BOTH)、左侧(LEADING)或右侧(TRAILING)的指定字符 SELECT TRIM(BOTH 'x' FROM 'xxhelloxx'); hello
REPEAT(str, count) 重复字符串 SELECT REPEAT('ab', 3); ababab
SPACE(n) 返回 n 个空格 SELECT CONCAT('A', SPACE(3), 'B'); A B
ELT(N, str1, str2, ...) 返回第 N 个字符串 SELECT ELT(2, 'A', 'B', 'C'); B
FIELD(str, str1, str2, ...) 返回 str 在列表中位置(索引) SELECT FIELD('B', 'A', 'B', 'C'); 2
FIND_IN_SET(str, strlist) 查找字符串是否在以逗号分隔的列表中 SELECT FIND_IN_SET('b', 'a,b,c'); 2
MAKE_SET(bits, str1, str2, ...) 根据 bits 的二进制位返回字符串集合 SELECT MAKE_SET(5, 'a', 'b', 'c'); a,c
INSERT(str, pos, len, newstr) 在字符串中替换部分内容 SELECT INSERT('abcdef', 2, 3, 'XYZ'); aXYZef
QUOTE(str) 用单引号包裹字符串并转义特殊字符 SELECT QUOTE('O\'Reilly'); 'O'Reilly'

(2)示例:

示例1:拼接学生姓名与班级
sql 复制代码
SELECT id, CONCAT(name, ' - ', class_name) AS student_info
FROM student;

示例2:将英文姓名转为小写
sql 复制代码
SELECT name, LOWER(name) AS name_lower
FROM student;

示例3:提取"班级名称"中的数字部分
sql 复制代码
SELECT class_name,
       SUBSTRING(class_name, 5, 1) AS class_number
FROM student
WHERE class_name IS NOT NULL;

示例4:在 id 前补零
sql 复制代码
SELECT id, LPAD(id, 4, '0') AS student_code, name
FROM student;

示例5:反转学生姓名
sql 复制代码
SELECT name, REVERSE(name) AS reversed_name
FROM student;

(二)数值函数

(2)MySQL常用数值函数

函数名 功能描述 示例 结果
ABS(x) 返回 x 的绝对值 SELECT ABS(-10); 10
SIGN(x) 返回 x 的符号(1 正数,0 零,-1 负数) SELECT SIGN(-8); -1
CEIL(x)CEILING(x) 向上取整(不小于 x 的最小整数) SELECT CEIL(3.14); 4
FLOOR(x) 向下取整(不大于 x 的最大整数) SELECT FLOOR(3.14); 3
ROUND(x, d) 四舍五入,保留 d 位小数(默认 0) SELECT ROUND(3.14159, 2); 3.14
TRUNCATE(x, d) 截断到 d 位小数,不四舍五入 SELECT TRUNCATE(3.14159, 2); 3.14
MOD(x, y)x % y 返回 x 除以 y 的余数 SELECT MOD(10, 3); 1
RAND() 返回 [0,1) 的随机数 SELECT RAND(); 0.7324(示例)
POW(x, y)POWER(x, y) 计算 x 的 y 次幂 SELECT POW(2, 3); 8
SQRT(x) 返回平方根 SELECT SQRT(16); 4
EXP(x) 计算 e 的 x 次幂 SELECT EXP(1); 2.7182818
LOG(x) 返回自然对数 ln(x) SELECT LOG(10); 2.302585
LOG10(x) 返回以 10 为底的对数 SELECT LOG10(100); 2
LOG2(x) 返回以 2 为底的对数 SELECT LOG2(8); 3
PI() 返回圆周率 π SELECT PI(); 3.141593
RADIANS(x) 将角度转为弧度 SELECT RADIANS(180); 3.141593
DEGREES(x) 将弧度转为角度 SELECT DEGREES(PI()); 180
SIN(x) 返回正弦值(x 以弧度为单位) SELECT SIN(PI()/2); 1
COS(x) 返回余弦值 SELECT COS(PI()); -1
TAN(x) 返回正切值 SELECT TAN(PI()/4); 1
COT(x) 返回余切值 SELECT COT(PI()/4); 1
LEAST(x1, x2, ...) 返回最小值 SELECT LEAST(3,7,2,5); 2
GREATEST(x1, x2, ...) 返回最大值 SELECT GREATEST(3,7,2,5); 7

示例1:计算每个学生与 22 岁的年龄差(绝对值)
sql 复制代码
SELECT id, age, ABS(age - 22) AS age_diff
FROM student;

示例2:判断学生年龄是奇数还是偶数
sql 复制代码
SELECT id, age, MOD(age, 2) AS age_mod_2
FROM student;


示例3:计算每个学生年龄的平方
sql 复制代码
SELECT id, POW(age, 2) AS age_square
FROM student;

示例4:为每位学生随机生成一个 0~100 的"模拟成绩"
sql 复制代码
SELECT id, name, ROUND(RAND() * 100) AS random_score
FROM student;

(三)日期函数

(1)MySQL 常用日期函数

函数名 功能说明 示例 结果说明
NOW() 返回当前日期和时间(精确到秒) SELECT NOW(); 2025-11-13 14:45:32
CURDATE() 返回当前日期(不含时间) SELECT CURDATE(); 2025-11-13
CURTIME() 返回当前时间(不含日期) SELECT CURTIME(); 14:45:32
SYSDATE() 返回执行 SQL 时的系统日期时间 SELECT SYSDATE(); NOW() 类似,但在多次调用中不同步
YEAR(date) 返回年份 SELECT YEAR(NOW()); 2025
MONTH(date) 返回月份 SELECT MONTH('2025-11-13'); 11
DAY(date) / DAYOFMONTH(date) 返回月份中的"日" SELECT DAY('2025-11-13'); 13
HOUR(time) 返回小时 SELECT HOUR(NOW()); 14
MINUTE(time) 返回分钟 SELECT MINUTE(NOW()); 45
SECOND(time) 返回秒 SELECT SECOND(NOW()); 32
WEEK(date) 返回一年中的第几周 SELECT WEEK('2025-11-13'); 45
DAYNAME(date) 返回星期名称 SELECT DAYNAME('2025-11-13'); Thursday
MONTHNAME(date) 返回月份英文名 SELECT MONTHNAME('2025-11-13'); November
DATE_FORMAT(date, format) 格式化日期输出 SELECT DATE_FORMAT(NOW(), '%Y年%m月%d日 %H:%i:%s'); 2025年11月13日 14:45:32
STR_TO_DATE(str, format) 将字符串解析为日期 SELECT STR_TO_DATE('2025-11-13 14:30:00', '%Y-%m-%d %H:%i:%s'); 2025-11-13 14:30:00
DATE_ADD(date, INTERVAL n unit) 日期加法 SELECT DATE_ADD('2025-11-13', INTERVAL 5 DAY); 2025-11-18
DATE_SUB(date, INTERVAL n unit) 日期减法 SELECT DATE_SUB('2025-11-13', INTERVAL 2 MONTH); 2025-09-13
DATEDIFF(date1, date2) 返回两个日期之间的天数差 SELECT DATEDIFF('2025-11-13', '2025-11-01'); 12
TIMESTAMPDIFF(unit, datetime1, datetime2) 返回时间差(指定单位) SELECT TIMESTAMPDIFF(DAY, '2025-11-01', '2025-11-13'); 12
LAST_DAY(date) 返回该月的最后一天 SELECT LAST_DAY('2025-11-13'); 2025-11-30
FROM_DAYS(n) 将天数(整数)转为日期 SELECT FROM_DAYS(739000); 2025-03-26
TO_DAYS(date) 将日期转为天数(整数) SELECT TO_DAYS('2025-11-13'); 739232

(四)流程函数

(1)MySQL 常用流程控制函数

函数 功能说明 示例 结果说明
IF(expr, true_value, false_value) 如果 expr 为真返回 true_value,否则返回 false_value SELECT name, IF(gender='女','女学生','男学生') AS gender_desc FROM student; 将性别转换为"男学生/女学生"
IFNULL(expr, alt_value) 如果 expr 为 NULL 返回 alt_value,否则返回 expr SELECT name, IFNULL(age, 18) AS age FROM student; 将年龄为 NULL 的学生设为 18
NULLIF(expr1, expr2) 如果 expr1 = expr2 返回 NULL,否则返回 expr1 SELECT id, NULLIF(age, 22) AS age_check FROM student; 年龄为 22 的返回 NULL,其余返回原值
COALESCE(expr1, expr2, ..., exprN) 返回第一个非 NULL 的值 SELECT name, COALESCE(age, 20, 18) AS age_value FROM student; 如果 age 为 NULL 返回 20,否则返回 age
CASE WHEN ... THEN ... [ELSE ...] END 多条件判断,相当于 if/else if/else sql SELECT name, age, CASE WHEN age<21 THEN '年轻' WHEN age<=23 THEN '中年' ELSE '成熟' END AS age_level FROM student; 根据年龄返回"年轻/中年/成熟"
GREATEST(expr1, expr2, ...) 返回最大值 SELECT name, GREATEST(age, 20) AS max_age FROM student; 将年龄和 20 比较,返回较大值
LEAST(expr1, expr2, ...) 返回最小值 SELECT name, LEAST(age, 22) AS min_age FROM student; 将年龄和 22 比较,返回较小值

四、约束

1. 概述

(1)概念

约束是作用于数据库表中字段或表的规则,用于限制数据的输入和修改,以保证数据的准确性和完整性。

(2)目的

保证数据正确性:避免无效或错误的数据存入表中

保证数据有效性:确保数据符合业务规则

保证数据完整性:维护表与表之间的关系一致性

(3)分类
约束类型 描述 对应关键字
主键约束 唯一标识表中的每一行记录,不能为 NULL PRIMARY KEY
唯一约束 保证列的值在表中唯一,可以为空(NULL 可以重复) UNIQUE
非空约束 列值不能为空 NOT NULL
外键约束 保证表与表之间的引用完整性,限制列的值必须在关联表中存在 FOREIGN KEY
检查约束 限制列值必须满足指定条件 CHECK
默认值约束 当插入数据时,如果没有指定列值,则使用默认值 DEFAULT

2. 约束演示

(1)示例表:stu
sql 复制代码
CREATE TABLE stu (
    id INT PRIMARY KEY,                 -- 主键约束
    name VARCHAR(50) NOT NULL,          -- 非空约束
    email VARCHAR(50) UNIQUE,           -- 唯一约束
    age INT CHECK (age >= 0),           -- 检查约束
    gender ENUM('男', '女') DEFAULT '男' -- 默认值约束
);

说明:

id:主键约束,唯一且不能为 NULL

name:非空约束,不能为空

email:唯一约束,每个邮箱唯一

age:检查约束,限制年龄必须 >= 0

gender:默认值约束,如果插入时未指定,则默认为 '男'


(2)测试插入数据
sql 复制代码
INSERT INTO stu (id, name, email, age) VALUES (1, 'Aria', 'aria@example.com', 20);
INSERT INTO stu (id, name, email, age, gender) VALUES (2, 'Bob', 'bob@example.com', 21, '男');
-- INSERT INTO stu (id, name, email, age) VALUES (1, 'Charlie', 'charlie@example.com', 22); -- 会报错,id 主键重复
-- INSERT INTO stu (id, name, email, age) VALUES (3, NULL, 'david@example.com', 23); -- 会报错,name 非空
-- INSERT INTO stu (id, name, email, age) VALUES (4, 'Eve', 'aria@example.com', 19); -- 会报错,email 唯一
-- INSERT INTO stu(id, name, email, age) VALUES (5, 'Frank', 'frank@example.com', -1); -- 会报错,age 检查约束

3. 外键约束

(1)概念

外键用于在两张表之间建立联系,确保数据的一致性和完整性。

外键列的值必须在引用表的主键或唯一列中存在

可以防止子表中出现不存在于主表的值

(2)语法

创建表时添加外键

sql 复制代码
CREATE TABLE 主表 (
    主键列 数据类型 PRIMARY KEY,
    其他列 ...
);

CREATE TABLE 子表 (
    子表主键列 数据类型 PRIMARY KEY,
    外键列 数据类型,
    其他列 ...
    [CONSTRAINT 外键名称] FOREIGN KEY (外键列) REFERENCES 主表(主键列)
);

已存在表中添加外键

sql 复制代码
ALTER TABLE 子表
ADD CONSTRAINT 外键名称 FOREIGN KEY (外键列) REFERENCES 主表(主键列);

删除外键

sql 复制代码
ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;

示例:部门与学生

sql 复制代码
-- 创建部门表(主表)
CREATE TABLE department (
    dept_id INT PRIMARY KEY,
    dept_name VARCHAR(50) NOT NULL
);

-- 创建学生表(子表),带外键约束
CREATE TABLE students (
    id INT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    dept_id INT,
    CONSTRAINT fk_student_dept FOREIGN KEY (dept_id)
        REFERENCES department(dept_id)
);

测试数据

sql 复制代码
-- 插入部门
INSERT INTO department (dept_id, dept_name) VALUES (1, '软件工程'), (2, '网络工程');

-- 插入学生
INSERT INTO students (id, name, dept_id) VALUES (1, 'Aria', 1), (2, 'Bob', 2);

-- 尝试插入不存在的部门
-- INSERT INTO students (id, name, dept_id) VALUES (3, 'Charlie', 3); -- 会报错

sql 复制代码
--删除外键
ALTER TABLE students DROP FOREIGN KEY fk_student_dept;

(3)删除/更新行为

在数据库中,外键约束不仅保证了表与表之间的数据一致性,还可以指定当主表记录被删除或更新时 ,子表应该如何处理。这部分行为通常通过 ON DELETEON UPDATE来控制。


行为分类

行为类型 描述 关键字
删除主表记录 当主表中被引用的记录被删除时,子表的对应外键列的处理方式 ON DELETE
更新主表记录 当主表中被引用的记录被更新时,子表的对应外键列的处理方式 ON UPDATE

可选操作及说明

操作关键字 描述
CASCADE 级联操作:主表删除或更新时,子表对应记录也会被删除或更新
SET NULL 设置空值:删除或更新主表记录时,子表外键列被置为 NULL(前提列允许 NULL)
RESTRICT 限制操作:禁止删除或更新主表中被引用的记录(默认行为)
NO ACTION RESTRICT 类似,禁止操作,在 MySQL 中效果相同
SET DEFAULT 设置默认值:删除或更新主表记录时,子表外键列被设置为默认值(列必须有默认值)

现在我们重新为 students 添加外键约束,并设置 ON DELETE / ON UPDATE 行为。

示例:

删除部门时,学生的部门ID变为 NULL;

更新部门ID时,学生表的对应外键自动同步更新。

sql 复制代码
ALTER TABLE students
ADD CONSTRAINT fk_student_dept
FOREIGN KEY (dept_id)
REFERENCES department(dept_id)
ON DELETE SET NULL
ON UPDATE CASCADE;

测试删除行为:

sql 复制代码
DELETE FROM department WHERE dept_id = 1;

学生表中所有 dept_id = 1 的行会自动变为 NULL(因为 ON DELETE SET NULL)。


测试更新行为:

sql 复制代码
UPDATE department SET dept_id = 3 WHERE dept_id = 2;

学生表中原本 dept_id = 2 的学生会自动更新为 dept_id = 3(因为 ON UPDATE CASCADE)。


五、多表查询

在项目开发中,数据库表通常不是孤立存在的。随着业务模块之间的关联加强,各表之间也会形成不同的关系,从而产生多表查询的需求。


(一)多表关系

1. 概述

在数据库表结构设计中,会根据业务实体之间的关联关系设计不同类型的表关系,常见的有三种:

(1)一对多(多对一)

例如:一个客户可以有多个订单。

(2)多对多

例如:一个订单可以包含多个商品,一个商品也可以出现在多个订单中(通过中间表实现)。

(3)一对一

例如:用户表与用户详情表,一条记录对应一条扩展记录。

这些关系是多表查询的基础。


(二)多表查询概述

多表查询指在一条 SQL 中同时从两张或多张表中查询数据。

示例表

sql 复制代码
-- 客户表
CREATE TABLE customer (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    level VARCHAR(20)
);

-- 订单表
CREATE TABLE orders (
    id INT PRIMARY KEY,
    customer_id INT,
    order_date DATE,
    amount DECIMAL(10,2)
);

-- 商品表
CREATE TABLE product (
    id INT PRIMARY KEY,
    product_name VARCHAR(50),
    category VARCHAR(50),
    price DECIMAL(10,2)
);

-- 订单明细
CREATE TABLE order_item (
    id INT PRIMARY KEY,
    order_id INT,
    product_id INT,
    quantity INT
);

INSERT INTO customer VALUES
(1, '张三', 'VIP'),
(2, '李四', '普通'),
(3, '王五', 'VIP'),
(4, '赵六', '普通');

INSERT INTO orders VALUES
(101, 1, '2024-01-10', 300),
(102, 1, '2024-02-05', 800),
(103, 2, '2024-02-20', 150),
(104, 3, '2024-03-15', 600);

INSERT INTO product VALUES
(1, '键盘', '外设', 150),
(2, '鼠标', '外设', 80),
(3, '显示器', '主机设备', 899),
(4, '显卡', '主机设备', 2500);

INSERT INTO order_item VALUES
(1, 101, 1, 1),
(2, 101, 2, 2),
(3, 102, 3, 1),
(4, 103, 2, 1),
(5, 104, 4, 1);

1. 笛卡儿积(需要避免)

笛卡尔乘积表示两个集合的所有组合情况。

例如:A 表 3 行 × B 表 4 行 → 12 组合。

多表查询时,若未加入连接条件,会产生无效笛卡尔积,因此必须使用连接条件过滤。


2. 多表查询分类:

1. 连接查询(JOIN)

内连接(INNER JOIN)

返回两表交集数据。

外连接(LEFT / RIGHT JOIN)

左外连接:保留左表全部 + 两表交集

右外连接:保留右表全部 + 两表交集

自连接(SELF JOIN)

同一张表自我连接,必须使用别名。


2. 子查询(Subquery)

SQL 中嵌套 SELECT,即为子查询。可用于:

SELECT

WHERE

FROM

INSERT/UPDATE/DELETE 条件中


(三)内连接

内连接返回两张表中满足连接条件的记录(可理解为"匹配的数据部分")。


1. 内连接查询语法

隐式内连接(旧写法)

sql 复制代码
SELECT 字段列表
FROM 表1, 表2
WHERE 连接条件;

显示内连接

sql 复制代码
SELECT 字段列表
FROM 表1
INNER JOIN 表2 ON 连接条件;

2. 内连接示例

查询客户及其订单信息

sql 复制代码
SELECT c.name, o.id AS order_id, o.amount
FROM customer c
INNER JOIN orders o ON c.id = o.customer_id;

(四)外连接(LEFT / RIGHT JOIN)

1. 左外连接(LEFT JOIN)

语法:

sql 复制代码
SELECT 字段列表
FROM 表1
LEFT JOIN 表2 ON 条件;

含义:返回表1全部 + 两表匹配部分

示例:查询所有客户(包括没下过订单的客户)

sql 复制代码
SELECT c.name, o.id AS order_id, o.amount
FROM customer c
LEFT JOIN orders o ON c.id = o.customer_id;

2. 右外连接(RIGHT JOIN)

语法:

sql 复制代码
SELECT 字段列表
FROM 表1
RIGHT JOIN 表2 ON 条件;

含义:返回表2全部 + 两表匹配部分

示例:查询所有订单及客户信息

sql 复制代码
SELECT c.name, o.id AS order_id, o.amount
FROM customer c
RIGHT JOIN orders o ON c.id = o.customer_id;

(五)自连接(SELF JOIN)

自连接指同一张表的不同别名相互连接。

语法:

sql 复制代码
SELECT 字段列表
FROM 表A AS a
JOIN 表A AS b ON 连接条件;

示例:员工与上级(父子级结构)

示例表

sql 复制代码
CREATE TABLE employee (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    leader_id INT
);

INSERT INTO employee VALUES
(1, '张三', NULL),
(2, '李四', 1),
(3, '王五', 1),
(4, '赵六', 2);

查询每个员工的上级

sql 复制代码
SELECT e1.name AS 员工, e2.name AS 上级
FROM employee e1
LEFT JOIN employee e2 ON e1.leader_id = e2.id;

(六)联合查询(UNION / UNION ALL)

用于合并多次查询的结果,要求:

列数一致

数据类型兼容

语法:

sql 复制代码
SELECT 字段列表 FROM 表A...
UNION[ALL]
SELECT 字段列表 FROM 表B...;

区别:

UNION:合并并去重

UNION ALL:合并但不去重(性能更好)


示例:客户姓名 + 商品名称合并结果

sql 复制代码
SELECT name FROM customer
UNION ALL
SELECT product_name FROM product;

(七)子查询(Subquery)

子查询是指在一个 SQL 语句中嵌套另一个 SELECT 查询语句,子查询可以作为外部查询的条件或数据来源。根据返回结果的形态,子查询可以分为四类:

标量子查询(Scalar Subquery):返回单个值(数字、字符串、日期等),常用于比较操作。

列子查询(Column Subquery):返回一列多行,用于 IN、ANY、ALL 等条件判断。

行子查询(Row Subquery):返回一行多列,用于多列比较或匹配。

表子查询(Table Subquery):返回多行多列,可以作为临时表在外部查询中使用。

子查询可以出现在 WHERE、FROM、SELECT,甚至 INSERT、UPDATE、DELETE 的条件中。


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

标量子查询返回单个值,通常用于比较或赋值场景。常用操作符包括 =、<>、>、>=、<、<=。

示例:查询金额最高的订单

sql 复制代码
SELECT *
FROM orders
WHERE amount = (
    SELECT MAX(amount) FROM orders
);

说明:子查询返回订单表中的最大金额,然后外部查询获取对应订单信息。


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

列子查询返回一列多行,用于判断字段是否在指定集合内,常配合 IN、NOT IN、ANY、ALL 使用。

示例:查询下过订单的客户

sql 复制代码
SELECT name
FROM customer
WHERE id IN (
    SELECT customer_id FROM orders
);

使用 ALL 示例:查询价格高于"外设"类所有商品价格的商品

sql 复制代码
SELECT product_name
FROM product
WHERE price > ALL (
    SELECT price FROM product WHERE category = '外设'
);

说明:列子查询可以将外部字段与子查询返回的多行数据进行集合比较,灵活实现过滤逻辑。


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

行子查询返回一行多列,常用于多列组合比较或匹配场景。

示例:查询与订单 101 商品结构完全相同的明细

sql 复制代码
SELECT *
FROM order_item
WHERE (product_id, quantity) IN (
    SELECT product_id, quantity
    FROM order_item
    WHERE order_id = 101
);

说明:通过行子查询可以同时比较多个字段,常用于订单明细、配置信息等多列匹配。


4. 表子查询(返回多行多列)

表子查询返回多行多列,常作为临时表在外部查询中使用,可用于聚合统计或复杂数据计算。

示例:查询每个订单的总金额

sql 复制代码
SELECT t.order_id, t.total_amount
FROM (
    SELECT oi.order_id, SUM(p.price * oi.quantity) AS total_amount
    FROM order_item oi
    JOIN product p ON oi.product_id = p.id
    GROUP BY oi.order_id
) t;

说明:子查询生成了一个临时表 t ,外部查询直接使用聚合结果,避免了重复计算。


六、事务

(一)事务简介

事务(Transaction)是数据库中的一个逻辑执行单元,由一组操作构成,这些操作要么全部成功、要么全部失败,是不可分割的整体。

例如,银行转账属于典型事务场景:

A 给 B 转 100 元

操作包含:A 账户扣 100、B 账户加 100

两步必须要么都成功,要么都失败,否则会造成资金丢失


MySQL 默认自动提交(autocommit)

MySQL 默认开启 autocommit = 1:

每执行一条 DML(INSERT/UPDATE/DELETE)语句,MySQL 自动提交一个事务。

sql 复制代码
SELECT @@autocommit;

如果想把一个事务中的操作"打包提交",必须关闭自动提交:

sql 复制代码
SET @@autocommit = 0;

(二)事务操作

方式一:使用 autocommit 控制事务

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

sql 复制代码
SELECT @@autocommit;     -- 查看当前事务提交方式
SET @@autocommit = 0;    -- 设置为手动提交

2. 手动提交事务

sql 复制代码
COMMIT;

3. 手动回滚事务

sql 复制代码
ROLLBACK;

方式二:显式开启事务

当你想手动控制事务时:

sql 复制代码
START TRANSACTION;
-- 或
BEGIN;

执行操作后:

sql 复制代码
COMMIT;    -- 提交
ROLLBACK;  -- 回滚

(三)事务四大特性(ACID)

原子性(Atomicity): 事务是不可分割的最小操作单元,要么全部成功,要么全部失败。

一致性(Consistency): 事务完成时,必须使所有的数据都保持一致状态。

隔离性(Isolation):不同事务之间相互隔离,并发执行不互相影响。

持久性(Durability):事务一旦提交,结果永久保存,即使宕机也不会丢失。

(四)并发事务问题

多事务同时执行,会产生如下典型问题:

问题 描述
脏读(Dirty Read) 读到了另一个事务未提交的数据
不可重复读(Non-repeatable Read) 同一事务,多次读取 同一记录,结果不一致
幻读(Phantom Read) 同一事务内多次执行 范围查询,数据行数不一致

(五)事务隔离级别

隔离级别(从低到高) 脏读(Dirty Read) 不可重复读(Non-repeatable Read) 幻读(Phantom Read) 说明
Read Uncommitted ✔(会出现) ✔(会出现) ✔(会出现) 几乎没有隔离,问题最多
Read Committed ✖(不会出现) ✔(可能出现) ✔(可能出现) Oracle 默认级别
Repeatable Read(MySQL 默认) ✔(理论上可能) MySQL 通过间隙锁减少幻读
Serializable 完全串行执行,无并发问题

隔离级别越高,数据越安全

并发性能越差


查看当前隔离级别

sql 复制代码
SELECT @@TRANSACTION_ISOLATION;

修改隔离级别

sql 复制代码
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

级别可选:

READ UNCOMMITTED

READ COMMITTED

REPEATABLE READ

SERIALIZABLE


(六)事务隔离级别演示

为了更直观地理解事务隔离级别对数据库并发行为的影响,我们可以使用两个独立的 CMD 窗口分别启动两个 MySQL 客户端实例,通过模拟两个并发事务,观察四种典型并发问题的实际发生过程。


1. 准备测试环境

执行以下 SQL,创建并初始化测试库:

sql 复制代码
CREATE DATABASE test_tx;
USE test_tx;

-- 账户表(用于脏读与不可重复读演示)
CREATE TABLE account (
    id INT PRIMARY KEY,
    balance INT
);
INSERT INTO account VALUES (1, 1000);

-- 订单表(用于幻读演示)
CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    amount INT
);
INSERT INTO orders(amount) VALUES (200), (300), (150);

2. 脏读(Dirty Read)演示

脏读指的是一个事务读取到了另一个事务 尚未提交 的数据,只会在 READ UNCOMMITTED 隔离级别下出现。

(1)两个窗口均设置隔离级别
sql 复制代码
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
(2)模拟过程

CMD1(修改但不提交)

sql 复制代码
START TRANSACTION;
UPDATE account SET balance = 500 WHERE id = 1;
-- 不提交事务

CMD2(读取未提交数据)

sql 复制代码
SELECT balance FROM account WHERE id = 1;

CMD2 会读取到 500,即 CMD1 尚未提交的数据,这就是典型的脏读。

CMD1 回滚:

sql 复制代码
ROLLBACK;

3. 不可重复读(Non-repeatable Read)演示

不可重复读是指同一个事务内的两次查询结果不一致,通常在 READ COMMITTED 下发生。

(1)设置隔离级别
sql 复制代码
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
(2) 模拟过程

CMD1:开启事务并读取数据

sql 复制代码
START TRANSACTION;
SELECT balance FROM account WHERE id = 1;   -- 结果:1000

CMD2:修改并提交

sql 复制代码
UPDATE account SET balance = 800 WHERE id = 1;
COMMIT;

CMD1:再次读取

sql 复制代码
SELECT balance FROM account WHERE id = 1;   -- 结果:800,与第一次不同

这就是"不可重复读":同一个事务中,两次读取同一行数据却得到了不同结果。

CMD1 结束事务

sql 复制代码
COMMIT;

4.幻读(Phantom Read)演示

幻读是指事务在按某个范围条件查询时,发现后续的查询结果出现了"凭空多出的记录"。在 READ COMMITTED 和 Repeatable Read(MySQL 默认) 下理论上都可能发生幻读。

为简化演示,我们使用 READ COMMITTED

(1)设置隔离级别
sql 复制代码
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
(2)模拟操作

CMD1:第一次查询

sql 复制代码
START TRANSACTION;
SELECT * FROM orders WHERE amount > 100;  
-- 结果:3 行

CMD2:插入满足条件的新数据

sql 复制代码
INSERT INTO orders(amount) VALUES (500);
COMMIT;

CMD1:再次查询

sql 复制代码
SELECT * FROM orders WHERE amount > 100;
-- 结果:4 行,比第一次多一行

这条"凭空出现"的记录就是幻读(Phantom Row)。

CMD1 提交事务

sql 复制代码
COMMIT;

5. 验证最高隔离级别 Serializable

Serializable 是最严格的隔离级别,会强制所有读取操作加锁,使并发行为表现为串行执行,从而完全避免幻读。

(1)设置隔离级别
sql 复制代码
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
(2)模拟过程

CMD1:

sql 复制代码
START TRANSACTION;
SELECT * FROM orders WHERE amount > 100;

CMD2(尝试插入新数据):

sql 复制代码
INSERT INTO orders(amount) VALUES (999);

此时 CMD2 会被阻塞,直到 CMD1 完成提交,因为 Serializable 会对读范围加锁,从根源上消除幻读。

在演示 MySQL 的事务隔离级别(如脏读、不可重复读、幻读等)时,将两个 CMD 窗口都设置为相同的隔离级别是完全可行的,也是教学和实验中最常用的做法。

严格来说,实际上只有涉及读取数据的窗口需要设置隔离级别即可,但为了保证实验环境的一致性、避免干扰和减少理解难度,建议在实际演示时,两侧窗口都显式地设置隔离级别。

相关推荐
百***35333 小时前
PostgreSQL_安装部署
数据库·postgresql
rayylee5 小时前
生活抱怨与解决方案app
数据库·生活
Lucifer三思而后行6 小时前
使用 BR 备份 TiDB 到 AWS S3 存储
数据库·tidb·aws
百***17077 小时前
Oracle分页sql
数据库·sql·oracle
qq_436962187 小时前
数据中台:打破企业数据孤岛,实现全域资产化的关键一步
数据库·人工智能·信息可视化·数据挖掘·数据分析
weixin_537765808 小时前
【数据库管理】MySQL主从复制详解
数据库·mysql
q***33378 小时前
数据库高安全—openGauss安全整体架构&安全认证
数据库·安全·架构
范纹杉想快点毕业9 小时前
《嵌入式开发硬核指南:91问一次讲透底层到架构》
java·开发语言·数据库·单片机·嵌入式硬件·mongodb
倚肆9 小时前
MySQL 配置文件属性详解
数据库·mysql