MySQL基础——游标、触发器、事务

游标

游标(cursor) 是一个存储在MySQL服务器上的数据库查询,它不是一条SELECT语句,而是被该语句检索出来的结果集。在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。
MySQL游标只能用于存储过程。

使用游标

使用游标涉及几个明确的步骤:

  1. 在能够使用游标前,必须声明(定义)它。这个过程实际没有检索数据,指示定义要使用的SELECT语句。
  2. 一旦声明后,必须打开游标以供使用。这个过程用前面定义的SELECT语句把数据实际检索出来。
  3. 对于填充有数据的游标,根据需要取出(检索)各行。
  4. 在结束游标使用时,必须关闭游标。
创建游标

使用DECLARE语句创建。DECLARE命名游标,并定义相应的SELECT语句,根据需要带WHERE和其他子句。存储过程处理完成后,游标就消失了,因为它局限于存储过程中。

在游标定义后,可以打开它。打开和关闭使用OPENCLOSE关键字。游标关闭后必须重新打开才能使用,使用声明过的游标不需要再次声明。
隐含关闭 如果不明确关闭游标,MySQL会在到达END语句时自动关闭它。

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

下面的语句定义了名为ordernumbers的游标,使用了可以检索所有订单的SELECT语句(FETCH用来检索当前行的order_num列,自动从第一行开始,存储到一个名为o的局部声明变量中,对检索出的数据不做任何处理):

mysql> CREATE PROCEDURE processorders()
    -> BEGIN
    ->
    ->   -- Declare local variables
    ->   DECLARE o INT;
    ->
    ->   -- Declare the cursor
    ->   DECLARE ordernumbers CURSOR
    ->   FOR
    ->   SELECT order_num FROM orders;
    ->
    ->   -- Open the cursor
    ->   OPEN ordernumbers;
    ->
    ->   -- Get the cursor
    ->   FETCH ordernumbers INTO o;
    ->
    ->   -- Close the cursor
    ->   CLOSE ordernumbers;
    ->
    -> END;
    -> //
Query OK, 0 rows affected (0.01 sec)

下面的例子将对取出来的数据进行某种实际的处理:

CREATE PROCEDURE processorders()
BEGIN
    
    -- Declare local variables
    DECLARE done BOOLEAN DEFAULT 0;
    DECLARE o INT;
    DECLARE t DECIMAL(8,2);

    -- Declare the cursor
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;
    -- Declare continue handler
    DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;

    -- Create a table to store the results
    CREATE TABLE IF NOT EXISTS ordertotals
        (order_num INT, total DECIMAL(8,2));

    -- Open the cursor
    OPEN ordernumbers;
    
    -- loop through all rows
    REPEAT

        -- Get order number
        FETCH ordernumbers INTO o;

        -- Get the total for this order
        CALL ordertotal(o, 1, t);

        -- Insert order and total into ordertotals
        INSERT INTO ordertotals(order_num, total)
        VALUES(o, t);

    -- End of loop
    UNTIL done END REPEAT;

    -- Close the cursor
    CLOSE ordernumbers;
    
END;

查看存储过程创建的表:

mysql> CALL processorders();
Query OK, 1 row affected (0.03 sec)

mysql> SELECT * FROM ordertotals;
+-----------+---------+
| order_num | total   |
+-----------+---------+
|     20005 |  158.86 |
|     20009 |   40.78 |
|     20010 |    NULL |
|     20006 |   58.30 |
|     20007 | 1060.00 |
|     20008 |  132.50 |
|     20008 |  132.50 |
+-----------+---------+
7 rows in set (0.00 sec)
  • DECLARE CONTINUE HANDLER FOR SQLSTATE ‘02000’ SET done=1;这条语句定义了一个CONTINUE HANDLERS,它是在条件出现时被执行的代码。这里指当SQLSTATE '02000’出现时,SET done=1。SQLSTATE '02000’是一个未找到条件,当REPEAT由于没有更多的行供循环而不能继续时,出现这个条件。
  • DECLARE语句的次序 DECLARE语句定义的局部变量必须在定义任意游标或句柄之前定义,而句柄必须在游标之后定义。

触发器

MySQL语句在需要时被执行,存储过程也是。
触发器 是在MySQL响应以下任意语句而自动执行的一条MySQL语句(或位于BEGIN和END语句之间的一组语句):

  • DELETE
  • INSERT
  • UPDATE

其他MySQL语句不支持触发器。
只有表支持触发器,视图不支持。
触发器的某些用途是执行对要插入表中的值的检查,或对更新中涉及的值进行计算。

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

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

触发器名必须在每个表唯一,但不是在每个数据库中唯一。但最好还是在数据库范围内使用唯一的触发器名。
要创建触发器或删除触发器,使用 CREATE TRIGGER或 DROP TRIGGER声明:

CREATE
    [DEFINER = user]
    TRIGGER trigger_name
    trigger_time trigger_event
    ON tbl_name FOR EACH ROW
    [trigger_order]
    trigger_body

trigger_time: { BEFORE | AFTER }

trigger_event: { INSERT | UPDATE | DELETE }

trigger_order: { FOLLOWS | PRECEDES } other_trigger_name
DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name

可以为给定的表定义具有相同触发事件和动作时间的多个触发。例如,BEFORE UPDATE一个表可以有两个触发器。默认情况下,具有相同触发事件和动作时间的触发将按照其创建顺序进行激活。要影响触发器的顺序,请在其后指定一个子句,该子句FOR EACH ROW指示FOLLOWS或, PRECEDES并且还具有相同的触发器事件和动作时间的现有触发器的名称。使用 FOLLOWS,新触发器将在现有触发器之后激活。使用PRECEDES,新触发器将在现有触发器之前激活。

例如,以下触发器定义BEFORE INSERT为account表定义了另一个 触发器 :

mysql> CREATE TRIGGER ins_transaction BEFORE INSERT ON account
       FOR EACH ROW PRECEDES ins_sum
       SET
       @deposits = @deposits + IF(NEW.amount>0,NEW.amount,0),
       @withdrawals = @withdrawals + IF(NEW.amount<0,-NEW.amount,0);
Query OK, 0 rows affected (0.01 sec)

MySQL在触发器执行过程中按以下方式处理错误:

  • 如果BEFORE触发器失败,则不会执行相应行上的操作。
  • 一个BEFORE触发器会被尝试插入或修改行所激活,而不管的尝试是否成功随后。
  • 一个AFTER触发器只有当任何BEFORE触发器和行操作成功执行后执行。
  • BEFORE或 AFTER触发器期间的错误会导致导致触发器调用的整个语句失败。
  • 对于事务表,语句失败应导致该语句执行的所有更改回滚。触发器失败会导致语句失败,因此触发器失败也会导致回滚。对于非事务表,无法执行这种回滚,因此,尽管该语句失败,但在错误点之前执行的任何更改仍然有效。

在触发器主体内,使用OLD和NEW关键字可以访问受触发器影响的行中的列。
触发器可以按名称包含对表的直接引用:

CREATE TABLE test1(a1 INT);
CREATE TABLE test2(a2 INT);
CREATE TABLE test3(a3 INT NOT NULL AUTO_INCREMENT PRIMARY KEY);
CREATE TABLE test4(
  a4 INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  b4 INT DEFAULT 0
);

delimiter |

CREATE TRIGGER testref BEFORE INSERT ON test1
  FOR EACH ROW
  BEGIN
    INSERT INTO test2 SET a2 = NEW.a1;
    DELETE FROM test3 WHERE a3 = NEW.a1;
    UPDATE test4 SET b4 = b4 + 1 WHERE a4 = NEW.a1;
  END;
|

delimiter ;

INSERT INTO test3 (a3) VALUES
  (NULL), (NULL), (NULL), (NULL), (NULL),
  (NULL), (NULL), (NULL), (NULL), (NULL);

INSERT INTO test4 (a4) VALUES
  (0), (0), (0), (0), (0), (0), (0), (0), (0), (0);

假设您将以下值插入表中 test1,如下所示:

mysql> INSERT INTO test1 VALUES 
       (1), (3), (1), (7), (1), (8), (4), (4);
Query OK, 8 rows affected (0.01 sec)
Records: 8  Duplicates: 0  Warnings: 0

结果四个表包含以下数据:

mysql> SELECT * FROM test1;
+------+
| a1   |
+------+
|    1 |
|    3 |
|    1 |
|    7 |
|    1 |
|    8 |
|    4 |
|    4 |
+------+
8 rows in set (0.00 sec)

mysql> SELECT * FROM test2;
+------+
| a2   |
+------+
|    1 |
|    3 |
|    1 |
|    7 |
|    1 |
|    8 |
|    4 |
|    4 |
+------+
8 rows in set (0.00 sec)

mysql> SELECT * FROM test3;
+----+
| a3 |
+----+
|  2 |
|  5 |
|  6 |
|  9 |
| 10 |
+----+
5 rows in set (0.00 sec)

mysql> SELECT * FROM test4;
+----+------+
| a4 | b4   |
+----+------+
|  1 |    3 |
|  2 |    0 |
|  3 |    1 |
|  4 |    2 |
|  5 |    0 |
|  6 |    0 |
|  7 |    1 |
|  8 |    1 |
|  9 |    0 |
| 10 |    0 |
+----+------+
10 rows in set (0.00 sec)

事务

事务处理(transaction processing) 可以用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行,要么完全不执行。
事务(transaction) 指一组SQL语句;
回退(rollback) 指撤销指定SQL语句的过程;
提交(commit) 指将未存储的SQL语句结果写入数据库表;
保留点(savepoint) 指事务处理中设置的临时占位符(place-holder),你可以对它发布回退(与回退整个事务处理不同)。

管理事务处理的关键在于将SQL语句组分解为逻辑块,并明确规定数据何时应该回退,何时不应该。
MySQL使用下面的语句标识事务的开始:

START TRANSACTION

事务处理用来管理INSERT、UPDATE和DELETE语句。不能回退SELECT语句(这样做也没有什么意义),不能回退CREATE或DROP操作。事务处理块中可以使用这两条语句,但是如果执行回退,它们不会被撤销。

为了支持回退部分事务,必须能在事务处理块中合适位置放置占位符,这样可以回退到某个占位符。
为了创建占位符,可使用SAVEPOINT语句:

SAVEPOINT delete1;
...
ROLLBACK TO delete1;

默认的MySQL行为都是自动提交所有更改的。为指示MySQL不自动提交更改,需使用以下语句:

SET autocommit=0;

使用时START TRANSACTION,自动提交保持禁用状态,直到您使用COMMIT或结束事务 ROLLBACK。然后,自动提交模式将恢复为之前的状态。

你可能感兴趣的:(《MySQL必知必会》学习笔记,数据库,mysql)