深入 mysql,掌握子查询、EXISTS、事务、隔离级别、视图、存储过程和函数

子查询的概念与应用

子查询(即一个查询嵌套在另一个查询中)为 SQL 提供了更高的灵活性和强大的数据处理能力。

例如,我们可以在一个 SELECT 语句的 WHERE 子句中嵌套另一个 SELECT 语句。

示例 1:查询最高分学生的姓名和班级

sql 复制代码
SELECT name, class FROM student WHERE score = (SELECT MAX(score) FROM student);

示例 2:查询成绩高于平均成绩的学生

sql 复制代码
SELECT * FROM student WHERE score > (SELECT AVG(score) FROM student);

EXISTS 和 NOT EXISTS 子句

EXISTS 和 NOT EXISTS 是特殊的 SQL 操作符,用于测试子查询是否返回结果。

示例 1:查询有员工的部门

sql 复制代码
SELECT name FROM department WHERE EXISTS (SELECT * FROM employee WHERE department.id = employee.department_id);

示例 2:查询没有员工的部门

sql 复制代码
SELECT name FROM department WHERE NOT EXISTS (SELECT * FROM employee WHERE department.id = employee.department_id);

子查询在 DML 操作中的应用

子查询不仅可以在 SELECT 语句中使用,还可以在数据修改语句(如 INSERT、UPDATE、DELETE)中使用。

示例 1:插入平均价格数据

sql 复制代码
-- 向 avg_price_by_category 表插入数据
INSERT INTO avg_price_by_category (category, avg_price) 

-- 从 product 表中选择 category 和平均 price
SELECT category, AVG(price) 

-- 从 product 表中获取数据
FROM product 

-- 按照 category 字段分组
GROUP BY category;

示例 2:更新技术部员工的名称

sql 复制代码
-- 更新 employee 表中的数据
UPDATE employee 

-- 设置 name 字段的值,将 '技术-' 添加到原有 name 前
SET name = CONCAT('技术-', name) 

-- 指定更新条件:部门必须是 '技术部'
WHERE department_id = (SELECT id FROM department WHERE name = '技术部');

示例 3:删除技术部所有员工

sql 复制代码
DELETE FROM employee WHERE department_id = (SELECT id FROM department WHERE name = '技术部');

事务的基本概念与操作

事务 是一组原子性的 SQL 命令集合,要么全部执行,要么全部不执行。

当更新多个相关表时,使用事务可以保证整体数据的一致性。

基本语法

  • 开启事务: START TRANSACTION;
  • 提交事务: COMMIT; 数据永久保存,不可回滚。
  • 回滚事务: ROLLBACK; 数据回退到事务开始前的状态。

示例场景

假设需要更新订单详情表(order_items)和订单总金额表(orders)。

如果只更新了订单详情而未成功更新订单总金额,数据将不一致。此时,事务的应用非常关键。

sql 复制代码
START TRANSACTION;
UPDATE order_items SET quantity=1 WHERE order_id=3;
UPDATE orders SET total_amount=200 WHERE id=3;
COMMIT;

如果中途发现错误或执行失败,可以使用 ROLLBACK; 撤销所有更改。

事务的保存点(Savepoint)

在复杂的事务中,可能需要在特定步骤设置回滚点,以便在错误发生时只回滚到某个特定点:

设置保存点: SAVEPOINT savepoint_name;

回滚到保存点: ROLLBACK TO savepoint_name;

sql 复制代码
START TRANSACTION;
SAVEPOINT sp1;
UPDATE order_items SET quantity=1 WHERE order_id=3;
SAVEPOINT sp2;
UPDATE orders SET total_amount=200 WHERE id=3;
ROLLBACK TO sp2;  -- 回滚到 sp2 保存点
COMMIT;

事务的隔离级别

我们先来看看脏读、不可重复读和幻读,这三种通常在多个事务并发执行时发生,分别对应不同的隔离问题。

脏读(Dirty Read)

脏读发生在一个事务读取了另一个事务未提交的数据。如果这个未提交的事务最终被回滚(取消),那么第一个事务读到的数据就是"脏"的,因为这些数据从未被真正地保存过。

示例:

  • 事务 A 修改一条记录,然后暂停。
  • 事务 B 读取了事务 A 修改的同一条记录。
  • 如果事务 A 回滚更改,事务 B 读到的数据实际上是不存在的。

不可重复读(Non-repeatable Read)

不可重复读是指在同一事务中,多次读取同一数据集合时,由于其他事务的修改操作,导致后续的读取结果与初次读取不一致。

示例:

  • 事务 A 读取了一条记录。
  • 事务 B 更新了事务 A 刚刚读取的记录,然后提交。
  • 事务 A 再次读取同一记录时,发现数据已经改变。

不可重复读的问题主要由于更新(UPDATE)操作引起。

幻读(Phantom Read)

幻读问题涉及到在同一事务中执行相同的查询两次,但由于其他事务插入或删除了符合查询条件的记录,导致两次查询结果的行数不一致。

示例:

  • 事务 A 根据某个条件查询表中的多行数据。
  • 事务 B 在事务 A 的查询条件中插入新的行,然后提交。
  • 事务 A 再次使用相同的条件查询数据时,发现多出了一些行。

幻读主要由插入(INSERT)或删除(DELETE)操作引起,与不可重复读类似,但更关注于整个数据集的一致性,而不仅仅是单条记录的一致性。

事务隔离级别

事务隔离级别定义了一个事务可能受其他并发事务影响的程度。

MySQL 支持四种隔离级别:

  • READ UNCOMMITTED(未提交读): 可以读取未提交的数据,存在脏读和不可重复读问题。
  • READ COMMITTED(提交读): 只能读取已提交的数据,避免脏读,但不可重复读和幻读问题仍然存在。
  • REPEATABLE READ(可重复读): 保证在同一事务中多次读取的数据一致,MySQL 默认级别,解决了不可重复读,但可能有幻读。
  • SERIALIZABLE(可串行化): 最高隔离级别,事务串行执行,避免了所有读取问题,但并发性能较差。

查询当前事务隔离级别:

sql 复制代码
SELECT @@transaction_isolation;

设置事务隔离级别:

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

在 MySQL 数据库系统中,连接到服务器后,可以访问其管理的多个数据库。

每个数据库可能包括以下组成部分:

  1. 表:用于存储数据的基本结构。
  2. 视图:基于表的查询结果集,用于简化和定制数据访问。
  3. 存储过程:用于执行一系列 SQL 语句的预编译代码块,可以接受参数并在数据库服务器上执行。
  4. 函数:执行运算并返回一个值的 SQL 语句集。

视图的创建与应用

视图是基于 SQL 查询创建的虚拟表,主要用于简化复杂的 SQL 查询。

例如,使用以下 SQL 语句基于 customers 和 orders 表创建一个视图 customer_orders:

sql 复制代码
-- 创建一个名为customer_orders的视图
CREATE VIEW customer_orders AS 
    -- 选择以下列来定义视图的内容
    SELECT 
        c.name AS customer_name,  -- 从customers表中选择name列,并将其重命名为customer_name
        o.id AS order_id,         -- 从orders表中选择id列,并将其重命名为order_id
        o.order_date,             -- 选择orders表中的order_date列
        o.total_amount            -- 选择orders表中的total_amount列
    FROM customers c              -- 指定从customers表中选择数据,给这个表指定一个别名c
    JOIN orders o                 -- 与orders表进行连接操作,给这个表指定一个别名o
    ON c.id = o.customer_id;      -- 定义连接条件,即customers表的id列与orders表的customer_id列相匹配

优点:

  • 简化查询:用户可以通过简单的 SELECT * FROM customer_orders 查询复杂的数据集。
  • 权限控制:通过视图可以限制对基础数据的访问,只展示必要的数据。

限制:

  • 视图主要用于数据查询,对于数据的增删改操作有限制,通常只适用于包含单一数据表的视图。

存储过程的创建与调用

存储过程是一种在数据库中保存的程序,可以优化常用的或复杂的操作。

下面是一个名为 get_customer_orders 的存储过程,用于查询特定客户的所有订单:

sql 复制代码
-- 设置SQL语句的分隔符为美元符号$
DELIMITER $

-- 创建一个名为get_customer_orders的存储过程
CREATE PROCEDURE get_customer_orders(IN customer_id INT)
BEGIN
    -- 存储过程的内容是执行一个SELECT查询
    SELECT o.id AS order_id,       -- 选择orders表的id列,并将其重命名为order_id
           o.order_date,           -- 选择orders表中的order_date列
           o.total_amount          -- 选择orders表中的total_amount列
    FROM orders o                  -- 指定从orders表中选择数据,并给这个表指定一个别名o
    WHERE o.customer_id = customer_id;  -- 查询条件是orders表中的customer_id列等于传入的customer_id参数
END $
-- 恢复SQL语句的分隔符为分号;
DELIMITER ;

使用方法:

  • 存储过程通过 CALL get_customer_orders(5); 调用,其中 5 是客户 ID。

函数的创建与应用

函数是可以返回单个值的 SQL 程序。例如,创建一个计算平方的函数 square:

sql 复制代码
DELIMITER $ 
-- 设置语句的分隔符为$,这是为了将整个CREATE FUNCTION语句作为一个整体执行,特别是当函数体内有多条语句时。

CREATE FUNCTION square(x INT) RETURNS INT
-- 创建一个名为square的函数,这个函数接收一个整型参数x,并返回一个整型值。这个返回值是参数x的平方。

BEGIN
    -- BEGIN和END之间是函数的主体部分。

    DECLARE result INT;
    -- 声明一个名为result的整型变量,用于存储计算结果。

    SET result = x * x;
    -- 计算参数x的平方,并将结果赋值给变量result。

    RETURN result;
    -- 返回变量result的值,即x的平方。

END $
-- 函数主体结束。

DELIMITER ;
-- 将语句的分隔符重置为默认的分号(;)。

在 MySQL 中,默认情况下不允许创建函数,需要先设置:

sql 复制代码
SET GLOBAL log_bin_trust_function_creators = 1;

实际应用:

  • 函数可以用于查询,例如 SELECT product_name, square(price) FROM order_items_view;。
  • 创建复杂函数,如 get_order_total,用于计算订单的总金额:
sql 复制代码
-- 设置语句结束符为 $,这是为了在创建函数时,可以把整个函数体看作是一个语句
DELIMITER $

-- 创建一个名为 get_order_total 的函数,这个函数接收一个整型参数 order_id
CREATE FUNCTION get_order_total(order_id INT) RETURNS DECIMAL(10,2)
BEGIN
    -- 声明一个名为 total 的变量,用于存储计算出的订单总额,数据类型为 DECIMAL(10,2),即最多10位数,其中小数点后2位
    DECLARE total DECIMAL(10,2);
    
    -- 从 order_items 表中选择与传入的 order_id 相匹配的所有条目,计算它们的 quantity(数量)与 price(价格)之积的总和
    -- 并将这个总和赋值给之前声明的 total 变量
    SELECT SUM(quantity * price) INTO total FROM order_items WHERE order_id = order_id;
    
    -- 返回计算出的订单总额
    RETURN total;
END $

-- 恢复语句结束符为 ;,即将 DELIMITER 设置回默认的 ;
DELIMITER ;

调用方法:

  • 通过 SELECT id, get_order_total(id) FROM orders; 来获取订单的总金额。

存储过程和函数的区别

目的和用途

  • 存储过程:主要用于执行动作,如数据插入、更新、删除等。它可以执行一系列复杂的 SQL 语句,并进行事务管理。
  • 函数:主要用于计算并返回一个值。函数通常用于在 SQL 语句中进行计算,如转换数据、计算数值等。

返回值

  • 存储过程:可以返回零个、一个或多个值(通常通过输出参数或结果集),也可以不返回任何值。
  • 函数:必须返回一个值,这是其定义的一部分。

调用方式

  • 存储过程:使用 CALL 语句调用。
  • 函数:可以直接在 SQL 语句中调用,如在 SELECT、WHERE 或 CASE 等语句中。

使用场景

  • 存储过程:适用于需要执行多个步骤的业务逻辑,或当需要使用循环、条件语句等复杂控制结构时。
  • 函数:适用于需要在查询中重复使用的计算。由于函数必须是确定性的或不依赖于外部状态,它们在数据处理中非常有用。

事务处理

  • 存储过程:可以控制事务(即事务的开始和结束)。这使得存储过程适合处理复杂的业务逻辑,其中可能需要回滚操作或提交多个步骤作为一个整体。
  • 函数:通常不处理事务。在函数中进行事务控制可能会导致不可预测的结果。
相关推荐
小五Five几秒前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序几秒前
vue3 封装request请求
java·前端·typescript·vue
临枫5411 分钟前
Nuxt3封装网络请求 useFetch & $fetch
前端·javascript·vue.js·typescript
前端每日三省2 分钟前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript
小刺猬_9853 分钟前
(超详细)数组方法 ——— splice( )
前端·javascript·typescript
渊兮兮4 分钟前
Vue3 + TypeScript +动画,实现动态登陆页面
前端·javascript·css·typescript·动画
鑫宝Code5 分钟前
【TS】TypeScript中的接口(Interface):对象类型的强大工具
前端·javascript·typescript
爱吃青椒不爱吃西红柿‍️12 分钟前
华为ASP与CSP是什么?
服务器·前端·数据库
一棵开花的树,枝芽无限靠近你16 分钟前
【PPTist】添加PPT模版
前端·学习·编辑器·html
凡人的AI工具箱16 分钟前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang