<?php
include_once $_SERVER['DOCUMENT_ROOT'] . '/include/shared-manual.inc';
$TOC = array();
$TOC_DEPRECATED = array();
$PARENTS = array();
include_once dirname(__FILE__) ."/toc/book.pdo.inc";
$setup = array (
  'home' => 
  array (
    0 => 'index.php',
    1 => 'PHP Manual',
  ),
  'head' => 
  array (
    0 => 'UTF-8',
    1 => 'zh',
  ),
  'this' => 
  array (
    0 => 'pdo.transactions.php',
    1 => '事务与自动提交',
    2 => '事务与自动提交',
  ),
  'up' => 
  array (
    0 => 'book.pdo.php',
    1 => 'PDO',
  ),
  'prev' => 
  array (
    0 => 'pdo.connections.php',
    1 => '连接与连接管理',
  ),
  'next' => 
  array (
    0 => 'pdo.prepared-statements.php',
    1 => '预处理语句与存储过程',
  ),
  'alternatives' => 
  array (
  ),
  'source' => 
  array (
    'lang' => 'zh',
    'path' => 'reference/pdo/transactions.xml',
  ),
  'history' => 
  array (
  ),
);
$setup["toc"] = $TOC;
$setup["toc_deprecated"] = $TOC_DEPRECATED;
$setup["parents"] = $PARENTS;
manual_setup($setup);

contributors($setup);

?>
<div id="pdo.transactions" class="chapter">
 <h1 class="title">事务与自动提交</h1>

 <p class="para">
  现在通过 PDO 连接上了，在开始进行查询前，必须先理解 PDO 是如何管理事务的。事务支持四大特性（ACID）：原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）以及持久性（Durability）。通俗地讲，在一个事务中执行的任何操作，即使是分阶段执行的，也能保证安全地应用于数据库，并在提交时不会受到来自其他连接的干扰。事务操作也可以根据请求自动撤销（假设还没有提交），这使得在脚本中处理错误更加容易。
 </p>
 <p class="para">
  事务通常是通过把一批更改“积蓄”起来然后使之同时生效而实现的；这样做的好处是可以大大地提供这些更改的效率。换句话说，事务可以使脚本更快，而且可能更健壮（不过需要正确地使用事务才能获得这样的好处）。
 </p>
 <p class="para">
  不幸的是，并非每种数据库都支持事务，因此当第一次打开连接时，PDO 
  需要在所谓的“自动提交”模式下运行。自动提交模式意味着，如果数据库支持，运行的每个查询都有它自己的隐式事务，如果数据库不支持事务，则没有。如果需要一个事务，则必须用
  <span class="methodname"><a href="pdo.begintransaction.php" class="methodname">PDO::beginTransaction()</a></span> 方法来启动。如果底层驱动不支持事务，则抛出一个 PDOException
  异常（不管错误处理设置是怎样的，这都是一个严重的错误状态）。一旦开始了事务，可用 <span class="methodname"><a href="pdo.commit.php" class="methodname">PDO::commit()</a></span>
  或 <span class="methodname"><a href="pdo.rollback.php" class="methodname">PDO::rollBack()</a></span> 来完成，这取决于事务中的代码是否运行成功。
 </p>
 <div class="warning"><strong class="warning">警告</strong>
  <p class="para">
   PDO 仅在驱动层检查是否具有事务处理能力。如果某些运行时条件意味着事务不可用，且数据库服务接受请求去启动一个事务，<span class="methodname"><a href="pdo.begintransaction.php" class="methodname">PDO::beginTransaction()</a></span> 将仍然返回 <strong><code><a href="reserved.constants.php#constant.true">true</a></code></strong> 而且没有错误。
  </p>
  <p class="para">
   试着在 MySQL 数据库的 MyISAM 数据表中使用事务就是一个很好的示例。
  </p>
 </div>
 <div class="warning"><strong class="warning">警告</strong>
  <p class="para">
   <em>DDL 语句的隐式提交：</em>
   某些数据库在事务中执行诸如 <code class="literal">DROP TABLE</code> 或 <code class="literal">CREATE TABLE</code>
   等数据库定义语言（DDL）语句时，会自动发出隐式提交。这意味着在同一事务中之前所做的任何更改都将<em>自动提交</em>，且无法回滚。
  </p>
  <p class="para">
   <code class="literal">MySQL</code> 和 <code class="literal">Oracle</code> 就是表现出这种行为的示例数据库。
  </p>
 </div>
 <p class="para">
  <div class="example" id="example-1">
   <p><strong>示例 #1 隐式提交示例</strong></p>
   <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br />$pdo</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">beginTransaction</span><span style="color: #007700">();<br /></span><span style="color: #0000BB">$pdo</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">exec</span><span style="color: #007700">(</span><span style="color: #DD0000">"INSERT INTO users (name) VALUES ('Rasmus')"</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$pdo</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">exec</span><span style="color: #007700">(</span><span style="color: #DD0000">"CREATE TABLE test (id INT PRIMARY KEY)"</span><span style="color: #007700">); </span><span style="color: #FF8000">// 这里发生了隐式提交<br /></span><span style="color: #0000BB">$pdo</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">rollBack</span><span style="color: #007700">(); </span><span style="color: #FF8000">// 这不会撤销 MySQL 或 Oracle 中的 INSERT/CREATE 操作<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
   </div>

  </div>
 </p>
 <p class="para">
  <em>最佳实践：</em>若使用强制此行为的数据库，则避免在事务内执行 DDL 语句。必要时，将 DDL 操作与事务逻辑分离。
 </p>
 <p class="para">
  当脚本结束或连接即将被关闭时，如果尚有一个未完成的事务，那么 PDO 将自动回滚该事务。这种安全措施有助于在脚本意外终止时避免出现不一致的情况——如果没有显式地提交事务，那么假设是某个地方出错了，所以执行回滚来保证数据安全。
 </p>
 <div class="warning"><strong class="warning">警告</strong>
  <p class="para">
   只有通过 <span class="methodname"><a href="pdo.begintransaction.php" class="methodname">PDO::beginTransaction()</a></span> 启动一个事务后，才可能发生自动回滚。如果手动发出一条查询启动事务，则
   PDO 无法知晓，从而在必要时不能进行回滚。
  </p>
 </div>
 <p class="para">
  <div class="example" id="example-2"><p><strong>示例 #2 在事务中执行批处理</strong></p>
   <div class="example-contents"><p>
    在下面示例中，假设为新员工创建一组条目，分配一个为 23 的
    ID。除了登记此人的基本数据之外，还需要记录他的工资。两个更新分别完成起来很简单，但通过封闭在
    <span class="methodname"><a href="pdo.begintransaction.php" class="methodname">PDO::beginTransaction()</a></span> 和 <span class="methodname"><a href="pdo.commit.php" class="methodname">PDO::commit()</a></span>
    调用中，可以保证在更改完成之前，其他人无法看到这些更改。如果发生了错误，catch 块回滚自事务启动以来发生的所有更改，并输出一条错误信息。
   </p></div>
   <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /></span><span style="color: #007700">try {<br />  </span><span style="color: #0000BB">$dbh </span><span style="color: #007700">= new </span><span style="color: #0000BB">PDO</span><span style="color: #007700">(</span><span style="color: #DD0000">'odbc:SAMPLE'</span><span style="color: #007700">, </span><span style="color: #DD0000">'db2inst1'</span><span style="color: #007700">, </span><span style="color: #DD0000">'ibmdb2'</span><span style="color: #007700">, <br />      array(</span><span style="color: #0000BB">PDO</span><span style="color: #007700">::</span><span style="color: #0000BB">ATTR_PERSISTENT </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">true</span><span style="color: #007700">));<br />  echo </span><span style="color: #DD0000">"Connected\n"</span><span style="color: #007700">;<br />} catch (</span><span style="color: #0000BB">Exception $e</span><span style="color: #007700">) {<br />  die(</span><span style="color: #DD0000">"Unable to connect: " </span><span style="color: #007700">. </span><span style="color: #0000BB">$e</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">getMessage</span><span style="color: #007700">());<br />}<br /><br />try {  <br />  </span><span style="color: #0000BB">$dbh</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">setAttribute</span><span style="color: #007700">(</span><span style="color: #0000BB">PDO</span><span style="color: #007700">::</span><span style="color: #0000BB">ATTR_ERRMODE</span><span style="color: #007700">, </span><span style="color: #0000BB">PDO</span><span style="color: #007700">::</span><span style="color: #0000BB">ERRMODE_EXCEPTION</span><span style="color: #007700">);<br /><br />  </span><span style="color: #0000BB">$dbh</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">beginTransaction</span><span style="color: #007700">();<br />  </span><span style="color: #0000BB">$dbh</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">exec</span><span style="color: #007700">(</span><span style="color: #DD0000">"insert into staff (id, first, last) values (23, 'Joe', 'Bloggs')"</span><span style="color: #007700">);<br />  </span><span style="color: #0000BB">$dbh</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">exec</span><span style="color: #007700">(</span><span style="color: #DD0000">"insert into salarychange (id, amount, changedate) <br />      values (23, 50000, NOW())"</span><span style="color: #007700">);<br />  </span><span style="color: #0000BB">$dbh</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">commit</span><span style="color: #007700">();<br />  <br />} catch (</span><span style="color: #0000BB">Exception $e</span><span style="color: #007700">) {<br />  </span><span style="color: #0000BB">$dbh</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">rollBack</span><span style="color: #007700">();<br />  echo </span><span style="color: #DD0000">"Failed: " </span><span style="color: #007700">. </span><span style="color: #0000BB">$e</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">getMessage</span><span style="color: #007700">();<br />}<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
   </div>

  </div>
 </p>
 <p class="para">
  并不局限于在事务中更改，也可以发出复杂的查询来提取数据，还可以使用那些信息来构建更多的更改和查询；当事务激活时，可以保证其他人在操作进行当中无法作出更改。想更进一步阅读关于事务的信息，可参考数据库服务提供的文档。
 </p>
</div>
<?php manual_footer($setup); ?>