当前位置: 首页 > news >正文

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:拼接学生姓名与班级
SELECT id, CONCAT(name, ' - ', class_name) AS student_info
FROM student;


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


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


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


示例5:反转学生姓名
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 岁的年龄差(绝对值)
SELECT id, age, ABS(age - 22) AS age_diff
FROM student;

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


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


示例4:为每位学生随机生成一个 0~100 的“模拟成绩”
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_valueSELECT name, IF(gender='女','女学生','男学生') AS gender_desc FROM student;将性别转换为“男学生/女学生”
IFNULL(expr, alt_value)如果 expr 为 NULL 返回 alt_value,否则返回 exprSELECT name, IFNULL(age, 18) AS age FROM student;将年龄为 NULL 的学生设为 18
NULLIF(expr1, expr2)如果 expr1 = expr2 返回 NULL,否则返回 expr1SELECT 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/elsesql 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)分类
约束类型描述对应关键字
主键约束唯一标识表中的每一行记录,不能为 NULLPRIMARY KEY
唯一约束保证列的值在表中唯一,可以为空(NULL 可以重复)UNIQUE
非空约束列值不能为空NOT NULL
外键约束保证表与表之间的引用完整性,限制列的值必须在关联表中存在FOREIGN KEY
检查约束限制列值必须满足指定条件CHECK
默认值约束当插入数据时,如果没有指定列值,则使用默认值DEFAULT

2. 约束演示

(1)示例表:stu
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)测试插入数据
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)语法

        创建表时添加外键

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

        已存在表中添加外键

ALTER TABLE 子表
ADD CONSTRAINT 外键名称 FOREIGN KEY (外键列) REFERENCES 主表(主键列);

        删除外键

ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;

示例:部门与学生

-- 创建部门表(主表)
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)
);

测试数据

-- 插入部门
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); -- 会报错


--删除外键
ALTER TABLE students DROP FOREIGN KEY fk_student_dept;


(3)删除/更新行为

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


行为分类

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

 可选操作及说明

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

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

示例:

删除部门时,学生的部门ID变为 NULL; 
更新部门ID时,学生表的对应外键自动同步更新。

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

测试删除行为:

DELETE FROM department WHERE dept_id = 1;

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


测试更新行为:

UPDATE department SET dept_id = 3 WHERE dept_id = 2;

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


五、多表查询

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


(一)多表关系

1. 概述

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

(1)一对多(多对一)
例如:一个客户可以有多个订单。

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

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

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


(二)多表查询概述

多表查询指在一条 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. 内连接查询语法

 隐式内连接(旧写法)

SELECT 字段列表
FROM 表1, 表2
WHERE 连接条件;

 显示内连接

SELECT 字段列表
FROM 表1
INNER JOIN 表2 ON 连接条件;

2. 内连接示例

查询客户及其订单信息

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)

语法:

SELECT 字段列表
FROM 表1
LEFT JOIN 表2 ON 条件;

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

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

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)

语法:

SELECT 字段列表
FROM 表1
RIGHT JOIN 表2 ON 条件;

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

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

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)

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

语法:

SELECT 字段列表
FROM 表A AS a
JOIN 表A AS b ON 连接条件;

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

示例表

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);


查询每个员工的上级

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


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

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

        列数一致

        数据类型兼容

语法:

SELECT 字段列表 FROM 表A...
UNION[ALL]
SELECT 字段列表 FROM 表B...;

区别:

UNION:合并并去重

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


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

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. 标量子查询(返回单个值)

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

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

SELECT *
FROM orders
WHERE amount = (SELECT MAX(amount) FROM orders
);

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


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

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

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

SELECT name
FROM customer
WHERE id IN (SELECT customer_id FROM orders
);


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

SELECT product_name
FROM product
WHERE price > ALL (SELECT price FROM product WHERE category = '外设'
);

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


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

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

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

SELECT *
FROM order_item
WHERE (product_id, quantity) IN (SELECT product_id, quantityFROM order_itemWHERE order_id = 101
);

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


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

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

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

SELECT t.order_id, t.total_amount
FROM (SELECT oi.order_id, SUM(p.price * oi.quantity) AS total_amountFROM order_item oiJOIN product p ON oi.product_id = p.idGROUP BY oi.order_id
) t;

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


六、事务

(一)事务简介

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

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

A 给 B 转 100 元

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

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


MySQL 默认自动提交(autocommit)

MySQL 默认开启 autocommit = 1:
每执行一条 DML(INSERT/UPDATE/DELETE)语句,MySQL 自动提交一个事务。

SELECT @@autocommit;

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

SET @@autocommit = 0;

(二)事务操作

方式一:使用 autocommit 控制事务

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

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

2. 手动提交事务

COMMIT;

3. 手动回滚事务

ROLLBACK;

方式二:显式开启事务

当你想手动控制事务时:

START TRANSACTION;
-- 或
BEGIN;

执行操作后:

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完全串行执行,无并发问题

隔离级别越高,数据越安全
并发性能越差


查看当前隔离级别

SELECT @@TRANSACTION_ISOLATION;

修改隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

级别可选:

READ UNCOMMITTED

READ COMMITTED

REPEATABLE READ

SERIALIZABLE


(六)事务隔离级别演示

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


1. 准备测试环境

执行以下 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)两个窗口均设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
(2)模拟过程

CMD1(修改但不提交)

START TRANSACTION;
UPDATE account SET balance = 500 WHERE id = 1;
-- 不提交事务

CMD2(读取未提交数据)

SELECT balance FROM account WHERE id = 1;

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

CMD1 回滚:

ROLLBACK;


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

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

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

CMD1:开启事务并读取数据

START TRANSACTION;
SELECT balance FROM account WHERE id = 1;   -- 结果:1000

CMD2:修改并提交

UPDATE account SET balance = 800 WHERE id = 1;
COMMIT;

CMD1:再次读取

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

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

CMD1 结束事务

COMMIT;


4.幻读(Phantom Read)演示

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

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

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

CMD1:第一次查询

START TRANSACTION;
SELECT * FROM orders WHERE amount > 100;  
-- 结果:3 行

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

INSERT INTO orders(amount) VALUES (500);
COMMIT;

CMD1:再次查询

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

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

CMD1 提交事务

COMMIT;


5. 验证最高隔离级别 Serializable

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

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

CMD1:

START TRANSACTION;
SELECT * FROM orders WHERE amount > 100;

CMD2(尝试插入新数据):

INSERT INTO orders(amount) VALUES (999);

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

在演示 MySQL 的事务隔离级别(如脏读、不可重复读、幻读等)时,将两个 CMD 窗口都设置为相同的隔离级别是完全可行的,也是教学和实验中最常用的做法。
严格来说,实际上只有涉及读取数据的窗口需要设置隔离级别即可,但为了保证实验环境的一致性、避免干扰和减少理解难度,建议在实际演示时,两侧窗口都显式地设置隔离级别。

http://www.dtcms.com/a/613519.html

相关文章:

  • 【Java架构师体系课 | MySQL篇】③ Explain执行计划详解
  • Bugku-web题目-xxx二手交易市场
  • 织梦 图片网站武冈 网站建设
  • WebForms Button:深入解析与最佳实践
  • 深度学习实战(基于pytroch)系列(二十)二维卷积层
  • 每日两道算法(2)
  • Ajax 数据请求:从 XMLHttpRequest 到现代前端数据交互的演进
  • Docker 容器连接
  • 手机网站的必要性建设网络平台 请示
  • Vue3 实现 12306 原版火车票组件:从像素级还原到自适应适配【源码】
  • 玄机-第八章 内存马分析-java03-fastjson
  • 人工智能算法优化YOLO的目标检测能力
  • 网站建设常用的编程语言apache设置网站网址
  • 漳州市网站建设费用p2p的网站开发
  • JAVA之二叉树
  • Gitee完全新手教程
  • 具身智能-8家国内外典型具身智能VLA模型深度解析
  • Go 边缘计算在智能汽车产业的应用
  • (五)自然语言处理笔记——迁移学习
  • 长春网站设计长春网络推广项目计划书包含哪些内容
  • ubuntu 25.10 安装Podman
  • 工业自动化核心系统与概念综述
  • 一步一步学习使用LiveBindings() TListView的进阶使用()
  • 全爱科技携智能计算解决方案亮相高交会
  • 建设部招标网站新闻型网站建设
  • MFC中使用GDI+ 自定义等待界面
  • 信息论(五):联合熵与条件熵
  • flume抽取kafka数据到kafka,数据无法从topicA抽取到topicB
  • 基于最小权限原则的云计算Amazon VPC多层应用安全架构设计
  • 11.2 FastGPT部署指南:Docker一键部署企业级RAG框架