Mysql从入门到入魔——8. 视图、存储过程、事务处理

x33g5p2x  于2021-12-19 转载在 其他  
字(8.4k)|赞(0)|评价(0)|浏览(336)

本篇主要内容

为什么使用视图、视图的创建、修改和删除;为什么使用存储过程,三种参数在存储过程中的使用;如何进行事务处理,以及事务隔离级别的种类及各自的缺陷。话不多说,进入正题!

1.视图

1.1 为什么使用视图

视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态检索 数据的查询。

视图的作用:

  • 重用 SQL 语句。
  • 简化复杂的 SQL 操作。在编写查询后,可以方便地重用它而不必知道 其基本查询细节。
  • 使用表的一部分而不是整个表。
  • 保护数据。可以授予用户访问表的特定部分的权限,而不是整个表的 访问权限。
  • 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。

创建视图之后,可以用与表基本相同的方式使用它们。可以对视图执行 SELECT 操作,过滤和排序数据,将视图联结到其他视图或表,甚至添加和更新数据。

1.2 创建视图

CREATE VIEW 语句来创建视图。详细使用方式见下节。

1.3 用视图简化复杂的联结

一个最常见的视图应用是隐藏复杂的 SQL,这通常涉及联结。

现在创建一个名为 ProductCustomers 的视图,它联结三个表,返回已订购了任意产品的所有顾客的列表。

mysql> CREATE VIEW ProductCustomers AS
    -> SELECT cust_name, cust_contact, prod_id
    -> FROM Customers, Orders, OrderItems
    -> WHERE Customers.cust_id = Orders.cust_id
    ->  AND OrderItems.order_num = Orders.order_num;

通过该视图,查询订购了产品 RGAN01 的顾客。

mysql> SELECT cust_name, cust_contact
    -> FROM ProductCustomers
    -> WHERE prod_id = 'RGAN01';
+---------------+--------------------+
| cust_name     | cust_contact       |
+---------------+--------------------+
| Fun4All       | Denise L. Stephens |
| The Toy Store | Kim Howard         |
+---------------+--------------------+

可以看出,视图极大地简化了复杂 SQL 语句的使用。

1.4 用视图过滤数据

可以定义 CustomerEMailList 视图,过滤没有电子邮件地址的顾客。

mysql> CREATE VIEW CustomerEMailList AS
    -> SELECT cust_id, cust_name, cust_email
    -> FROM Customers
    -> WHERE cust_email IS NOT NULL;

现在,可以像使用其他表一样使用视图 CustomerEMailList

mysql> SELECT *
    -> FROM CustomerEMailList;
+------------+--------------+-----------------------+
| cust_id    | cust_name    | cust_email            |
+------------+--------------+-----------------------+
| 1000000001 | Village Toys | sales@villagetoys.com |
| 1000000003 | Fun4All      | jjones@fun4all.com    |
| 1000000004 | Fun4All      | dstephens@fun4all.com |
+------------+--------------+-----------------------+

1.5 修改视图

使用 ALTER VIEW 语句来修改视图,修改时要保证视图存在。

现在同样过滤没有电子邮件地址的顾客,但只返回 cust_id 列。

mysql> ALTER VIEW CustomerEMailList AS
    -> SELECT cust_id
    -> FROM Customers
    -> WHERE cust_email IS NOT NULL;

1.6 删除视图

使用 DROP VIEW 语句来删除视图,删除时要保证视图存在。

DROP VIEW CustomerEMailList;

1.7 性能问题

因为视图不包含数据,所以每次使用视图时,都必须处理查询执行时需要的所有检索。如果你用多个联结和过滤创建了复杂的视图或者嵌 套了视图,性能可能会下降得很厉害。因此,在部署使用了 大量视图 的应用前,应该进行测试。

2.存储过程

2.1 为什么使用存储过程

优点:

  • 通过把 “某个处理” 封装在一个易用的单元中,可以简化复杂的操作
  • 由于不要求反复建立一系列处理步骤,因而保证了数据的一致性。
  • 简化对变动的管理。如果表名、列名或业务逻辑(或别的内容)有变化,那么只需要更改存储过程的代码。使用它的人员甚至不需要知道这些变化。
  • 因为存储过程通常以编译过的形式存储,所以 DBMS 处理命令所需的工作量少,提高了性能。

简单来说,使用存储过程有三个主要的好处:简单、安全、高性能。

2.2 创建存储过程

我们来看一个简单的存储过程例子,对具有邮件地址的顾客进行计数。

mysql> DELIMITER $$
mysql> CREATE PROCEDURE MailingListCount()
    -> BEGIN
    ->     SELECT COUNT(*) AS cust_count
    ->     FROM Customers
    ->     WHERE cust_email IS NOT NULL;
    -> END$$

在定义过程时,使用 DELIMITER $$ 命令将语句的结束符号从分号 ; 临时改为 $$,使得过程体中使用的分号被直接传递到服务器,而不会被 Mysql 解释。

使用 CALL 存储过程名(参数) 来调用存储过程。

mysql> DELIMITER ;
mysql> CALL MailingListCount();
+------------+
| cust_count |
+------------+
|          3 |
+------------+

2.3 存储过程体

存储过程体包含了在过程调用时必须执行的语句,例如: dml、ddl 语句,if-then-elsewhile-do 语句、声明变量的declare 语句等。

过程体格式:以 begin 开始,以 end 结束(可嵌套)

BEGIN
  BEGIN
    BEGIN
      statements; 
    END
  END
END

注意:每个嵌套块及其中的每条语句,必须以分号结束,表示过程体结束的 begin-end 块(又叫做复合语句 compound statement ),则不需要分号。

2.4 存储过程参数

存储过程可以有 0 个或多个参数,用于存储过程的定义。

3种参数类型:

  • IN (输入参数):表示调用者向过程传入值(传入值可以是字面量或变量)
  • OUT (输出参数):表示过程向调用者传出值(可以返回多个值)(传出值只能是变量)
  • INOUT (输入输出参数):既表示调用者向过程传入值,又表示过程向调用者传出值(值只能是变量)

2.4.1 IN输入参数

mysql> DELIMITER $$
mysql> CREATE PROCEDURE in_test(IN p_in INT)
    -> BEGIN
    ->     SELECT p_in;    -- 第一次查询
    ->     SET p_in = 2;   -- 修改p_in的值
    ->     SELECT p_in;    -- 第二次查询
    -> END$$
    
mysql> DELIMITER ;

mysql> SET @p_in = 1;

mysql> CALL in_test(@p_in);
+------+
| p_in |
+------+
|    1 |
+------+

+------+
| p_in |
+------+
|    2 |
+------+

mysql> SELECT @p_in;
+-------+
| @p_in |
+-------+
|     1 |
+-------+

虽然 p_in 在存储过程中被修改,但并不影响 @p_in 的值,因为 in_test 只接受输入参数,并不输出参数,所以相当于在函数内改变,但并未将这个值输出给 @p_in

2.4.2 OUT输入参数

mysql> DELIMITER $$
mysql> CREATE PROCEDURE out_test(OUT p_out INT)
    -> BEGIN
    ->     SELECT p_out;    -- 第一次查询
    ->     SET p_out = 2;   -- 修改p_out的值
    ->     SELECT p_out;    -- 第二次查询
    -> END$$

mysql> DELIMITER ;

mysql> SET @p_out = 1;

mysql> CALL out_test(@p_out);
+-------+
| p_out |
+-------+
|  NULL |
+-------+

+-------+
| p_out |
+-------+
|     2 |
+-------+

mysql> SELECT @p_out;
+--------+
| @p_out |
+--------+
|      2 |
+--------+

第一个返回结果为 NULL 是因为 OUT 是向调用者输出参数,不接收输入的参数,所以第一次查询时 p_out 还未赋值,所以是 NULL 。最后 @p_out 变量的值变为2是因为调用了 out_test 存储过程,输出参数,改变了 p_out 变量的值。

2.4.3 INOUT输入输出参数

mysql> DELIMITER $$
mysql> CREATE PROCEDURE inout_test(INOUT p_inout INT)
    -> BEGIN
    ->     SELECT p_inout;    -- 第一次查询
    ->     SET p_inout = 2;   -- 修改p_inout的值
    ->     SELECT p_inout;    -- 第一次查询
    -> END$$
    
mysql> DELIMITER ;

mysql> SET @p_inout = 1;

mysql> CALL inout_test(@p_inout);
+---------+
| p_inout |
+---------+
|       1 |
+---------+

+---------+
| p_inout |
+---------+
|       2 |
+---------+

mysql> SELECT @p_inout;
+----------+
| @p_inout |
+----------+
|        2 |
+----------+

调用 inout_test 存储过程,既接受了输入的参数,也输出参数, @p_inout 的值被改变。

2.5 删除存储过程

使用 DROP PROCEDURE 来删除存储过程。

mysql> DROP PROCEDURE in_test;

3.管理事务处理

3.1 事务处理

事务处理(transaction processing)是一种机制, 用来管理必须成批执行的SQL` 操作,保证数据库不包含不完整的操作结果。利用事务处理,可以保证一组操作不会中途停止,它们要么完全执行,要么完全不执行(除非明确指示)。如果没有错误发生,整组语句提交给(写到)数据库表;如果发生错误,则进行回退(撤销),将数据库恢复到某个已知且安全的状态,以此来维护数据库的完整性。

事务处理术语:

  • 事务(transaction):指一组 SQL 语句;
  • 回退(rollback):指撤销指定 SQL 语句的过程;
  • 提交(commit):指将未存储的 SQL 语句结果写入数据库表;
  • 保留点(savepoint):指事务处理中设置的临时占位符(placeholder), 可以对它发布回退(与回退整个事务处理不同)。

事务处理用来管理 INSERTUPDATEDELETE 语句。不能回退 SELECT 语句(回退 SELECT 语句也没有必要),也不能回退 CREATEDROP 操 作。事务处理中可以使用这些语句,但进行回退时,这些操作也不撤销。

一般来说,事务是必须满足4个条件( ACID )::原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

  • 原子性: 一个事务( transaction )中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚( Rollback )到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性: 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
  • 隔离性: 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化( Serializable)。
  • 持久性: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
    在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显式地开启一个事务务须使用命令 BEGIN 或 START TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。

3.2 控制事务处理

  • BEGIN / START TRANSACTION :显式地开启一个事务;
  • COMMIT / COMMIT WORK :提交事务,使已对数据库进行的所有修改成为永久性的;
  • ROLLBACK / ROLLBACK WORK :回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;
  • SAVEPOINTSAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT
  • RELEASE SAVEPOINT :删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
  • ROLLBACK TO :把事务回滚到标记点;
  • SET TRANSACTION :用来设置事务的隔离级别。InnoDB 存储引擎提供事务的隔离级别有 READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交,项目中常用)、REPEATABLE READ(可重复读,Mysql 默认隔离级别) 和 SERIALIZABLE(串行化)。

简单示例:

mysql> use test;

mysql> CREATE TABLE transaction_test(id int(5)) ENGINE = INNODB;  # 创建数据表
 
mysql> SELECT * FROM transaction_test;
Empty set (0.01 sec)

mysql> BEGIN;  # 开始事务
 
mysql> INSERT INTO runoob_transaction_test VALUE(1);
 
mysql> INSERT INTO runoob_transaction_test VALUE(2);
 
mysql> COMMIT; # 提交事务
 
mysql>  SELECT * FROM transaction_test;
+------+
| id   |
+------+
| 1    |
| 2    |
+------+


mysql> BEGIN;    # 开始事务

mysql> INSERT INTO transaction_test VALUES(3);

mysql> SAVEPOINT first_insert;    # 声明一个保存点

mysql> INSERT INTO transaction_test VALUES(4);

mysql> SAVEPOINT second_insert;    # 声明一个保存点

mysql> INSERT INTO transaction_test VALUES(5);

mysql> ROLLBACK TO second_insert;    # 回滚到 second_insert 保存点

mysql> SELECT * FROM transaction_test;    # 因为回滚所以数据没有插入
+------+
| id   |
+------+
|    1 |
|    2 |
|    3 |
|    4 |
+------+

mysql> ROLLBACK TO first_insert;

mysql> SELECT * FROM transaction_test;
+------+
| id   |
+------+
|    1 |
|    2 |
|    3 |
+------+

mysql> COMMIT;    # 执行 COMMIT 或 ROLLBACK 后保存点自动释放

3.3 事务隔离级别

先看一下如何查看事务隔离级别。

# 查看默认事务隔离级别(session)
select @@transaction_isolation;
# 查看当前会话的隔离级别
select @@session.transaction_isolation;
# 查看全局的事务隔离级别
select @@global.transaction_isolation;

Mysql 下事务的隔离级别有四种,由低到高依次为 Read uncommittedRead committedRepeatable read (默认)、Serializable ,这四个级别中的后三个级别可以逐个解决脏读不可重复读幻读的问题。

3.3.1 脏读

对于两个事务T1与T2,T1读取了已经被T2更新但是还没有提交的字段之后,若此时T2回滚,T1读取的内容就是临时并且无效的。

示例:

打开两个Mysql客户端,分别执行下面操作,查询当前会话的隔离级别(默认 REPEATABLE READ )。修改当前会话隔离级别为( READ UNCOMMITTED )。全局事务隔离级别仍然为 REPEATABLE READ

mysql> SELECT @@session.transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ         |
+-------------------------+

mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;    # 修改会话隔离级别

mysql> SELECT @@session.transaction_isolation;    # 当前会话隔离级别已修改
+---------------------------------+
| @@session.transaction_isolation |
+---------------------------------+
| READ-UNCOMMITTED                |
+---------------------------------+

mysql> SELECT @@global.transaction_isolation;    # 全局事务隔离级别未修改
+--------------------------------+
| @@global.transaction_isolation |
+--------------------------------+
| REPEATABLE-READ                |
+--------------------------------+

之后黑框用来做更新,白框用来查询。

|

|
| ------------ |

由于黑框的 ④ 回滚,白色背景的客户端中 ③ 读取的数据就是临时并且无效的。即脏读。

3.3.2 不可重复读

对于两个事务T1和T2,T1读取了一个字段,然后T2更新了该字段并提交之后,当T1再次读取的时候,结果不一致的情况发生。

|

|
| ------------ |

由于黑框的更新操作,白框出现两次读取的结果不一致。

3.3.3 幻读

对于两个事务T1、T2,T1从表中读取数据,然后T2进行了INSERT操作并提交,当T1再次读取的时候,结果不一致的情况发生。

|

|
| ------------ |

由于黑框的插入操作,白框出现两次读取的结果不一致。

几种现象在各隔离级别中出现的可能性

隔离级别脏读(Dirty Read)不可重复读(NonRepeatable Read)幻读(Phantom Read)
未提交读(Read uncommitted)可能可能可能
已提交读(Read committed)不可能可能可能
可重复读(Repeatable read)不可能不可能可能
可串行化(Serializable )不可能不可能不可能

参考链接:
MySQL存储过程的创建及调用
MySQL事务(脏读、不可重复读、幻读)

相关文章