第14课:事务

当你要执行多个操作,而这几个操作是捆绑在一起的,一个执行失败,其他操作即使执行成功也要回滚,就用到了事务
事务支持四大特性(ACID):原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)以及持久性(Durability)
假设DB选择Mysql,如果要使用事务 MYSQL数据库表引擎要选择innodb

热门使用场景:
转账  支付

两个例子,一个例子用PHP原生连接MYSQL函数来完成,另一个用PDO方式,让大家在熟悉事务的过程中,也熟悉PHP对MYSQL的不同操作方式
1.原生函数
<?php
//数据库连接
$conn = mysql_connect('localhost', 'root', '');
mysql_select_db('godeye', $conn);
mysql_query("SET NAMES UTF8");

//支持事务的表必须是InnoDB类型,一段事务中只能出现一次:
mysql_query('START TRANSACTION');//开始事务
mysql_query('ROLLBACK');//回滚事务
mysql_query('COMMIT');//提交事务

//如果一段事务中出现多次回滚事务,则在,提交事务时只将第一次回滚前至开始事务后对数据库的所有操作取消,第一次回滚后至提交事务前所有对数据库操作仍将有效,所以一般将回滚语句仅放在提交事务语句前
mysql_query('START TRANSACTION');
$isBad = 0;

$ins_testTable1 = "INSERT INTO testtable1(NAME,age)VALUES('first',23)";
if(!mysql_query($ins_testTable1)){
    $isBad =1;
}
//插入语句字段名有错
$ins_testTable2 = "INSERT INTO testtable1(NAME,ages)VALUES('second','24')";
if(!mysql_query($ins_testTable2)){
    $isBad =1;
}
if($isBad == 1){
    echo $isBad;
    mysql_query('ROLLBACK');
} else {
    mysql_query('COMMIT');
}
mysql_close($conn);
?>

2.PDO方式
假设为新员工创建一组条目,分配一个为23的ID。除了登记此人的基本数据之外,还需要记录他的工资。两个更新分别完成起来很简单,但通过封闭在 PDO::beginTransaction() 和PDO::commit() 调用中,可以保证在更改完成之前,其他人无法看到这些更改。如果发生了错误,catch 块回滚自事务启动以来发生的所有更改,并输出一条错误信息
<?php
try {
  $dbh = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2', 
      array(PDO::ATTR_PERSISTENT => true));
  echo "Connected\n";
} catch (Exception $e) {
  die("Unable to connect: " . $e->getMessage());
}

try {  
  $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  $dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, 0);
  $dbh->beginTransaction();
  $dbh->exec("insert into godeye (id, first, last) values (23, 'Joe', 'www.godeye.org')");
  $dbh->exec("insert into salarychange (id, amount, changedate) 
      values (23, 50000, NOW())");
  $dbh->commit();
  
} catch (Exception $e) {
  $dbh->rollBack();
  echo "Failed: " . $e->getMessage();
}
$dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, 1);
?>
注意事项:
1.谈及事务,就不得不说另一种情况:比如有个goods商品表,当商品数量大于1的时候,才能订货,但是数量的判断是个查询语句,如果恰好现在商品只有1个,恰好2个人来同时订货,第一个人查询下,发现还有一个存货,要下单,这个时候,可能第二个人已经下单完成,也就是说,实际库存已经是0了,第一个人下单应该失败的。
这个时候,就需要在查询的时候锁住表,当对应事务结束之后再解锁,保证每个事务里的查询结果都不会受到其他操作的影响
实现方式为select num from goods where id = :id for update;
通过select .... for update来锁住表
2.上面的$dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, 1);对自动提交的设置一定要注意,在事务开始之前要关闭自动提交,在事务结束之后再打开