快速回顾 MySQL:游标、触发器和事务处理

前提要述:参考书籍《MySQL必知必会》

16.1 游标

游标(cursor)是一个存储在MySQL服务器上的数据库查询,它不是一条SELECT语句,而是被该语句检索出来的结果集。

有时,需要在检索出来的行中前进或后退一行或多行,这就游标的用处。在存储了游标之后,应用程序可以根据需要滚动或者浏览其中的数据。

不像多数DBMS,MySQL游标只能用于存储过程(和函数)。

16.1.1 创建游标

游标使用DECLARE语句创建。DECLARE命名游标,类型固定为CURSOR,并定义相应的SELECT语句,根据需要带WHERE和其他子句。

检索所有订单的SELECT语句:先别创建下面的这个,现在只是演示定义

CREATE PROCEDURE processorders()
BEGIN
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;
END;

16.1.2 使用游标

使用游标涉及几个步骤:

  • 在能够使用游标前,必须声明它。但是还没有检索数据。
  • 一旦声明后,必须打开游标以供使用。就会把数据实际检索出来。
  • 对于填有数据的游标,根据需要取出各行。
  • 在结束游标使用时,必须关闭游标。

注意:下面这些开启和关闭都是在存储过程中开启和关闭的,而不是先创建带有游标的存储过程,再去开启和关闭。

打开游标使用OPEN关键字:

OPEN processorders;

关闭游标使用OPEN关键字:

CLOSE processorders;

在关闭时,会释放游标使用的所有内部内存和资源。如果不明确的关闭游标,MySQL将会在到达END语句时自动关闭它。

举第一个例子:从游标中检索单个行:创建完没显示什么,不急。

CREATE PROCEDURE processorders()
BEGIN
    -- 定义一个变量
    DECLARE o INT;
    -- 创建游标
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;
    -- 开启游标
    OPEN ordernumbers;
    -- 检索行
    FETCH ordernumbers INTO o;
    -- 关闭游标
    CLOSE ordernumbers;
END;

解释:
在一个游标被打开后,可以使用FETCH语句分别访问它的每一行。FETCH指定检索什么数据(所需的列),检索出来的数据存储在什么地方。它还向前移动游标中的内部行指针,使得下一条FETCH语句检索下一行(不重复读取同一行。)

第二个例子:循环检索数据:

CREATE PROCEDURE processorders()
BEGIN
    -- 定义变量
    DECLARE done BOOLEAN DEFAULT 0;
    DECLARE o INT;
    -- 创建游标
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;
    -- 规定循环的终止条件,使得done为真
    DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
    -- 开启游标
    OPEN ordernumbers;
    -- 循环开始
    REPEAT
        -- 检索行
        FETCH ordernumbers INTO o;
    -- 每次判断是否到达终止条件    
    UNTIL done END REPEAT;
    -- 关闭游标
    CLOSE ordernumbers;
END;

解释:这个例子中的FETCH是在REPEAT内,因此它反复执行直到done为真(由UNTIL done END REPEAT;规定)。并且先把变量done默认值设为0,表示假(false)。而定义下面的语句来使得done为1,即真:

DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;

该语句定义了 CONTINUE HANDLER ,它是在条件出现时被执行的代码。这里,它指出SQLSTATE '0200’出现时,SET done=1。而SQLSTATE '0200’是一个未找到的条件,当REPEAT由于没有更多的行供循环而不能继续时,出现这个条件。

对于DECLARE语句定义的局部变量必须在定义任意游标或句柄(句柄就是上面那句CONTINUE HANDLER)之前定义,而句柄必须在游标之后定义。不遵守此顺序会报错。

上面的只是演示,并不能看到实际效果。下面给个实际效果:

CREATE PROCEDURE processorders()
BEGIN
    -- 定义变量
    DECLARE done BOOLEAN DEFAULT 0;
    DECLARE o INT;
    DECLARE t DECIMAL(8,2);
    -- 创建游标
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;
    -- 规定循环的终止条件,使得done为真
    DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
    -- 创建一个存储结果数据的表
    CREATE TABLE IF NOT EXISTS ordertotals
        (order_num INT, total DECIMAL(8,2));
    -- 开启游标
    OPEN ordernumbers;
    -- 循环开始
    REPEAT
        -- 检索行
        FETCH ordernumbers INTO o;
        -- 调用之前是否含有营业税的合计的存储过程
        CALL ordertotal(o, 1, t);
        -- 把结果数据插入刚刚创建的表
        INSERT INTO ordertotals(order_num, total) VALUES(o, t);
    -- 每次判断是否到达终止条件    
    UNTIL done END REPEAT;
    -- 关闭游标
    CLOSE ordernumbers;
END;

解释:此存储过程不返回结果,但能创建和填充另一个表,名为ordertotals。该表将保存存储过程生成的结果。FETCH取每个order_num,然后用CALL执行另一个存储过程(前面存储过程的章节最后创建的)来计算每个订单的带税的合计(结果存储到t)。最后,用INSERT保存每个订单的订单号和合计。

CALL processorders();
SELECT * FROM ordertotals;

小结:

  • 可以看出该过程确实有点难写,而且很容易写错。游标的用途之一就是上面最后一个实例,也可以用来把一个表的数据复制到另一个表(假设表中还有其他不同的字段)中(假设有很多条数据,使用普通的INSERT一条一条执行插入是非常慢的。前面也说了,存储过程比单独使用SQL语句执行快,现在加上游标,就可以循环插入)。
  • 先定义游标(DECLARE … CURSOR FOR sql语句),再启动(OPEN …),最后再关闭(CLOSE …)。

16.2 触发器

如果你想要某条语句在事件发生(比如插入、删除等操作)时自动执行,要怎么办?这就需要触发器。触发器是MySQL响应以下任意语句而自动执行的一条MySQL语句(或位于BEGIN和END语句之间的一组语句):

  • DELETE;
  • INSERT;
  • UPDATE。

其他MySQL语句不支持触发器。

16.2.1 创建触发器

在创建触发器,需要给出4条信息:

  • 唯一的触发器名;
  • 触发器关联的表;
  • 触发器应该响应的活动(DELETE、INSERT或UPDATE);
  • 触发器何时执行(处理之前或处理之后)。

触发器使用CREATE TRIGGER语句创建,比如:

CREATE TRIGGER newproduct AFTER INSERT ON products
FOR EACH ROW SELECT 'Product added';

解释:触发器可在一个操作之前或之后执行,这里出给了AFTER INSERT,所以此触发器将在INSERT语句成功执行后执行。这个触发器还这指定FOR EACH NOW,因此代码对每个插入的行执行。所以,这个例子的意思是:文本Product added将对products表每个插入的行显示一次。

只有表才支持触发器,视图不支持(临时表也不支持)。

小结创建触发器所需的关键字:

  • CREATE TRIGGER 唯一触发器名
  • AFTER INSERT ON table_name:在插入成功后执行
  • BEFORE INSERT ON table_name:在插入成功之前执行
  • 还有其他的DELECT、UPDATE也是上面的写法
  • FOR EACH ROW:对每个插入的行执行
  • 最后跟上SQL语句。

触发器按每个表每个事件每次地定义,每个表每个事件每次只允许一个触发器。因此,可得每个表最多支持6个触发器(INSERT、UPDATE、DELETE,三个的之前和之后)。单一的触发器不能于多个事件或多个表关联。

注意:如果BEFORE触发器失败(执行之前),则MySQL将不执行请求的操作。此外,如果BEFORE触发器或语句本身失败,MySQL将不执行AFTER触发器(如果有的话)。

16.2.2 删除触发器

使用DROP TRIGGER语句删除:

DROP TRIGGER newproduct;

注意:触发器不能更新或覆盖。所以要修改触发器,必须先删除。


下面介绍几种类型的触发器。

16.2.3 INSERT触发器

需要注意以下几点:

  • 在INSERT触发器代码内,可引用一个名为NEW的虚拟表,访问被插入的行。
  • 在BEFORE INSERT触发器中,NEW中的值也可以被更新(允许更改被插入的值)。
  • 对于AUTO_INCREMENT列,NEW在INSERT执行之前包含0,在INSERT执行之后包含新的自动生成值。

例子:下面是基于MySQL版本5.0的写法,5.0之后按下面的写法会报错。

CREATE TRIGGER neworder AFTER INSERT ON orders
FOR EACH ROW SELECT NEW.order_num;

执行上面语句会报错:Not allowed to return a result set from a trigger

解决方法:加上 into @ee,因为从MySQL5以后不支持触发器返回结果集,即存储在临时变量中,还可以调用。

即:

CREATE TRIGGER neworder AFTER INSERT ON orders
FOR EACH ROW SELECT NEW.order_num into @ee, @ee;

CREATE TRIGGER newproduct AFTER INSERT ON products
FOR EACH ROW SELECT ‘Product added’ INTO @ee;
解释:创建一个名为neworder的触发器,按照AFTER INSERT ON orders执行。在插入一个新订单到orders表之后,MySQL生成一个新的订单号并保存到order_num中。触发器从NEW.order_num取得这个值并返回它。对于NEW的虚拟表,必须是AFTER INSERT执行,因为在BEFORE INSERT语句执行之前,新order_num还没生成。

使用:

INSERT INTO orders(order_date, cust_id)
VALUES(Now(), 10001);

因为触发器把结果存储在变量中,所以插入时显示不出什么,得使用:

SELECT @ee;

输出:

+-------+
| @ee   |
+-------+
| 20012 |
+-------+
1 row in set (0.06 sec)

通常,将BEFORE用于数据验证和净化(目的是保证插入表中的数据确实是需要的数据)。UPDATE也一样。

16.2.3 DELETE触发器

需要知道以下几点:

  • 在DELETE触发器代码内,可以引用一个名为OLD的虚拟表,访问被删除的行。
  • OLD中的值全都是只读的,不能更新。

例子:使用OLD保存将要被删除的行道一个存档表中:

CREATE TRIGGER deleteorder BEFORE DELETE ON orders
FOR EACH ROW
BEGIN
    INSERT INTO archive_orders(order_num, order_date, cust_id) VALUES(OLD.order_num, OLD.order_date, OLD.cust_id);
END;

解释:在任意订单被删除前将执行此触发器。使用一条INSERT语句将OLD中的值(要被删除的订单)保存道一个名为archive_orders的存档表中(需要手动创建与orders相同的列的archive_orders的表)

使用BEFORE DELETE触发器的优点:如果由于某种原因,订单不能存档,DELETE本身将被放弃。

使用BEGIN END的好处是触发器能容纳多条SQL语句(在BEGIN END块中一条挨着一条)。

16.2.4 UPDATE触发器

需要知道以下几点:

  • 在UPDATE触发器代码内,可以引用一个名为OLD的虚拟表访问修改前的值,引用一个名为NEW的虚拟表访问更新后的值。
  • 在BEFORE UPDATE触发器中,NEW中的值可能也被更新(允许更改将要用于UPDATE语句中的值)。
  • OLD中的值全都是只读的,不能更新。

下面的例子保证州名缩写总是大写:

CREATE TRIGGER updatevendor BEFORE UPDATE ON vendors
FOR EACH ROW SET NEW.vend_state = UPPER(NEW.vend_state);

任何数据净化都需要在UPDATE语句之前进行。像这个例子,每次更新一个行时,NEW.vend_state中的值(将用来更新表行的值)都用UPPER(NEW.vend_state)替换。


小结:

  • 与其他DBMS相比,MySQL 5中支持的触发器相当初级。5以后会加强。
  • 创建触发器可能需要特殊的安全访问权限,但是,触发器的执行是自动的。如果INSERT、UPDATE、DELETE语句能够执行,则相关的触发器也能执行。
  • 应该使用触发器来保证数据的一致性(大小写、格式等)。而且是透明进行的,与客户机应用无关。
  • 触发器是创建审计跟踪。使用触发器,把更改记录到另一表很容易。就像上面的例子。
  • MySQL触发器中不支持CALL语句(5.0版本)。这表示不能从触发器内调用存储过程。所需的存储过程代码需要复制到触发器内。

16.3 事务处理

说到事务,就要知道引擎。使用InnoBD是支持事务处理的。

事务处理(transaction processing)可以用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行,要么完全不执行。

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

  • 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

  • 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

事务的几个术语:

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

最常说的例子:就是转账时,假设我支付宝转账给我朋友100块,那么我余额就会少掉100块,而我朋友的余额就会多出一百块。而在该过程中,因为系统某些原因,我转账后我的朋友没有收到钱,而我的余额已经少掉100块了,如果没有事务的处理,那么过程就是这样,我的100块都不知道去哪了;如果使用事务,那么这就属于转账失败,那么我的100块会回到我的余额中。而朋友也不会收到我的100块。

16.3.1 使用事务处理

管理事务处理的关键在于将SQL语句组分解为逻辑块,并明确规定数据何时应该回退,何时不应该回退。

MySQL使用下面的语句来标识一个事务的开始:

START TRANSACTION;

1. 使用ROLLBACK

MySQL的ROLLBACK命令用来回退MySQL语句,例子:

SELECT * FROM ordertotals;
START TRANSACTION;
DELETE FROM ordertotals;
SELECT * FROM ordertotals;
ROLLBACK;
SELECT * FROM ordertotals;

解释:使用ROLLBACK语句回退START TRANSACTION之后的所有语句。所以,第一次查询是有数据,第二次删除表数据后再查询就没有数据,之后回退,再查询,又有数据。

事务处理用来管理INSERT、UPDATE和DELETE语句,不能回退SELECT语句。也不能回退CREATE或DROP操作,但在事务处理块中可以使用这两条语句,但不会回退。

2. 使用COMMIT

一般的MySQL语句都是直接针对数据库表执行和编写的。这就是所谓的隐式提交(implicit commit),即提交(保存)操作是自动进行的。

但是,在事务处理块中,提交不会隐式地进行。所以需要明确提交,使用COMMIT语句。如下:

START TRANSACTION;
DELETE FROM orderitems WHERE order_num = 20010;
DELETE FROM orders WHERE order_num = 20010;
COMMIT;

解释:只有两条语句都成功执行时,才会执行COMMIT。否则有任意一条错误,都不会COMMIT。但是我执行的时候,第二条硬改成错误的,结果COMMIT时第一条的还是执行了,我很奇怪?就网上了解:我自己的了解就是上面那两句,如果两条语句之一有错误,在COMMIT时就都不执行。下面是参考:对mysql事务提交、回滚的错误理解

事务的回滚不是这么理解的,正确的理解应该是:如果事务中所有sql语句执行正确则需要自己手动提交commit;否则有任何一条执行错误,需要自己提交一条rollback,这时会回滚所有操作,而不是commit会给你自动判断和回滚。

3. 使用保留点

前面讲的都是会回退到定义事务时,而有时需要回退部分事务处理,所以需要保留点,能在事务处理块中合适的位置放置占位符,那么需要回退时,可以回退到某个占位符。

占位符使用SAVEPOINT语句创建,比如:

SAVEPOINT delete1;

每一个保留点都取它标识它的唯一名,以便在回退时,MysQL知道要回退到何处。下面是使用:

ROLLBACK TO delete1;

可以在MysQL代码中设置任意多的保留点,越多越好,就越能按自己的意愿灵活地进行回退。

释放保留点:保留点在事务处理完成(执行一条ROLLBACK或COMMIT)后自动释放。也可以使用RELEASE SAVEPOINT明确地释放保留点。

默认的MySQL行为是自动提交所以更改。任何时候执行一条SQL语句,该语句实际上都是针对表执行的,而且所做的更改立即生效。为让MySQL不自动提交更改,需要使用下面的语句:

SET autocommit=0;

autocommit标志决定释放自动提交更改,不管有没有COMMIT语句。设置autocommit为0(假)指示MySQL不自动提交更改。直到被设置autocommit为真时。

发布了59 篇原创文章 · 获赞 30 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_41800884/article/details/104057521