引言:
PHP 调用 PostgreSQL 数据库主要通过 pgsql 扩展和 PDO_PGSQL 驱动实现。本文系统讲解环境配置、两种连接方式、SQL 语句执行、预处理语句防护 SQL 注入、事务处理、错误处理机制及 JSON/UUID 等 PostgreSQL 特有数据类型操作,帮助 PHP 开发者安全高效地连接和使用 PostgreSQL 数据库。
PHP 的 PostgreSQL 数据库调用完全指南
PostgreSQL 是一种功能强大的开源关系型数据库,与 PHP 的配合在现代 Web 开发中非常常见。相比其他数据库,PostgreSQL 在复杂查询、数据完整性和高级数据类型支持方面有着独特优势。PHP 连接 PostgreSQL 主要通过两种官方扩展来实现:pgsql 扩展和 PDO_PGSQL 驱动。本文将系统讲解从环境配置到连接操作,再到数据增删改查和安全实践的完整流程。
一、环境准备:安装 PostgreSQL 扩展
在开始编码之前,需要先确保 PHP 环境已经安装了连接 PostgreSQL 所需的扩展。
各平台安装方式
Ubuntu/Debian 系统:
1
| sudo apt-get install php-pgsql
|
安装完成后,pgsql 和 pdo_pgsql 扩展会自动启用。
CentOS/RHEL 系统:
1
| sudo yum install php-pgsql
|
Windows 系统:
下载 PHP 后,在 php.ini 配置文件中取消 extension=php_pgsql.dll 和 extension=php_pdo_pgsql.dll 前的注释符号(分号),保存后重启 Web 服务器即可。
macOS 系统(使用 Homebrew):
1 2 3 4
| brew install php@8.2 brew install postgresql@15
pecl install pdo_pgsql
|
通过源码编译安装
如果需要从源码编译 PHP 并包含 PostgreSQL 支持,可以在编译时添加以下选项:
1
| ./configure --with-pgsql[=DIR] --with-pdo-pgsql[=DIR]
|
其中 [=DIR] 是 PostgreSQL 的安装目录或 pg_config 的路径。
验证扩展是否安装成功
正常输出应包含 pgsql 和 pdo_pgsql,表示扩展已成功加载。
二、两种连接方式详解
PHP 连接 PostgreSQL 主要有两种方式,各有侧重。
方式一:pgsql 扩展
pgsql 扩展是 PHP 专门为 PostgreSQL 设计的原生扩展,提供了一系列以 pg_ 为前缀的函数。它更专注于 PostgreSQL 的特性,适合只需要连接 PostgreSQL 且对性能有要求的项目。
连接参数说明:
| 参数 |
说明 |
默认值 |
host |
数据库服务器地址 |
localhost |
port |
数据库端口 |
5432 |
dbname |
数据库名称 |
用户名 |
user |
数据库用户名 |
操作系统用户名 |
password |
数据库密码 |
空 |
sslmode |
SSL 模式(require/disable/prefer) |
prefer |
示例:使用 pgsql 扩展连接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php $host = "localhost"; $port = "5432"; $dbname = "mydatabase"; $user = "myuser"; $password = "mypassword";
$connection_string = "host=$host port=$port dbname=$dbname user=$user password=$password"; $dbconn = pg_connect($connection_string);
if ($dbconn) { echo "成功连接到 PostgreSQL 数据库!"; } else { echo "连接失败:" . pg_last_error(); } ?>
|
使用 SSL 连接:
1 2 3 4 5 6 7
| <?php $connection_string = "host=localhost port=5432 dbname=mydatabase user=myuser password=mypassword sslmode=require"; $dbconn = pg_connect($connection_string); if (!$dbconn) { die("SSL 连接失败:" . pg_last_error()); } ?>
|
方式二:PDO_PGSQL 驱动
PDO(PHP Data Objects)提供了统一的数据库访问接口,使用 PDO_PGSQL 驱动可以让代码在多种数据库之间平滑切换,可移植性更好。
示例:使用 PDO_PGSQL 连接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php $dsn = "pgsql:host=localhost;port=5432;dbname=mydatabase"; $user = "myuser"; $password = "mypassword";
try { $pdo = new PDO($dsn, $user, $password); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); echo "成功连接到 PostgreSQL 数据库!"; } catch (PDOException $e) { die("连接失败:" . $e->getMessage()); } ?>
|
两种方式对比
| 特性 |
pgsql 扩展 |
PDO_PGSQL |
| 数据库支持 |
仅 PostgreSQL |
支持 12+ 种数据库 |
| 接口风格 |
函数式 |
面向对象 |
| 预处理语句 |
支持(pg_prepare/pg_execute) |
支持(prepare/execute) |
| 命名参数 |
不支持(仅位置占位符 $1, $2) |
支持命名参数 :name |
| 异常处理 |
手动检查 pg_last_error() |
原生支持 try-catch |
| 适用场景 |
PostgreSQL 专属高性能项目 |
跨数据库项目或需要统一接口 |
三、核心数据库操作
查询数据(SELECT)
pgsql 扩展查询示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <?php $dbconn = pg_connect("host=localhost dbname=mydatabase user=myuser password=mypassword");
if (!$dbconn) { die("连接失败:" . pg_last_error()); }
$query = "SELECT id, name, quantity FROM inventory ORDER BY id"; $result = pg_query($dbconn, $query);
if (!$result) { die("查询失败:" . pg_last_error($dbconn)); }
while ($row = pg_fetch_assoc($result)) { echo "ID: " . $row['id'] . " - Name: " . $row['name'] . " - Quantity: " . $row['quantity'] . "<br>"; }
while ($row = pg_fetch_object($result)) { echo "ID: " . $row->id . " - Name: " . $row->name . " - Quantity: " . $row->quantity . "<br>"; }
while ($row = pg_fetch_row($result)) { echo "Data row = (" . implode(", ", $row) . ") <br>"; }
pg_free_result($result); pg_close($dbconn); ?>
|
PDO 查询示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php try { $pdo = new PDO("pgsql:host=localhost;dbname=mydatabase", "myuser", "mypassword"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $sql = "SELECT id, name, quantity FROM inventory ORDER BY id"; $stmt = $pdo->query($sql); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { echo "ID: " . $row['id'] . " - Name: " . $row['name'] . " - Quantity: " . $row['quantity'] . "<br>"; } while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { echo "ID: " . $row['id'] . " - Name: " . $row['name'] . " - Quantity: " . $row['quantity'] . "<br>"; } } catch (PDOException $e) { echo "查询失败:" . $e->getMessage(); } ?>
|
插入数据(INSERT)
使用 pgsql 扩展插入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php $dbconn = pg_connect("host=localhost dbname=mydatabase user=myuser password=mypassword");
if (!$dbconn) { die("连接失败:" . pg_last_error()); }
$name = "banana"; $quantity = 150; $query = "INSERT INTO inventory (name, quantity) VALUES ('$name', $quantity)"; $result = pg_query($dbconn, $query);
if ($result) { echo "数据插入成功"; } else { echo "插入失败:" . pg_last_error($dbconn); }
pg_close($dbconn); ?>
|
使用 PDO 插入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php try { $pdo = new PDO("pgsql:host=localhost;dbname=mydatabase", "myuser", "mypassword"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $sql = "INSERT INTO inventory (name, quantity) VALUES ('banana', 150)"; $pdo->exec($sql); $lastId = $pdo->lastInsertId('inventory_id_seq'); echo "数据插入成功,ID: " . $lastId; } catch (PDOException $e) { echo "插入失败:" . $e->getMessage(); } ?>
|
更新数据(UPDATE)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php
$new_quantity = 200; $name = 'banana'; $query = "UPDATE inventory SET quantity = $new_quantity WHERE name = '$name'"; $result = pg_query($dbconn, $query);
if ($result) { $affected = pg_affected_rows($result); echo "更新成功,影响行数:" . $affected; } else { echo "更新失败:" . pg_last_error($dbconn); }
$sql = "UPDATE inventory SET quantity = 200 WHERE name = 'banana'"; $rows = $pdo->exec($sql); echo "更新成功,影响行数:" . $rows; ?>
|
删除数据(DELETE)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php
$query = "DELETE FROM inventory WHERE name = 'orange'"; $result = pg_query($dbconn, $query);
if ($result) { $affected = pg_affected_rows($result); echo "删除成功,影响行数:" . $affected; } else { echo "删除失败:" . pg_last_error($dbconn); }
$sql = "DELETE FROM inventory WHERE name = 'orange'"; $rows = $pdo->exec($sql); echo "删除成功,影响行数:" . $rows; ?>
|
四、预处理语句与 SQL 注入防护
预处理语句是防御 SQL 注入攻击的标准做法,使用时应严格遵守。
pgsql 扩展中的预处理语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?php $dbconn = pg_connect("host=localhost dbname=mydatabase user=myuser password=mypassword");
if (!$dbconn) { die("连接失败:" . pg_last_error()); }
$result = pg_prepare($dbconn, "insert_query", "INSERT INTO inventory (name, quantity) VALUES ($1, $2)");
if (!$result) { die("预处理失败:" . pg_last_error($dbconn)); }
$result = pg_execute($dbconn, "insert_query", array("banana", 150));
if ($result) { echo "数据插入成功"; } else { echo "插入失败:" . pg_last_error($dbconn); }
$result = pg_prepare($dbconn, "select_query", "SELECT id, name, quantity FROM inventory WHERE quantity > $1"); $result = pg_execute($dbconn, "select_query", array(100));
while ($row = pg_fetch_assoc($result)) { echo "ID: " . $row['id'] . " - Name: " . $row['name'] . " - Quantity: " . $row['quantity'] . "<br>"; }
pg_free_result($result); pg_close($dbconn); ?>
|
PDO 预处理语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php try { $pdo = new PDO("pgsql:host=localhost;dbname=mydatabase", "myuser", "mypassword"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $stmt = $pdo->prepare("INSERT INTO inventory (name, quantity) VALUES (:name, :quantity)"); $stmt->execute([':name' => 'banana', ':quantity' => 150]); echo "插入成功,ID: " . $pdo->lastInsertId('inventory_id_seq'); $stmt = $pdo->prepare("INSERT INTO inventory (name, quantity) VALUES (?, ?)"); $stmt->execute(['apple', 100]); echo "插入成功"; $stmt = $pdo->prepare("SELECT id, name, quantity FROM inventory WHERE quantity > :minQty"); $stmt->execute([':minQty' => 50]); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { echo "ID: " . $row['id'] . " - Name: " . $row['name'] . " - Quantity: " . $row['quantity'] . "<br>"; } } catch (PDOException $e) { echo "操作失败:" . $e->getMessage(); } ?>
|
五、事务处理
PostgreSQL 支持完整的 ACID 事务,PHP 中也可以很好地管理事务操作。
pgsql 扩展事务处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php $dbconn = pg_connect("host=localhost dbname=mydatabase user=myuser password=mypassword");
if (!$dbconn) { die("连接失败:" . pg_last_error()); }
pg_query($dbconn, "BEGIN");
try { pg_query($dbconn, "UPDATE accounts SET balance = balance - 100 WHERE id = 1"); pg_query($dbconn, "UPDATE accounts SET balance = balance + 100 WHERE id = 2"); pg_query($dbconn, "COMMIT"); echo "事务提交成功"; } catch (Exception $e) { pg_query($dbconn, "ROLLBACK"); echo "事务回滚:" . $e->getMessage(); }
pg_close($dbconn); ?>
|
PDO 事务处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php try { $pdo = new PDO("pgsql:host=localhost;dbname=mydatabase", "myuser", "mypassword"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->beginTransaction(); $pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1"); $pdo->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2"); $pdo->commit(); echo "事务提交成功"; } catch (PDOException $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } echo "事务回滚:" . $e->getMessage(); } ?>
|
六、错误处理机制
pgsql 扩展错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php $dbconn = pg_connect("host=localhost dbname=mydatabase user=myuser password=mypassword");
if (!$dbconn) { die("连接失败:" . pg_last_error()); }
$result = pg_query($dbconn, "SELECT * FROM non_existent_table"); if (!$result) { $error = pg_last_error($dbconn); $errorCode = pg_result_error_field($result, PGSQL_DIAG_SQLSTATE); echo "错误代码: $errorCode<br>"; echo "错误信息: $error<br>"; } else { pg_free_result($result); }
if (pg_last_error($dbconn)) { echo "最近操作发生错误:" . pg_last_error($dbconn); }
pg_close($dbconn); ?>
|
PDO 错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php try { $pdo = new PDO("pgsql:host=localhost;dbname=mydatabase", "myuser", "mypassword"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->query("SELECT * FROM non_existent_table"); } catch (PDOException $e) { echo "错误代码: " . $e->getCode() . "<br>"; echo "错误信息: " . $e->getMessage() . "<br>"; echo "错误文件: " . $e->getFile() . "<br>"; echo "错误行号: " . $e->getLine() . "<br>"; } ?>
|
七、连接池与持久连接
pgsql 扩展持久连接
1 2 3 4 5 6 7 8 9 10 11 12
| <?php
$dbconn = pg_connect("host=localhost dbname=mydatabase user=myuser password=mypassword", PGSQL_CONNECT_FORCE_NEW);
$dbconn = pg_pconnect("host=localhost dbname=mydatabase user=myuser password=mypassword"); if ($dbconn) { echo "持久连接建立成功"; } else { echo "连接失败:" . pg_last_error(); } ?>
|
PDO 持久连接
1 2 3 4 5 6 7 8 9 10
| <?php
$dsn = "pgsql:host=localhost;dbname=mydatabase;persist=true"; try { $pdo = new PDO($dsn, "myuser", "mypassword"); echo "持久连接建立成功"; } catch (PDOException $e) { echo "连接失败:" . $e->getMessage(); } ?>
|
八、操作 PostgreSQL 特有数据类型
JSON/JSONB 数据类型操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php
$jsonData = json_encode(['name' => 'product1', 'tags' => ['electronics', 'sale']]); $query = "INSERT INTO products (name, metadata) VALUES ('Product A', '$jsonData')";
$query = "SELECT metadata->>'name' as meta_name FROM products"; $result = pg_query($dbconn, $query); while ($row = pg_fetch_assoc($result)) { echo "元数据中的名称: " . $row['meta_name'] . "<br>"; }
$stmt = $pdo->prepare("SELECT metadata->>'name' as meta_name FROM products"); $stmt->execute(); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { echo "元数据中的名称: " . $row['meta_name'] . "<br>"; } ?>
|
UUID 类型操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php
$query = "INSERT INTO users (id, name) VALUES (gen_random_uuid(), 'John Doe')";
$query = "SELECT id, name FROM users"; $result = pg_query($dbconn, $query); while ($row = pg_fetch_assoc($result)) { echo "UUID: " . $row['id'] . " - Name: " . $row['name'] . "<br>"; } ?>
|
九、数据库性能优化建议
| 建议 |
说明 |
| 使用索引 |
为经常查询的列建立索引,注意避免过多索引影响写入性能 |
| 使用连接池 |
减少频繁创建和销毁连接的开销 |
| 批量操作 |
使用 INSERT INTO ... VALUES (...), (...), (...) 或 COPY 命令批量导入 |
| 预处理语句 |
使用预处理语句提升重复查询的性能 |
| 合理设置连接超时 |
避免长时间等待无法响应的数据库连接 |
| 监控慢查询 |
利用 PostgreSQL 的 pg_stat_statements 扩展分析慢查询 |
十、完整的连接配置示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <?php class Database { private static ?PDO $instance = null; private function __construct() {} public static function getInstance(): PDO { if (self::$instance === null) { $config = [ 'host' => getenv('DB_HOST') ?: 'localhost', 'port' => getenv('DB_PORT') ?: '5432', 'dbname' => getenv('DB_NAME') ?: 'myapp', 'user' => getenv('DB_USER') ?: 'appuser', 'password' => getenv('DB_PASS') ?: '', 'sslmode' => getenv('DB_SSL') ?: 'disable', ]; $dsn = sprintf( "pgsql:host=%s;port=%s;dbname=%s;sslmode=%s", $config['host'], $config['port'], $config['dbname'], $config['sslmode'] ); try { self::$instance = new PDO($dsn, $config['user'], $config['password']); self::$instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); self::$instance->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); } catch (PDOException $e) { die("数据库连接失败: " . $e->getMessage()); } } return self::$instance; } }
$db = Database::getInstance(); $stmt = $db->query("SELECT * FROM inventory"); while ($row = $stmt->fetch()) { print_r($row); } ?>
|
总结
PHP 与 PostgreSQL 的配合使用是现代 Web 开发中的常见实践,核心要点可归纳为:
- 选择扩展:pgsql 扩展适合 PostgreSQL 专属高性能项目,PDO_PGSQL 适合跨数据库或需要更高抽象层的项目
- 安全第一:始终使用预处理语句防止 SQL 注入,合理配置错误处理
- 连接配置:密码避免硬编码,生产环境注意连接池和超时配置
- 事务处理:使用事务保证数据一致性
- 特有功能:利用 PostgreSQL 的 JSON、UUID 等高级数据类型
掌握这些基础操作后,可以进一步学习 PostgreSQL 的存储过程、全文搜索、高级索引和分区表等特性,构建更强大的 Web 应用。