一 存储过程和函数
1.1 什么是存储过程和函数
存储过程和函数是事先经过编译并存储在数据库中的一段sql语句的集合,调用存储过程和函数可以简化应用开发人员的很多工作,减少数据在数据库这服务器之间的传输,对于提高数据吹的效率有好处的。
存储过程和函数的区别在于函数必须有返回值,而存储过程没有,存储过程的参数可以使用in, out,inout类型,而函数的参数只能是int类型的,做过有函数从其他类型的数据库迁移到mysql,那么可能因此需要将函数改造成存储过程。
1.2 存储过程和函数的相关操作
在对存储过程或函数进行操作时,首先确认用户是否具有相应的权限,例如,创建存储过程挥着函数需要create routine 权限,修改或者删除存储过程或者函数需要alter routine权限,执行存储过程或者函数需要execute权限。
1.2.1 创建,修改存储过程或者函数
创建,修改存储过程或者函数的语法:
CREATE PROCEDURE sp_name ([proc_parameter[,...]]) [characteristic ...] routine_body CREATE FUNCTION sp_name ([func_parameter[,...]]) RETURNS type [characteristic ...] routine_body proc_parameter: [ IN | OUT | INOUT ] param_name type func_parameter: param_name type type: Any valid MySQL data type characteristic: LANGUAGE SQL | [NOT] DETERMINISTIC | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA } | SQL SECURITY { DEFINER | INVOKER } | COMMENT 'string' routine_body: Valid SQL procedure statement or statements ALTER {PROCEDURE | FUNCTION} sp_name [characteristic ...] characteristic: { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA } | SQL SECURITY { DEFINER | INVOKER } | COMMENT 'string'
调用过程的语法如下:
CALL sp_name([parameter[,...]])
mysql的存储过程和函数中允许包含DDL语句,也允许在存储过程中执行提交(commit,即确认提交之前的修改)或者回滚(rollback,即放弃之前的修改),但是存储过程和函数中不允许执行LOAD DATA INFILE语句。此外,存储过程和函数中可以调用其他的额过程或者函数。下面创建了一个新的过程film_in_stock:
mysql> DELIMITER $$ mysql> mysql> CREATE PROCEDURE film_in_stock(IN p_film_id INT, IN p_store_id INT, OUT p_film_count INT) -> READS SQL DATA -> BEGIN -> SELECT inventory_id -> FROM inventory -> WHERE film_id = p_film_id -> AND store_id = p_store_id -> AND inventory_in_stock(inventory_id); -> -> SELECT FOUND_ROWS() INTO p_film_count; -> END $$ Query OK, 0 rows affected (0.00 sec) mysql> mysql> DELIMITER ;
上面是在使用的样例数据库中创建的一个过程,该过程用来检查film_id和store_id对应的inventory是否满足要求,并且返回满足要求的inventory_id以及满足要走的记录数。
通常我们执行创建过程和函数之前都会通过“DELIMITER $$”命令将语句的结束符从“;”修改成其他符号,这里使用的是“$$”.这样在过程和函数中的“;”就不会被mysql解释成语句的结束而提示错误。在存储过程或者函数创建完毕,通过“DELIMITER ;”命令再将结束符改回成“;”。
可以看到在这个过程调用了函数inventory_in_stock(),并且这个过程有两个输入参数和一个输出参数。下面通过调用这个过程来看看返回的结果。
如果需要检查file_id=2 store_id=2 对应的inventory的情况,则首先手工执行过程中的sql与,以查看执行的效果。
mysql> SELECT inventory_id -> FROM inventory -> WHERE film_id = 2 -> AND store_id = 2 -> AND inventory_in_stock(inventory_id); +--------------+ | inventory_id | +--------------+ | 10 | | 11 | +--------------+ 2 rows in set (0.00 sec)
满足条件的记录应该是两条,inventory_id分别是10和11.如果将这个查询封装在存储过程中调用,那么调用过程的执行情况如下:
mysql> CALL film_in_stock(2,2,@a); +--------------+ | inventory_id | +--------------+ | 10 | | 11 | +--------------+ 2 rows in set (0.00 sec) Query OK, 0 rows affected (0.00 sec) mysql> select @a; +------+ | @a | +------+ | 2 | +------+ 1 row in set (0.00 sec)
可以看到调用存储过程与直接执行sql的小幅哦是相同的,但是存储过程的好处在于处理逻辑都封装在数据库端,调用者不需要了解中间处理逻辑,一旦处理逻辑发生变化,只需要修改存储过程即可,而对调用者的程序完全没有影响。
另外,和视图的创建语法稍有不同,存储过程和函数的create语法不支持使用create or replace对存储过程和函数进行修改,如果需要对已有的存储过程或者函数进行修改,需要执行alter语法。
1.2.2 删除存储过程或者函数
一次只能删除一个存储过程或者函数,删除过程或者函数需要有该过程或者函数的alter routine 权限,具体语法如下:
DROP {PROCEDURE | FUNCTION} [IF EXISTS] sp_name
例如,使用drop语法删除file_in_stock过程:
mysql> DROP PROCEDURE film_in_stock;
Query OK, 0 rows affected (0.00 sec)
1.2.3 查看存储过程或者函数
存储过程或者函数创建以后,用户可能需要查看存储过程或者函数的状态或者定义等信息,便于了解存储过程或者函数的基本情况,下面将介绍如何查看存储过程或函数相关信息。
1. 查看存储过程或者函数的状态
SHOW {PROCEDURE | FUNCTION} STATUS [LIKE 'pattern']
下面演示的是查看过程file_in_stock的信息:
mysql> show procedure status like 'film_in_stock'G *************************** 1. row *************************** Db: sakila Name: film_in_stock Type: PROCEDURE Definer: root@localhost Security_type: DEFINER Comment: 1 row in set (0.00 sec)
2.查看存储过程或者函数的定义
SHOW CREATE {PROCEDURE | FUNCTION} sp_name
下面演示的是查看过程file_in_stock的定义:
mysql> show create procedure film_in_stock G *************************** 1. row *************************** Procedure: film_in_stock sql_mode: Create Procedure: CREATE DEFINER=`root`@`localhost` PROCEDURE `film_in_stock`(IN p_film_id INT, IN p_store_id INT, OUT p_film_count INT) READS SQL DATA BEGIN SELECT inventory_id FROM inventory WHERE film_id = p_film_id AND store_id = p_store_id AND inventory_in_stock(inventory_id); SELECT FOUND_ROWS() INTO p_film_count; END 1 row in set (0.00 sec)
3.通过查看information_schema.routines了解存储过程和函数的信息
除了以上两种方法,我们还可以查看表来了解存储过程和函数的相关信息,通过查看information_schems.routines就可以获得存储过程和函数的包括名称,类型,语法,创建人等信息。
例如,通过查看information_schema.routines得到过程file_in_stock的定义:
mysql> select * from routines where ROUTINE_NAME = 'film_in_stock' G *************************** 1. row *************************** SPECIFIC_NAME: film_in_stock ROUTINE_CATALOG: NULL ROUTINE_SCHEMA: sakila ROUTINE_NAME: film_in_stock ROUTINE_TYPE: PROCEDURE DTD_IDENTIFIER: NULL ROUTINE_BODY: SQL ROUTINE_DEFINITION: BEGIN SELECT inventory_id FROM inventory WHERE film_id = p_film_id AND store_id = p_store_id AND inventory_in_stock(inventory_id); SELECT FOUND_ROWS() INTO p_film_count; END EXTERNAL_NAME: NULL EXTERNAL_LANGUAGE: NULL PARAMETER_STYLE: SQL IS_DETERMINISTIC: NO SQL_DATA_ACCESS: READS SQL DATA SQL_PATH: NULL SECURITY_TYPE: DEFINER SQL_MODE: ROUTINE_COMMENT: DEFINER: root@localhost 1 row in set (0.00 sec)
1.2.4 流程控制
可以使用if,case,loop,leave,iterate,repeat以及while语句进行流程的控制,下面将逐一进行分析。
1. if语句
if实现条件的判断,满足不同的条件执行不同的语句列表,具体语法如下:
IF search_condition THEN statement_list
[ELSEIF search_condition THEN statement_list] ...
[ELSE statement_list]
END IF
2. case语句
case实现比if更复杂一些的条件构造,具体语法如下:
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list] ...
[ELSE statement_list]
END CASE
Or:
CASE
WHEN search_condition THEN statement_list
[WHEN search_condition THEN statement_list] ...
[ELSE statement_list]
END CASE
if语句也可以使用case语句来完成
case when i_staff_id = 2 then set @x1 = @x1 + d_amount; else set @x2 = @x2 + d_amount; end case; 或者: case i_staff_id when 2 then set @x1 = @x1 + d_amount; else set @x2 = @x2 + d_amount; end case;
3. loop语句
LOOP实现简单的循环,退出循环的条件需要使用其他的语句定义,通常可以使用leave语句实现,具体语法如下:
[begin_label:] LOOP
statement_list
END LOOP [end_label]
如果不在statement_list中增加退出循环的语句,那么loop语句可以用来实现简单的死循环。
4. leave语句
用来从标注的流程中退出,通常和begin_end或者循环一起使用。
下面是使用一个loop和leave的简单的例子,循环100次向actor表中插入记录,当插入100条记录以后,退出循环:
mysql> CREATE PROCEDURE actor_insert () -> BEGIN -> set @x = 0; -> ins: LOOP -> set @x = @x + 1; -> IF @x = 100 then -> leave ins; -> END IF; -> INSERT INTO actor(first_name,last_name) VALUES ('Test','201'); -> END LOOP ins; -> END; -> $$ Query OK, 0 rows affected (0.00 sec) mysql> call actor_insert(); Query OK, 0 rows affected (0.01 sec) mysql> select count(*) from actor where first_name='Test'; +----------+ | count(*) | +----------+ | 100 | +----------+ 1 row in set (0.00 sec)
5. iterate语句
iterate语句必须使用在循环中,作用是跳过当前循环的剩下的语句,直接进入下一轮循环。下面的例子使用了iterate语句,当@x变量是偶数的时候,不再执行循环中剩下的语句,而直接进入下一轮的循环:
mysql> CREATE PROCEDURE actor_insert () -> BEGIN -> set @x = 0; -> ins: LOOP -> set @x = @x + 1; -> IF @x = 10 then -> leave ins; -> ELSEIF mod(@x,2) = 0 then -> ITERATE ins; -> END IF; -> INSERT INTO actor(actor_id,first_name,last_name) VALUES (@x+200,'Test',@x); -> END LOOP ins; -> END; -> $$ Query OK, 0 rows affected (0.00 sec) mysql> call actor_insert(); Query OK, 0 rows affected (0.00 sec) mysql> select actor_id,first_name,last_name from actor where first_name='Test'; +----------+------------+-----------+ | actor_id | first_name | last_name | +----------+------------+-----------+ | 201 | Test | 1 | | 203 | Test | 3 | | 205 | Test | 5 | | 207 | Test | 7 | | 209 | Test | 9 | +----------+------------+-----------+ 5 rows in set (0.00 sec)
6. repeat语句
有条件的循环控制语句,当满足条件的时候退出循环,具体语法如下:
[begin_label:] REPEAT
statement_list
UNTIL search_condition
END REPEAT [end_label]
7. while语句
while语句实现的也是有条件的循环控制语句,即当满足条件时执行循环的内容,具体语法如下:
begin_label:] WHILE search_condition DO
statement_list
END WHILE [end_label]
while语句和repeat循环的区别在于:while是满足条件才执行循环,repeat是满足条件退出循环:while是在首次循环执行之前就能判断条件,所以循环最少执行0次,而repeat是在首次执行完循环知否才判断条件,所以循环最少执行1次。以下例子就是用来对比repeat和while语句的功能:
mysql> delimiter $$ mysql> CREATE PROCEDURE loop_demo () -> BEGIN -> set @x = 1 , @x1 = 1; -> REPEAT -> set @x = @x + 1; -> until @x > 0 end repeat; -> -> while @x1 < 0 do -> set @x1 = @x1 + 1; -> end while; -> END; -> $$ Query OK, 0 rows affected (0.00 sec) mysql> delimiter ; mysql> call loop_demo(); Query OK, 0 rows affected (0.00 sec) mysql> select @x,@x1; +------+------+ | @x | @x1 | +------+------+ | 2 | 1 | +------+------+ 1 row in set (0.00 sec)
从判断的条件上看,初始值都是满足退出循环的条件的,但是repeat循环仍然执行了一次以后才退出循环的,而while循环则一次都没有执行。
二 触发器
mysql从5.0.2版本开始支持触发器的功能,触发器是与表有关的数据库对象,在满足定义条件的触发,并执行触发器中定义的语句的集合。触发器的这种特性可以协助停用在数据库端确保数据的完整性。
2.1 创建触发器
创建触发器的语法如下:
CREATE TRIGGER trigger_name trigger_time trigger_event
ON tbl_name FOR EACH ROW trigger_stmt
注意: 触发器只能创建在永久表上,不能对临时表创建触发器
其中trigger_time是触发器的触发事件,可以是before或者after,before的含义是指在检查约束钱触发,而after是在检查约束后触发。
其中trigger_event是触发器的触发事件,可以是insert,update或者delete
在同一个表相同触发时间的相同触发事件,只能定义一个触发器,如果在某个表的不同字段的after更新触发器,在mysql中只能定义成一个触发器,在触发器中通过判断更新的字段进行相应的处理。
使用别名old和new;来引用触发器中发生变化的记录内容,这与其他的数据库是相似的,现在触发器还只支持行级触发的,不支持语句级触发。
在样例数据库中,为file表创建了after insert的触发器,具体如下:
DELIMITER $$
CREATE TRIGGER ins_film
AFTER INSERT ON film FOR EACH ROW BEGIN
INSERT INTO film_text (film_id, title, description)
VALUES (new.film_id, new.title, new.description);
END;
$$
delimiter ;
插入film表记录的时候,会向file_text表中也插入相应的记录。
mysql> INSERT INTO film VALUES -> (1001,'ACADEMY DINOSAUR', -> 'A Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies', -> 2006,1,NULL,6,'0.99',86,'20.99','PG','Deleted Scenes,Behind the Scenes','2006-02-15 05:03:42'); Query OK, 1 row affected (0.05 sec) mysql> select * from film_text where film_id=1001 G *************************** 1. row *************************** film_id: 1001 title: ACADEMY DINOSAUR description: A Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies 1 row in set (0.00 sec)
对于 INSERT INOT…ON DUPLICATE KEY UPDATE…语句来说,触发触发器的顺序可能会造成疑惑。下面对file表创建了 BEFROE INSERT、AFTER INSERT、BEFORE UPDATE、AFTER UPDATE触发器,然后插入记录,观察触发器的触发情况:
--创建 BEFROE INSERT、AFTER INSERT、BEFORE UPDATE、AFTER UPDATE 触发器: mysql> create table tri_demo(id int AUTO_INCREMENT,note varchar(20),PRIMARY KEY (id)); Query OK, 0 rows affected (0.03 sec) mysql> CREATE TRIGGER ins_film_bef -> BEFORE INSERT ON film FOR EACH ROW BEGIN -> INSERT INTO tri_demo (note) VALUES ('before insert'); -> END; -> $$ Query OK, 0 rows affected (0.00 sec) mysql> CREATE TRIGGER ins_film_aft -> AFTER INSERT ON film FOR EACH ROW BEGIN -> INSERT INTO tri_demo (note) VALUES ('after insert'); -> END; -> $$ Query OK, 0 rows affected (0.00 sec) mysql> CREATE TRIGGER upd_film_bef -> BEFORE update ON film FOR EACH ROW BEGIN -> INSERT INTO tri_demo (note) VALUES ('before update'); -> END; -> $$ Query OK, 0 rows affected (0.00 sec) mysql> CREATE TRIGGER upd_film_aft -> AFTER update ON film FOR EACH ROW BEGIN -> INSERT INTO tri_demo (note) VALUES ('after update'); -> END; -> $$ Query OK, 0 rows affected (0.00 sec) --插入记录已经存在的情况: mysql> select film_id,title from film where film_id = 1001; +---------+------------------+ | film_id | title | +---------+------------------+ | 1001 | ACADEMY DINOSAUR | +---------+------------------+ 1 row in set (0.00 sec) mysql> INSERT INTO film VALUES -> (1001,'Only test', -> 'Only test',2006,1,NULL,6,'0.99',86,'20.99','PG', -> 'Deleted Scenes,Behind the Scenes','2006-02-15 05:03:42') -> ON DUPLICATE KEY -> UPDATE title='update record'; Query OK, 2 rows affected (0.05 sec) mysql> select * from tri_demo; +----+---------------+ | id | note | +----+---------------+ | 1 | before insert | | 2 | before update | | 3 | after update | +----+---------------+ 3 rows in set (0.00 sec) --插入新记录的情况: mysql> delete from tri_demo; Query OK, 3 rows affected (0.00 sec) mysql> select film_id,title from film where film_id = 1002; Empty set (0.00 sec) mysql> INSERT INTO film VALUES -> (1002,'Only test', -> 'Only test',2006,1,NULL,6,'0.99',86,'20.99','PG', -> 'Deleted Scenes,Behind the Scenes','2006-02-15 05:03:42') -> ON DUPLICATE KEY -> UPDATE title='update record'; Query OK, 1 row affected (0.05 sec) mysql> mysql> select * from tri_demo; +----+---------------+ | id | note | +----+---------------+ | 4 | before insert | | 5 | after insert | +----+---------------+ 2 rows in set (0.00 sec)
从上面的例子可以知道,对于有重复的记录,需要进行update操作的insert,触发器触发的顺序是BEFORE INSERT、BEFORE UPDATE、AFTER UPDATE;对于没有重复记录的insert,就是简单的执行insert操作,触发器触发的顺序是BEFORE INSERT、AFTER INSERT。对于那些实际执行update操作的记录,仍然会执行before insert触发器的内容,在设计触发器的时候要考虑这点,避免错误的触发了触发器。
2.2 删除触发器
一次可以删除一个触发程序,如果没有指定schame_name,默认为当前数据库,具体语法如下:
DROP TRIGGER [schema_name.]trigger_name
例如,要删除file表上的触发器ins_film,可以使用如下几个命令:
mysql> drop trigger ins_film;
Query OK, 0 rows affected (0.00 sec)
2.3 查看触发器
可以通过执行show triggers命令查看触发器的状态,语法等信息,但是因为不能查询指定的触发器,所以每次都返回所有的触发器的信息,使用起来不是很方便,具体语法如下:
mysql> show triggers G *************************** 1. row *************************** Trigger: customer_create_date Event: INSERT Table: customer Statement: SET NEW.create_date = NOW() Timing: BEFORE Created: NULL sql_mode: STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZER O,TRADITIONAL,NO_AUTO_CREATE_USER Definer: root@localhost *************************** 2. row ***************************
另外一个查看方式是查询系统表的information_schame.triggers表,这个方式可以查询指定触发器的指定信息,操作起来明显方便很多。
mysql> desc triggers; +----------------------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +----------------------------+--------------+------+-----+---------+-------+ | TRIGGER_CATALOG | varchar(512) | YES | | | | | TRIGGER_SCHEMA | varchar(64) | NO | | | | | TRIGGER_NAME | varchar(64) | NO | | | | | EVENT_MANIPULATION | varchar(6) | NO | | | | | EVENT_OBJECT_CATALOG | varchar(512) | YES | | | | | EVENT_OBJECT_SCHEMA | varchar(64) | NO | | | | | EVENT_OBJECT_TABLE | varchar(64) | NO | | | | | ACTION_ORDER | bigint(4) | NO | | 0 | | | ACTION_CONDITION | longtext | YES | | | | | ACTION_STATEMENT | longtext | NO | | | | | ACTION_ORIENTATION | varchar(9) | NO | | | | | ACTION_TIMING | varchar(6) | NO | | | | | ACTION_REFERENCE_OLD_TABLE | varchar(64) | YES | | | | | ACTION_REFERENCE_NEW_TABLE | varchar(64) | YES | | | | | ACTION_REFERENCE_OLD_ROW | varchar(3) | NO | | | | | ACTION_REFERENCE_NEW_ROW | varchar(3) | NO | | | | | CREATED | datetime | YES | | | | | SQL_MODE | longtext | NO | | | | | DEFINER | longtext | NO | | | | +----------------------------+--------------+------+-----+---------+-------+ 19 rows in set (0.00 sec) mysql> select * from triggers where trigger_name = 'ins_film_bef' G *************************** 1. row *************************** TRIGGER_CATALOG: NULL TRIGGER_SCHEMA: sakila TRIGGER_NAME: ins_film_bef EVENT_MANIPULATION: INSERT EVENT_OBJECT_CATALOG: NULL EVENT_OBJECT_SCHEMA: sakila EVENT_OBJECT_TABLE: film ACTION_ORDER: 0 ACTION_CONDITION: NULL ACTION_STATEMENT: BEGIN INSERT INTO tri_demo (note) VALUES ('before insert'); END ACTION_ORIENTATION: ROW ACTION_TIMING: BEFORE ACTION_REFERENCE_OLD_TABLE: NULL ACTION_REFERENCE_NEW_TABLE: NULL ACTION_REFERENCE_OLD_ROW: OLD ACTION_REFERENCE_NEW_ROW: NEW CREATED: NULL SQL_MODE: DEFINER: root@localhost 1 row in set (0.01 sec)
2.4 触发器的使用
触发器执行的语句有两个限制。
1. 触发器程序不能调用将数据返回客户端的存储程序,也不能使用采用call语句的动态sql语句,但是允许存储程序通过参数将数据返回触发程序。也就是存储过程或者函数通过out或者inout类型的参数将数据返回触发器是可以的,但是不能直接返回数据的过程。
2. 不能早触发器中使用以显式或者隐式方式开始或结束事务的语句,如start transaction,commit或rollback
mysql的触发器是按照before触发器,行操作,after触发器的顺序执行的,其中任何一个操作发生错误都不会继续执行剩下的操作。如果是对事务表进行的操作,那么会整个作为一个事务被回滚(rollback),但是如果是对非事务表进行的操作,那么已经更新的记录将无法回滚,这也是设计触发器的时候需要注意的问题。
三 事务控制和锁定语句
mysql支持对myisam和memory存储引擎的表进行表级锁定,对BOB存储引擎的表进行页级锁定,对innodb存储引擎的表进行级锁定。默认情况下,表锁和行锁都是自动获取的,不需要额外的命令。但是在有的情况下,用户需要明确的进行锁表或者事务的控制,以确保整个事务的完整性,这样就需要使用事务控制和锁定语句来完成。
3.1 lock table 和unlock table
lock tables可以锁定用于当前线程的表。如果表被其他线程锁定,则当前线程会等待,知道获取所有锁定为止。
unlock tables 可以释放当前线程获得任何锁定。当前线程执行另一个lock tables时,或当与服务器的连接被关闭时,所以由当前线程锁定的表被隐含的解锁,具体语法如下:
LOCK TABLES tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE} [, tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}] ... UNLOCK TABLES
如图所示是一个获得表锁和释放表锁的简单例子,演示的是film_text表获得read锁的情况,其他session更新该表记录会等待锁,film_text表释放锁之后其他session可以更新操作。其中session1和session2表示两个同时打开的session,表格中的每一行表示每一时刻两个session的运行状况,后面的例子也都是同样格式。
3.2 事务控制
mysql通过set AUTOCOMMIT, START TRANSACTION, COMMIT和ROLLBACK等语句支持本地事务,具体语法如下。
START TRANSACTION | BEGIN [WORK]
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
SET AUTOCOMMIT = {0 | 1}
默认情况下,mysql是自动提交的,如果需要通过明确的commit和rollback来提交和回滚事务,那么需要通过明确的事务控制命令来开始事务。
1.start transaction或begin语句可以开始一项新的事务。
2.commit和rollback用来提交或回滚事务
3chain和release字句分别用来定义在事务提交或者回滚之后的操作,chain会启动一个新事物,并且和刚才的事务具有相同的隔离级别,release则会断开和客户端的连接。
4.set autocommit 可以修改当前连接的提交方式,如果设置了set autocommit=0,则设置之后的所有的事务都需要通过明确的命令进行提交或者回滚。
如果只是对某些语句需要进行事务控制,则使用start transaction 语句开始一个事务比较方便,这样事务结束之后可以自动回到自动提交的方式,如果希望所有的事务都不是自动提交的,那么通过修改autocommit来控制事务比较方便,这样不会在每个事务开始的时候再执行start transaction语句。
如图演示了使用start transaction开始的事务在提交之后自动回到自动提交的方式;如果在提交的时候使用commit and chain,那么会在提交后立即开始一个新的事务。
如果在锁表期间,用start transaction命令开始一个新事务,会造成一个隐含的unlock tables被执行,
因此,在同一个事务中,最好不使用不同的存储引擎的表,否则rollback时需要对非事务类型的表进行特别的处理,因为commit,rollback只能对事物类型的表进行提交和回滚。
通常情况下,只对提交的事务记录到二进制的日志中,但是如果一个事务中包含非事务类型的表,那么回滚操作也会被记录到二进制日志中,以确保非事务类型表的更新可以被复制到从slave数据库中。
在事务中可以通过定义 SAVEPOINT,指定回滚事务的一个部分,但是不能指定提交事务,的一个部分。对于复杂的应用,可以定义多个不同的 SAVEPOINT,满足不同的条件时,回滚不同的 SAVEPOINT。需要注意的是,如果定义了相同名字的 SAVEPOINT,则后面定义的SAVEPOINT 会覆盖之前的定义。对于不再需要使用的 SAVEPOINT,可以通过 RELEASE SAVEPOINT 命令删除 SAVEPOINT,删除后的 SAVEPOINT,不能再执行 ROLLBACK TO SAVEPOINT命令。
如表所示的例子就是模拟回滚事务的一个部分,通过定义 SAVEPOINT 来指定需要回滚的事务的位置。
3.3 分布式事务的使用
mysql从5.0.3开始支持分布式事务,当前分布式事务只支持innodb存储引擎,一个分布式事务会涉及多个行动,这些行动本身是事务性的。所有行动都必须一起成功完成,或者一起被回滚。
3.3.1 分布式事务的原理
在 MySQL 中,使用分布式事务的应用程序涉及一个或多个资源管理器和一个事务管理器。。
资源管理器(RM)用于提供通向事务资源的途径。数据库服务器是一种资源管理器。该管理器必须可以提交或回滚由 RM 管理的事务。例如,多台 MySQL 数据库作为多台资源管理器或者几台 Mysql 服务器和几台 Oracle 服务器作为资源管理器。
事务管理器(TM)用于协调作为一个分布式事务一部分的事务。TM 与管理每个事务的 RMs 进行通讯。一个分布式事务中各个单个事务均是分布式事务的“分支事务”。分布式事务和各分支通过一种命名方法进行标识。
要执行一个分布式事务,必须知道这个分布式事务涉及到了哪些资源管理器,并且把每个资源管理器的事务执行到事务可以被提交或回滚时。根据每个资源管理器报告的有关执行情况的内容,这些分支事务必须作为一个原子性操作全部提交或回滚。要管理一个分布式事务,必须要考虑任何组件或连接网络可能会故障。
用于执行分布式事务的过程使用两阶段提交,发生时间在由分布式事务的各个分支需要进行的行动已经被执行之后。
1.在第一阶段,所有的分支被预备好。即它们被 TM 告知要准备提交。通常,这意味着用于管理分支的每个 RM 会记录对于被稳定保存的分支的行动。分支指示是否它们可以这么做。这些结果被用于第二阶段。
2.在第二阶段,TM 告知 RMs 是否要提交或回滚。如果在预备分支时,所有的分支指示它们将能够提交,则所有的分支被告知要提交。如果在预备时,有任何分支指示它将不能提交,则所有分支被告知回滚。
在有些情况下,一个分布式事务可能会使用一阶段提交。例如,当一个事务管理器发现,一个分布式事务只由一个事务资源组成(即单一分支),则该资源可以被告知同时进行预备和提交。
3.3.2 分布式事务的语法
分布式事务(XA 事务)的 SQL 语法主要包括:
XA {START|BEGIN} xid [JOIN|RESUME]
XA START xid 用于启动一个带给定 xid 值的 XA 事务。每个 XA 事务必须有一个唯一的 xid值,因此该值当前不能被其他的 XA 事务使用。
xid 是一个 XA 事务标识符,用来唯一标识一个分布式事务。xid 值由客户端提供,或由MySQL 服务器生成。xid 值包含 1~3 个部分:
xid: gtrid [, bqual [, formatID ]]
gtrid 是一个分布式事务标识符,相同的分布式事务应该使用相同的 gtrid,这样可以明确知道 xa 事务属于哪个分布式事务。
bqual 是一个分支限定符,默认值是空串。对于一个分布式事务中的每个分支事务,bqual 值必须是唯一的。
formatID 是一个数字,用于标识由 gtrid 和 bqual 值使用的格式,默认值是 1。
下面其他 XA 语法中用到的 xid 值,都必须和 START 操作使用的 xid 值相同,也就是表示对这个启动的 XA 事务进行操作。
XA END xid [SUSPEND [FOR MIGRATE]]
XA PREPARE xid
使事务进入 PREPARE 状态,也就是两阶段提交的第一个提交阶段。
XA COMMIT xid [ONE PHASE]
XA ROLLBACK xid
这两个命令用来提交或者回滚具体的分支事务。也就是两阶段提交的第二个提交阶段,分支事务被实际的提交或者回滚。
XA RECOVER
XA RECOVER 返回当前数据库中处于 PREPARE 状态的分支事务的详细信息。
分布式的关键在于如何确保分布式事务的完整性,以及在某个分支出现问题时的故障解决。XA 的相关命令就是提供给应用如何在多个独立的数据库之间进行分布式事务的管理,包括启动一个分支事务、使事务进入准备阶段以及事务的实际提交回滚操作等。如表所示的例子演示了一个简单的分布式事务的执行,事务的内容是在 DB1 中插入一条记录,同时在 DB2 中更新一条记录,两个操作作为同一事务提交或者回滚。