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

使用场景

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

事务处理

  • 存储过程:可以控制事务(即事务的开始和结束)。这使得存储过程适合处理复杂的业务逻辑,其中可能需要回滚操作或提交多个步骤作为一个整体。
  • 函数:通常不处理事务。在函数中进行事务控制可能会导致不可预测的结果。
相关推荐
桂月二二4 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
Ai 编码助手5 小时前
在 Go 语言中如何高效地处理集合
开发语言·后端·golang
小丁爱养花5 小时前
Spring MVC:HTTP 请求的参数传递2.0
java·后端·spring
Channing Lewis5 小时前
什么是 Flask 的蓝图(Blueprint)
后端·python·flask
hunter2062066 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb6 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角6 小时前
CSS 颜色
前端·css
九酒6 小时前
从UI稿到代码优化,看Trae AI 编辑器如何帮助开发者提效
前端·trae
浪浪山小白兔7 小时前
HTML5 新表单属性详解
前端·html·html5
轩辕烨瑾7 小时前
C#语言的区块链
开发语言·后端·golang