事务(Transaction)及其ACID属性
事务是由一组SQ语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
隔离性(Isoation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
持久性(Durabe):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
事物一般和异常处理结合
- <?php
- $lnk = mysql_connect("localhost", "root", "");
- mysql_select_db("test");
- mysql_query("BEGIN");
- try {
- mysql_query("INSERT INTO test VALUES(1, 'yangjun')");
- mysql_query("INSERT INTO test VALUES(1, 'yangjun')");
- mysql_query("INSERT INTO test VALUES(2, '杨俊')");
- mysql_query("COMMIT"); //全部成功,提交执行结果
- }catch (Exception $e){
- //$e->getMessage();
- mysql_query("ROLLBACK"); //有任何错误发生,回滚并取消执行结果
- }
在这里要注意,
MyISAM:不支持事务 ,用于只读程序提高性能
InnoDB:支持ACID事务、行级锁、并发
Berkeley DB:支持事务
还有一点要注意:MySQL默认的行为是在每条SQL语句执行后执行一个COMMIT语句,从而有效的将每条语句独立为一个事务。
但往往,我们需要在使用事务的时候,是需要执行多条sql语句的。这就需要我们手动设置MySQL的autocommit属性为0,默认为1。
同时,使用START TRANSACTION语句显式的打开一个事务 。如上面的示例。
如果不这样做,会有什么结果呢?
我们将上面第二段代码中 //mysql_query(‘SET autocommit=0′); 和 // mysql_query($sql3); 注释去掉,然后执行。
此时,mysql_query($sql3) 执行就不会insert到数据库中。
- //对于不支持事务的MyISAM引擎数据库可以使用表锁定的方法:
- $sql_1=" LOCK TABLES test WRITE ";
- mysql_query($sql_1);
- $sql_2=" INSERT INTO test VALUES('".$a."','".$b."') ";
- if(mysql_query($sql_2)){
- echo 'successful!';
- }else{
- echo 'Unsuccessful!';
- }
- $sql_3=" UNLOCK TABLES ";
- mysql_query($sql_3);
如果我们将 // mysql_query(‘SET autocommit=1′); 本句注释去掉,那么mysql_query($sql3); 就会执行成功。
通常COMMIT或ROLLBACK语句执行时才完成一个事务,但是有些DDL语句等会隐式触发COMMIT。
比如下列语句
- ALTER FUNCTION
- ALTER PROCEDURE
- ALTER TABLE
- BEGIN
- CREATE DATABASE
- CREATE FUNCTION
- CREATE INDEX
- CREATE PROCEDURE
- CREATE TABLE
- DROP DATABASE
- DROP FUNCTION
- DROP INDEX
- DROP PROCEDURE
- DROP TABLE
- UNLOCK TABLES
- LOAD MASTER DATA
- LOCK TABLES
- RENAME TABLE
- TRUNCATE TABLE
- SET AUTOCOMMIT=1
- START TRANSACTION
我们再来举个例子看下。
- $sql1 = 'create table ScoreDetail_new(id int)';
- $sql2 = 'rename table ScoreDetail to ScoreDetail_bak';
- $sql3 = 'rename table ScoreDetail_new to ScoreDetail';
- $mysqli = new mysqli('localhost','root','','DB_Lib2Test');
- $mysqli->autocommit(false);//开始事物
- $mysqli->query($sql1);
- $mysqli->query($sql2);
- $mysqli->query($sql3);
- if(!$mysqli->errno){
- $mysqli->commit();
- echo 'ok';
- }else{
- echo 'err';
- $mysqli->rollback();
- }
在上面的示例中,假如$sql2执行出错了,$sql1照样会执行的。为什么呢?
因为rename在执行的时候,mysql默认会先执行commit,再执行rename。
Mysql不支持嵌套事务
- set autocommit=0;
- start TRANSACTION ;
- insert into person (firstName,lastName) VALUES ('tr1','tr2');
- START transaction ;
- --这个时候前一个事务已经被commit了. insert了一次.
- insert into person (firstName,lastName) VALUES ('tr1','tr2');
- commit;
- --又insert了一遍
- ROLLBACK;
START TRANSACTION:开始事务,如果已经有一个事务在运行,则会触发一个隐藏的COMMIT .
用savepoint事物嵌套
- mysql> update books set free =1, new=1;
- Query OK, 0 rows affected (0.06 sec)
- Rows matched: 79 Changed: 0 Warnings: 0
- mysql> commit;
- Query OK, 0 rows affected (0.00 sec)
- mysql> SET AUTOCOMMIT=1;
- Query OK, 0 rows affected (0.00 sec)
- mysql> begin;
- Query OK, 0 rows affected (0.00 sec)
- mysql> select free, new from books limit 1;
- +------+-----+
- | free | new |
- +------+-----+
- | 1 | 1 |
- +------+-----+
- 1 row in set (0.00 sec)
- mysql> update books set free = 0;
- Query OK, 79 rows affected (0.01 sec)
- Rows matched: 79 Changed: 79 Warnings: 0
- mysql> select free, new from books limit 1;
- +------+-----+
- | free | new |
- +------+-----+
- | 0 | 1 |
- +------+-----+
- 1 row in set (0.00 sec)
- mysql> SAVEPOINT book1;
- Query OK, 0 rows affected (0.00 sec)
- mysql> update books set new = 0;
- Query OK, 79 rows affected (0.00 sec)
- Rows matched: 79 Changed: 79 Warnings: 0
- mysql> select free, new from books limit 1;
- +------+-----+
- | free | new |
- +------+-----+
- | 0 | 0 |
- +------+-----+
- 1 row in set (0.00 sec)
- mysql> rollback to book1;
- Query OK, 0 rows affected (0.00 sec)
- mysql> select free, new from books limit 1;
- +------+-----+
- | free | new |
- +------+-----+
- | 0 | 1 |
- +------+-----+
- 1 row in set (0.00 sec)
- mysql> rollback;
- Query OK, 0 rows affected (0.05 sec)
- mysql> select free, new from books limit 1;
- +------+-----+
- | free | new |
- +------+-----+
- | 1 | 1 |
- +------+-----+
- 1 row in set (0.00 sec)
创建可抛出一个异常的函数
- <?php
- //创建可抛出一个异常的函数
- function checkNum($number){
- if($number>1){
- throw new SqlException("Value must be 1 or below");
- }
- return true;
- }
- //在 "try" 代码块中触发异常
- try{
- checkNum(2);
- //如果异常被抛出,那么下面一行代码将不会被输出
- echo 'If you see this, the number is 1 or below';
- }catch(Exception $e){
- //捕获异常
- echo 'Message: ' .$e->getMessage();
- }catch(SqlException $e){
- //捕获异常
- echo 'Message: ' .$e->getMessage();
- }
- class SqlException extends Exception{}
- ?>