• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • 位置: php 中文手册 -> php 绑定的扩展库

    pdo 数据对象

    连接与连接管理

    连接是通过创建 PDO 基类的实例而建立的。不管使用哪种驱动程序,都是用 PDO 类名。构造函数接收用于指定数据库源(所谓的 DSN)以及可能还包括用户名和密码(如果有的话)的参数。

    Example #1 连接到 MySQL

    <?php
    $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
    ?>
    

    如果有任何连接错误,将抛出一个PDOException异常对象。如果想处理错误状态,可以捕获异常,或者选择留给通过 set_exception_handler()设置的应用程序全局异常处理程序。

    Example #2 处理连接错误

    <?php
    try {
        $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
        foreach($dbh->query('SELECT * from FOO') as $row) {
            print_r($row);
        }
        $dbh = null;
    } catch (PDOException $e) {
        print "Error!: " . $e->getMessage() . "<br/>";
        die();
    }
    ?>
    
    Warning

    如果应用程序不在 PDO 构造函数中捕获异常,zend 引擎采取的默认动作是结束脚本并显示一个回溯跟踪,此回溯跟踪可能泄漏完整的数据库连接细节,包括用户名和密码。因此有责任去显式(通过catch语句)或隐式(通过 set_exception_handler())地捕获异常。

    连接数据成功后,返回一个 PDO 类的实例给脚本,此连接在 PDO 对象的生存周期中保持活动。要想关闭连接,需要销毁对象以确保所有剩余到它的引用都被删除,可以赋一个NULL值给对象变量。如果不明确地这么做,PHP 在脚本结束时会自动关闭连接。

    Example #3 关闭一个连接

    <?php
    $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
    // 在此使用连接
    // 现在运行完成,在此关闭连接
    $dbh = null;
    ?>
    

    很多 web 应用程序通过使用到数据库服务的持久连接获得好处。持久连接在脚本结束后不会被关闭,且被缓存,当另一个使用相同凭证的脚本连接请求时被重用。持久连接缓存可以避免每次脚本需要与数据库回话时建立一个新连接的开销,从而让 web 应用程序更快。

    Example #4 持久化连接

    <?php
    $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass, array(
        PDO::ATTR_PERSISTENT => true
    ));
    ?>
    

    Note:

    如果想使用持久连接,必须在传递给 PDO 构造函数的驱动选项数组中设置PDO::ATTR_PERSISTENT。如果是在对象初始化之后用 PDO::setAttribute()设置此属性,则驱动程序将不会使用持久连接。

    Note:

    如果使用 PDO ODBC 驱动且 ODBC 库支持 ODBC 连接池(有unixODBC 和 Windows 两种做法;可能会有更多),建议不要使用持久的 PDO 连接,而是把连接缓存留给 ODBC 连接池层处理。 ODBC 连接池在进程中与其他模块共享;如果要求 PDO 缓存连接,则此连接绝不会被返回到 ODBC 连接池,导致创建额外的连接来服务其他模块。

    Using PHP 5.4.26, pdo_pgsql with libpg 9.2.8 (self compiled). As usual PHP never explains some critical stuff in documentation. You shouldn't expect that your connection is closed when you set $dbh = null unless all you do is just instantiating PDO class. Try following:
    <?php
    $pdo = new PDO('pgsql:host=192.168.137.1;port=5432;dbname=anydb', 'anyuser', 'pw');
    sleep(5);
    $stmt = $pdo->prepare('SELECT * FROM sometable');
    $stmt->execute();
    $pdo = null;
    sleep(60);
    ?>
    Now check your database. And what a surprise! Your connection hangs for another 60 seconds. Now that might be expectable because you haven't cleared the resultset.
    <?php
    $pdo = new PDO('pgsql:host=192.168.137.160;port=5432;dbname=platin', 'cappytoi', '1111');
    sleep(5);
    $stmt = $pdo->prepare('SELECT * FROM admin');
    $stmt->execute();
    $stmt->closeCursor();
    $pdo = null;
    sleep(60);
    ?>
    What teh heck you say at this point? Still same? Here is what you need to do to close that connection:
    <?php
    $pdo = new PDO('pgsql:host=192.168.137.160;port=5432;dbname=platin', 'cappytoi', '1111');
    sleep(5);
    $stmt = $pdo->prepare('SELECT * FROM admin');
    $stmt->execute();
    $stmt->closeCursor(); // this is not even required
    $stmt = null; // doing this is mandatory for connection to get closed
    $pdo = null;
    sleep(60);
    ?>
    PDO is just one of a kind because it saves you to depend on 3rd party abstraction layers. But it becomes annoying to see there is no implementation of a "disconnect" method even though there is a request for it for 2 years. Developers underestimate the requirement of such a method. First of all, doing $stmt = null everywhere is annoying and what is most annoying is you cannot forcibly disconnect even when you set $pdo = null. It might get cleared on script's termination but this is not always possible because script termination may delayed due to slow client connection etc.
    Anyway here is how to disconnect forcibly using postgresql:
    <?php
    $pdo = new PDO('pgsql:host=192.168.137.160;port=5432;dbname=platin', 'cappytoi', '1111');
    sleep(5);
    $stmt = $pdo->prepare('SELECT * FROM admin');
    $stmt->execute();
    $pdo->query('SELECT pg_terminate_backend(pg_backend_pid());');
    $pdo = null;
    sleep(60);
    ?>
    Following may be used for MYSQL: (not guaranteed)
    KILL CONNECTION_ID()
    I would please advice people who talk about database port in reference with socket files to please read up about what a socket file is. TCP/IP uses ports, a socket file however is a direct pipe line to your database. So no, you should not replace localhost with local ip if you use a different port on your database server, because the socket file has nothing to do with your TCP/IP setup. And whenever possible, using the local socket file is much faster than establishing new TCP/IP connections on each request which is only meant for remote database servers.
    To avoid exposing your connection details should you fail to remember to catch any exception thrown by the PDO constructor you can use the following class to implicitly change the exception handler temporarily.
    <?php
    Class SafePDO extends PDO {
     
        public static function exception_handler($exception) {
          // Output the exception details
          die('Uncaught exception: ', $exception->getMessage());
        }
     
        public function __construct($dsn, $username='', $password='', $driver_options=array()) {
          // Temporarily change the PHP exception handler while we . . .
          set_exception_handler(array(__CLASS__, 'exception_handler'));
          // . . . create a PDO object
          parent::__construct($dsn, $username, $password, $driver_options);
          // Change the exception handler back to whatever it was before
          restore_exception_handler();
        }
    }
    // Connect to the database with defined constants
    $dbh = new SafePDO(PDO_DSN, PDO_USER, PDO_PASSWORD);
    ?>
    
    As http://stackoverflow.com/questions/17630772/pdo-cannot-connect-remote-mysql-server points out; sometimes when you want to connect to an external server like this:
    <?php
    $conn = new PDO('mysql:host=123.4.5.6;dbname=test_db;port=3306','username','password');
    ?>
    it will fail no matter what. However if you put a space between mysql: and host like this:
    <?php
    $conn = new PDO('mysql: host=123.4.5.6;dbname=test_db;port=3306','username','password');
    ?>
    it will magically work. I'm not sure if this applies in all cases or server setups. But I think it's worth mentioning in the docs.
    Just thought I'd add in and give an explanation as to why you need to use 127.0.0.1 if you have a different port number.
    The mysql libraries will automatically use Unix sockets if the host of "localhost" is used. To force TCP/IP you need to set an IP address.
    Please note that you cannot use persistent connections to create temporary tables in mysql/mariadb.
    Tables you create using a statement like "create temporary table TABLE1 ..." are destroyed only when the mysql session ends (not php session !). This never happens if you use a persistent connection. 
    If you create a temporary table on a persistent connection, the table will live even after the php script ends. The next php script that will try to issue the same create temporary table statement, will receive an error.
    IMHO, this fact makes persistent connections quite useless.
    Note that you can specify a port number with "port=####", but this port number will be ignored if the host is localhost. If you want to connect to a local port other than the default, use host=127.0.0.1 instead of localhost.
    For being able to retrieve information from the db in utf-8 the connection assignment has to add to the dsn `charset=utf8`:
    <?php
    $dbh = new PDO('mysql:host=localhost;dbname=test;charset=utf8', $user, $pass);
    ?>
    
    If you are using PHP 5.4 and later, you can no longer use persistent connections when you have your own database class that derives from the native PDO object. If you do, you will get segmentation faults during the PHP process shutdown.
    Please see this bug report for more information: https://bugs.php.net/bug.php?id=63176
    If you want to keep connection after fork exit, you can kill with SIGKILL forked process.
    <?php
    $dbh = new PDO('pgsql:host=localhost;dbname=test', $user, $pass);
    $pid = pcntl_fork();
    if($pid == 0){
        // forked process 'll exit immediately
        exit;
    }
    sleep(1);
    $statement = $dbh->query('select 1');
    var_dump($statement);
    ?>
    Result: false
    <?php
    $dbh = new PDO('pgsql:host=localhost;dbname=test', $user, $pass);
    $pid = pcntl_fork();
    if($pid == 0){
        // use sigkill to close process
        register_shutdown_function(function(){
            posix_kill(getmypid(), SIGKILL);
        });
        // forked process 'll exit immediately
        exit;
    }
    sleep(1);
    $statement = $dbh->query('select 1');
    var_dump($statement);
    ?>
    Result: object(PDOStatement)#3 (1) {
     ["queryString"]=>
     string(8) "select 1"
    }
    On connection errors, the PDO constructor seems to do two things no matter your PDO::ATTR_ERRMODE setting:
    1. Trigger a warning
    2. Throw a PDOException
    If you set the PDO::ATTR_ERRMODE parameter, it will only take effect on further operations.
    Hello guys!
    Has anyone used the ORACLE WALLET feature in PHP or Java?
    https://docs.oracle.com/middleware/1213/wls/JDBCA/oraclewallet.htm#JDBCA596
    I would like to know how to implement it because I can not implement. We use PDO + PHP in all applications and now there is this demand of the DBA.
    Thank you

    事务与自动提交

    现在通过 PDO 连接上了,在开始进行查询前,必须先理解 PDO 是如何管理事务的。事务支持四大特性(ACID):原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)以及持久性(Durability)。通俗地讲,在一个事务中执行的任何操作,即使是分阶段执行的,也能保证安全地应用于数据库,并在提交时不会受到来自其他连接的干扰。事务操作也可以根据请求自动撤销(假设还没有提交),这使得在脚本中处理错误更加容易。

    事务通常是通过把一批更改“积蓄”起来然后使之同时生效而实现的;这样做的好处是可以大大地提供这些更改的效率。换句话说,事务可以使脚本更快,而且可能更健壮(不过需要正确地使用事务才能获得这样的好处)。

    不幸的是,并非每种数据库都支持事务,因此当第一次打开连接时,PDO 需要在所谓的“自动提交”模式下运行。自动提交模式意味着,如果数据库支持,运行的每个查询都有它自己的隐式事务,如果数据库不支持事务,则没有。如果需要一个事务,则必须用 PDO::beginTransaction()方法来启动。如果底层驱动不支持事务,则抛出一个 PDOException 异常(不管错误处理设置是怎样的,这都是一个严重的错误状态)。一旦开始了事务,可用 PDO::commit()或 PDO::rollBack()来完成,这取决于事务中的代码是否运行成功。

    Warning

    PDO 仅在驱动层检查是否具有事务处理能力。如果某些运行时条件意味着事务不可用,且数据库服务接受请求去启动一个事务,PDO::beginTransaction()将仍然返回TRUE而且没有错误。

    试着在 MySQL 数据库的 MyISAM 数据表中使用事务就是一个很好的例子。

    当脚本结束或连接即将被关闭时,如果尚有一个未完成的事务,那么 PDO 将自动回滚该事务。这种安全措施有助于在脚本意外终止时避免出现不一致的情况——如果没有显式地提交事务,那么假设是某个地方出错了,所以执行回滚来保证数据安全。

    Warning

    只有通过 PDO::beginTransaction()启动一个事务后,才可能发生自动回滚。如果手动发出一条查询启动事务,则 PDO 无法知晓,从而在必要时不能进行回滚。

    Example #1 在事务中执行批处理

    在下面例子中,假设为新员工创建一组条目,分配一个为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->beginTransaction();
      $dbh->exec("insert into staff (id, first, last) values (23, 'Joe', 'Bloggs')");
      $dbh->exec("insert into salarychange (id, amount, changedate) 
          values (23, 50000, NOW())");
      $dbh->commit();
      
    } catch (Exception $e) {
      $dbh->rollBack();
      echo "Failed: " . $e->getMessage();
    }
    ?>
    

    并不局限于在事务中更改,也可以发出复杂的查询来提取数据,还可以使用那些信息来构建更多的更改和查询;当事务激活时,可以保证其他人在操作进行当中无法作出更改。想更进一步阅读关于事务的信息,可参考数据库服务提供的文档。

    Typically data definition language clauses (DDL) will trigger the database engine to automatically commit:
    http://dev.mysql.com/doc/refman/5.0/en/implicit-commit.html
    Many other databases (e.g. Oracle) will implicitly commit before and after running DDL statements.
    Some DBMSs allow DDL (table creation/alteration) within transactions, some do not. Asking "Does my DBMS allow DDL within transactions without forcing a commit?" gives the following example answers:
    CUBRID: Yes
    DB2 UDB: Yes
    Firebird: Yes
    Informix: Yes
    MySQL: No
    Oracle: No (although schema upgrades can be rolled out using "edition-based redefinition")
    PostgreSQL: Yes
    SQLite: Yes
    SQL Server: Sometimes, depending on isolation level, type of command, etc.
    Sybase: Yes

    预处理语句与存储过程

    很多更成熟的数据库都支持预处理语句的概念。什么是预处理语句?可以把它看作是想要运行的 SQL 的一种编译过的模板,它可以使用变量参数进行定制。预处理语句可以带来两大好处:

    • 查询仅需解析(或预处理)一次,但可以用相同或不同的参数执行多次。当查询准备好后,数据库将分析、编译和优化执行该查询的计划。对于复杂的查询,此过程要花费较长的时间,如果需要以不同参数多次重复相同的查询,那么该过程将大大降低应用程序的速度。通过使用预处理语句,可以避免重复分析/编译/优化周期。简言之,预处理语句占用更少的资源,因而运行得更快。
    • 提供给预处理语句的参数不需要用引号括起来,驱动程序会自动处理。如果应用程序只使用预处理语句,可以确保不会发生SQL 注入。(然而,如果查询的其他部分是由未转义的输入来构建的,则仍存在 SQL 注入的风险)。

    预处理语句如此有用,以至于它们唯一的特性是在驱动程序不支持的时PDO 将模拟处理。这样可以确保不管数据库是否具有这样的功能,都可以确保应用程序可以用相同的数据访问模式。

    Example #1 用预处理语句进行重复插入

    下面例子通过用namevalue替代相应的命名占位符来执行一个插入查询

    <?php
    $stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
    $stmt->bindParam(':name', $name);
    $stmt->bindParam(':value', $value);
    // 插入一行
    $name = 'one';
    $value = 1;
    $stmt->execute();
    //  用不同的值插入另一行
    $name = 'two';
    $value = 2;
    $stmt->execute();
    ?>
    

    Example #2 用预处理语句进行重复插入

    下面例子通过用namevalue取代?占位符的位置来执行一条插入查询。

    <?php
    $stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (?, ?)");
    $stmt->bindParam(1, $name);
    $stmt->bindParam(2, $value);
    // 插入一行
    $name = 'one';
    $value = 1;
    $stmt->execute();
    // 用不同的值插入另一行
    $name = 'two';
    $value = 2;
    $stmt->execute();
    ?>
    

    Example #3 使用预处理语句获取数据

    下面例子获取数据基于键值已提供的形式。用户的输入被自动用引号括起来,因此不会有 SQL 注入攻击的危险。

    <?php
    $stmt = $dbh->prepare("SELECT * FROM REGISTRY where name = ?");
    if ($stmt->execute(array($_GET['name']))) {
      while ($row = $stmt->fetch()) {
        print_r($row);
      }
    }
    ?>
    

    如果数据库驱动支持,应用程序还可以绑定输出和输入参数。输出参数通常用于从存储过程获取值。输出参数使用起来比输入参数要稍微复杂一些,因为当绑定一个输出参数时,必须知道给定参数的长度。如果为参数绑定的值大于建议的长度,就会产生一个错误。

    Example #4 带输出参数调用存储过程

    <?php
    $stmt = $dbh->prepare("CALL sp_returns_string(?)");
    $stmt->bindParam(1, $return_value, PDO::PARAM_STR, 4000); 
    // 调用存储过程
    $stmt->execute();
    print "procedure returned $return_value\n";
    ?>
    

    还可以指定同时具有输入和输出值的参数,其语法类似于输出参数。在下一个例子中,字符串“hello”被传递给存储过程,当存储过程返回时,hello 被替换为该存储过程返回的值。

    Example #5 带输入/输出参数调用存储过程

    <?php
    $stmt = $dbh->prepare("CALL sp_takes_string_returns_string(?)");
    $value = 'hello';
    $stmt->bindParam(1, $value, PDO::PARAM_STR|PDO::PARAM_INPUT_OUTPUT, 4000); 
    // 调用存储过程
    $stmt->execute();
    print "procedure returned $value\n";
    ?>
    

    Example #6 占位符的无效使用

    <?php
    $stmt = $dbh->prepare("SELECT * FROM REGISTRY where name LIKE '%?%'");
    $stmt->execute(array($_GET['name']));
    // 占位符必须被用在整个值的位置
    $stmt = $dbh->prepare("SELECT * FROM REGISTRY where name LIKE ?");
    $stmt->execute(array("%$_GET[name]%"));
    ?>
    
    Note that when using name parameters with bindParam, the name itself, cannot contain a dash '-'. 
    example:
    <?php
    $stmt = $dbh->prepare ("INSERT INTO user (firstname, surname) VALUES (:f-name, :s-name)");
    $stmt -> bindParam(':f-name', 'John');
    $stmt -> bindParam(':s-name', 'Smith');
    $stmt -> execute();
    ?>
    The dashes in 'f-name' and 's-name' should be replaced with an underscore or no dash at all.
    See http://bugs.php.net/43130
    Adam

    错误与错误处理

    PDO 提供了三种不同的错误处理模式,以满足不同风格的应用开发:

    • PDO::ERRMODE_SILENT

      此为默认模式。 PDO 将只简单地设置错误码,可使用 PDO::errorCode()和 PDO::errorInfo()方法来检查语句和数据库对象。如果错误是由于对语句对象的调用而产生的,那么可以调用那个对象的 PDOStatement::errorCode()或 PDOStatement::errorInfo()方法。如果错误是由于调用数据库对象而产生的,那么可以在数据库对象上调用上述两个方法。

    • PDO::ERRMODE_WARNING

      除设置错误码之外,PDO 还将发出一条传统的 E_WARNING 信息。如果只是想看看发生了什么问题且不中断应用程序的流程,那么此设置在调试/测试期间非常有用。

    • PDO::ERRMODE_EXCEPTION

      除设置错误码之外,PDO 还将抛出一个 PDOException 异常类并设置它的属性来反射错误码和错误信息。此设置在调试期间也非常有用,因为它会有效地放大脚本中产生错误的点,从而可以非常快速地指出代码中有问题的潜在区域(记住:如果异常导致脚本终止,则事务被自动回滚)。

      异常模式另一个非常有用的是,相比传统 PHP 风格的警告,可以更清晰地构建自己的错误处理,而且比起静默模式和显式地检查每种数据库调用的返回值,异常模式需要的代码/嵌套更少。

      See Exceptions for more information about Exceptions in PHP.

    PDO 使用 SQL-92 SQLSTATE 来规范错误码字符串;不同 PDO 驱动程序负责将它们的本地代码映射为适当的 SQLSTATE 代码。PDO::errorCode()方法返回一个单独的 SQLSTATE 码。如果需要更多此错误的细节信息,PDO 还提供了一个 PDO::errorInfo()方法来返回一个包含 SQLSTATE 码、特定驱动错误码以及此驱动的错误字符串的数组。

    Example #1 创建 PDO 实例并设置错误模式

    <?php
    $dsn = 'mysql:dbname=testdb;host=127.0.0.1';
    $user = 'dbuser';
    $password = 'dbpass';
    try {
        $dbh = new PDO($dsn, $user, $password);
        $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $e) {
        echo 'Connection failed: ' . $e->getMessage();
    }
    ?>
    

    Note:

    不管当前是否设置了PDO::ATTR_ERRMODE,如果连接失败,PDO::__construct()将总是抛出一个 PDOException 异常。未捕获异常是致命的。

    Example #2 创建 PDO 实例并在构造函数中设置错误模式

    <?php
    $dsn = 'mysql:dbname=test;host=127.0.0.1';
    $user = 'googleguy';
    $password = 'googleguy';
    /*
        使用 try/catch 围绕构造函数仍然有效,即使设置了 ERRMODE 为 WARNING,
        因为如果连接失败,PDO::__construct 将总是抛出一个  PDOException 异常。
    */
    try {
        $dbh = new PDO($dsn, $user, $password, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING));
    } catch (PDOException $e) {
        echo 'Connection failed: ' . $e->getMessage();
        exit;
    }
    // 这里将导致 PDO 抛出一个 E_WARNING 级别的错误,而不是 一个异常 (当数据表不存在时)
    $dbh->query("SELECT wrongcolumn FROM wrongtable");
    ?>
    

    以上例程会输出:

    Warning: PDO::query(): SQLSTATE[42S02]: Base table or view not found: 1146 Table 'test.wrongtable' doesn't exist in
    /tmp/pdo_test.php on line 18
    
    Setting the PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION applies to both PDO and PDO::PDOStatement objects. Also, exceptions are thrown by: PDO::beginTransaction(), PDO::prepare(), PDOStatement::execute(), PDO::commit(), PDOStatement::fetch(), PDOStatement::fetchAll() and so on... Some of these are specified in their respective documentations as to return 'false' in case of an error.

    大对象(LOBs)

    应用程序在某一时刻,可能需要在数据库中存储“大”数据。“大”通常意味着“大约 4kb 或以上”,尽管某些数据库在数据达到“大”之前可以轻松地处理多达 32kb 的数据。大对象本质上可能是文本或二进制。在 PDOStatement::bindParam()或 PDOStatement::bindColumn())调用中使用PDO::PARAM_LOB类型码可以让 PDO 使用大数据类型。PDO::PARAM_LOB告诉 PDO 作为流来映射数据,以便能使用 PHP Streams API 来操作。

    Example #1 从数据库中显示一张图片

    下面例子绑定一个 LOB 到$lob 变量,然后用 fpassthru()将其发送到浏览器。因为 LOB 代表一个流,所以类似 fgets()、fread()以及 stream_get_contents()这样的函数都可以用在它上面。

    <?php
    $db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2');
    $stmt = $db->prepare("select contenttype, imagedata from images where id=?");
    $stmt->execute(array($_GET['id']));
    $stmt->bindColumn(1, $type, PDO::PARAM_STR, 256);
    $stmt->bindColumn(2, $lob, PDO::PARAM_LOB);
    $stmt->fetch(PDO::FETCH_BOUND);
    header("Content-Type: $type");
    fpassthru($lob);
    ?>
    

    Example #2 插入一张图片到数据库

    下面例子打开一个文件并将文件句柄传给 PDO 来做为一个 LOB 插入。PDO尽可能地让数据库以最有效的方式获取文件内容。

    <?php
    $db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2');
    $stmt = $db->prepare("insert into images (id, contenttype, imagedata) values (?, ?, ?)");
    $id = get_new_id(); // 调用某个函数来分配一个新 ID
    // 假设处理一个文件上传
    // 可以在 PHP 文档中找到更多的信息
    $fp = fopen($_FILES['file']['tmp_name'], 'rb');
    $stmt->bindParam(1, $id);
    $stmt->bindParam(2, $_FILES['file']['type']);
    $stmt->bindParam(3, $fp, PDO::PARAM_LOB);
    $db->beginTransaction();
    $stmt->execute();
    $db->commit();
    ?>
    

    Example #3 插入一张图片到数据库:Oracle

    对于从文件插入一个 lob,Oracle略有不同。必须在事务之后进行插入,否则当执行查询时导致新近插入 LOB 将以0长度被隐式提交:

    <?php
    $db = new PDO('oci:', 'scott', 'tiger');
    $stmt = $db->prepare("insert into images (id, contenttype, imagedata) " .
    "VALUES (?, ?, EMPTY_BLOB()) RETURNING imagedata INTO ?");
    $id = get_new_id(); // 调用某个函数来分配一个新 ID
    // 假设处理一个文件上传
    // 可以在 PHP 文档中找到更多的信息
    $fp = fopen($_FILES['file']['tmp_name'], 'rb');
    $stmt->bindParam(1, $id);
    $stmt->bindParam(2, $_FILES['file']['type']);
    $stmt->bindParam(3, $fp, PDO::PARAM_LOB);
    $stmt->beginTransaction();
    $stmt->execute();
    $stmt->commit();
    ?>
    
    A big gotcha exists for Oracle users.
    You have to save CLOB objects using PDO::PARAM_STR, not PDO::PARAM_LOB.
    But you MUST send the 4th argument, usually strlen($subject) or you get a LONG error.
    PDOStatement's methods bindParam and bindValue also work with strings, as in:
    <?php
     $data = file_get_contents($filename);
     $stmt->bindValue(1, $data, PDO::PARAM_LOB);
     //...
    ?>
    This was the only way I could make it work with PostgreSQL.
    There seems to be a bug that affects example 1 above. PDO::PARAM_LOB when used with pdo::bindColumn() is supposed to return a stream but it returns a string. Passing this string to fpassthru() then triggers an error with the message 'supplied argument is not a valid stream resource'. This has been reported in bug #40913. The work around is to do the following:
    <?php
    $stmt = $db->prepare("select contenttype, imagedata from images where id=?");
    $stmt->execute(array($_GET['id']));
    $stmt->bindColumn(1, $type, PDO::PARAM_STR, 256);
    $stmt->bindColumn(2, $lob, PDO::PARAM_LOB);
    $stmt->fetch(PDO::FETCH_BOUND);
    header("Content-Type: $type");
    echo($lob);
    ?>
    Since the browser is expecting an image after the call to header() writing the string representation of the binary output with echo() has the same affect as calling fpassthru().
    For selecting data out of Postgres, the data type of the column in the table determined if the parameter bound with PARAM_LOB returned a string or returned a resource.
    <?php
    // create table log ( data text ) ;
    $geth = $dbh->prepare('select data from log ');
    $geth->execute();
    $geth->bindColumn(1, $dataString, PDO::PARAM_LOB);
    $geth->fetch(PDO::FETCH_BOUND);
    echo ($dataString); // $dataString is a string
    // create table log ( data bytea ) ;
    $geth = $dbh->prepare('select data from log');
    $geth->execute();
    $geth->bindColumn(1, $dataFH, PDO::PARAM_LOB);
    $geth->fetch(PDO::FETCH_BOUND);
    fpassthru($dataFH); // $dataFH is a resource
    I spend a lot of time trying to get this to work, but no matter what I did PDO corrupted my data.
    I finally discovered that I had been using:
    $pdo->exec('SET CHARACTER SET utf8');
    in the TRY part of my connection script.
    This off course doesn't work when you feed binary input to PDO using the parameter lob.
    The DBMSs that are listed above have these (default) limits on the maximum size of a char string. The maximum is given in bytes so the number of characters storable can be smaller if a multibyte encoding is used.
    CUBRID:  16kB
    SQL Server: 2GB
    Firebird: 32kB
    IBM Db2:  32kB
    Informix: 32kB
    MySQL:   16kB
    Oracle:   2kB
    PostgreSQL: 1GB
    SQLite:   1 billion bytes
    4D: Unknown, but LOBs are limited to 2GB.
    I find it easier to use stream_get_contens to fetch the data of a lob using the file handle.
    <?php
    $stmt = $pdo->con->prepare( 'select * from filetable' );
    $stmt->execute();
    $res = $stmt->fetchAll( PDO::FETCH_ASSOC );
    for( $i=0; $i<count($res); $i++ ){
     $filename = "C:/tmp/".$res[$i]['FILE_ID'].'.xml';
     $content = stream_get_contents( $res[$i]['DATA_FILE'] );
     file_put_contents( $filename, $content );
    }
    ?>