深入 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 等语句中。

使用场景

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

事务处理

  • 存储过程:可以控制事务(即事务的开始和结束)。这使得存储过程适合处理复杂的业务逻辑,其中可能需要回滚操作或提交多个步骤作为一个整体。
  • 函数:通常不处理事务。在函数中进行事务控制可能会导致不可预测的结果。
相关推荐
我要洋人死34 分钟前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
monkey_meng1 小时前
【Rust中的迭代器】
开发语言·后端·rust
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
余衫马1 小时前
Rust-Trait 特征编程
开发语言·后端·rust
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
monkey_meng1 小时前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书