# 第 4-1 课:PostgreSQL 与 Psycopg 入门 ## 一、本课定位 你已经掌握 SQL、事务和 Java 数据库开发,因此本课不再从表、字段和增删改查讲起,而是集中回答一个问题:Python 程序怎样安全地连接 PostgreSQL 并执行 SQL? Python 数据库 API(Database API,DB-API)规定了数据库驱动的通用操作方式。Psycopg 3 是 PostgreSQL 的 Python 驱动,本课会把它与 JDBC 逐项对照。 ## 二、本课目标 完成本课后,你将能够: 1. 解释 DB-API、Psycopg 和 PostgreSQL 的关系; 2. 使用本地 TOML 文件保存数据库连接配置; 3. 使用 `psycopg.connect()` 创建连接; 4. 使用游标执行只读 SQL 并取得结果; 5. 使用参数化查询传递数据; 6. 使用 `with` 自动释放连接和游标; 7. 对照 JDBC 理解 Python 数据库代码; 8. 识别连接失败、依赖缺失和参数占位符错误。 ## 三、前置知识 本课默认已经掌握: - PostgreSQL 数据库地址、端口、数据库、用户名和密码的含义; - `SELECT` 基础语法; - JDBC 的 `Connection`、`PreparedStatement` 和 `ResultSet`; - Python 函数、异常、上下文管理器和文件读取基础。 本课只连接专用练习数据库。不要连接生产数据库,也不要使用具有创建用户、删除数据库等高权限的账号。 ## 四、DB-API、Psycopg 与 JDBC DB-API 是 Python 数据库驱动遵循的接口约定,不是一个需要单独安装的框架。不同数据库有不同驱动,但常见操作方式比较统一。 | Java/JDBC | Python/Psycopg | 作用 | |---|---|---| | PostgreSQL JDBC Driver | Psycopg 3 | 与 PostgreSQL 通信 | | `DriverManager.getConnection()` | `psycopg.connect()` | 创建数据库连接 | | `Connection` | `Connection` | 表示一次数据库会话 | | `PreparedStatement` | `Cursor.execute(sql, params)` | 执行参数化 SQL | | `ResultSet` | `Cursor` 和 `fetchone()` 等方法 | 读取查询结果 | | `try-with-resources` | `with` | 自动释放资源 | | `SQLException` | `psycopg.Error` | 表示数据库访问错误 | Psycopg 的游标(Cursor)同时承担“执行 SQL”和“读取结果”的职责。它不是数据库界面中的鼠标光标。 ## 五、准备远程练习数据库 建议为课程准备: - 一个独立数据库,例如 `python_course`; - 一个专用账号,例如 `python_student`; - 只授予课程所需权限; - 不与生产环境或其他重要测试数据共用。 本课不使用环境变量,也不要求把所有信息拼成数据库连接串,而是把连接参数分字段写入 TOML 配置: ```toml [postgresql] host = "数据库主机" port = 5432 dbname = "数据库名" user = "用户名" password = "密码" connect_timeout = 10 ``` 仓库中的 [config.example.toml](./config.example.toml) 只包含占位内容,可以提交 Git。实际配置写入同目录的 `config.toml`,该文件已经被项目 `.gitignore` 排除。 TOML(Tom's Obvious Minimal Language)是一种结构化配置格式。Python 3.11 及以上版本内置 `tomllib`,读取 TOML 不需要安装额外依赖,也不会修改操作系统或当前进程的环境变量。 ## 六、安装 Psycopg 3 你当前使用 Conda 的 `base` 环境,可以直接安装 Psycopg: ```powershell conda install -n base -c conda-forge "psycopg>=3,<4" psycopg-c ``` 安装完成后验证: ```powershell python -c "import psycopg; print(psycopg.__version__)" ``` 如果以后改用 Python 虚拟环境,也可以使用 `requirements.txt` 安装。无论使用哪种方式,导入时都写 `import psycopg`,不是 `import psycopg3`。 ## 七、创建本地 TOML 配置 进入本课目录,复制配置模板: ```powershell Copy-Item .\config.example.toml .\config.toml ``` 然后只在本地 `config.toml` 中填写真实的主机、端口、数据库名、用户名和密码。程序通过 Python 文件自身的位置寻找配置,因此从项目根目录或课程目录启动都可以。 ### 7.1 为什么选择 TOML - Python 3.13 可以直接使用内置 `tomllib`; - 字段和类型清楚,端口可以保持整数; - 不需要污染环境变量; - 比 XML 简洁,比 YAML 少一个第三方解析依赖; - 配置节结构与 Java 项目的 YAML、Properties 配置思路相近。 ### 7.2 代码硬编码可以怎么写 从技术上可以直接构造字典: ```python database_config = { "host": "数据库主机", "port": 5432, "dbname": "数据库名", "user": "用户名", "password": "密码", } ``` 这能帮助理解 `psycopg.connect()` 接收哪些参数,但真实密码一旦硬编码(Hard Coding)就可能进入 Git 历史。本课程标准示例使用 `config.toml`;如自行尝试硬编码,只能放在不提交的个人练习文件中。 ## 八、完整示例 示例文件为 [connection_example.py](./connection_example.py)。核心结构如下: ```python database_config = load_database_config(CONFIG_PATH) with psycopg.connect(**database_config) as connection: with connection.cursor() as cursor: cursor.execute( "SELECT current_database(), current_user, %s::text", ("Psycopg 连接成功",), ) database_name, user_name, message = cursor.fetchone() ``` 示例只读取当前数据库名和当前用户,不创建表、不修改数据。 ### 8.1 为什么参数使用 `%s` Psycopg 使用 `%s` 表示值参数,即使参数是整数也仍然使用 `%s`。参数值通过 `execute()` 的第二个参数单独传入: ```python cursor.execute("SELECT %s::text", (message,)) ``` 不要使用 f-string、字符串拼接或 `%` 运算符把数据直接写进 SQL: ```python # 错误示例:数据被直接拼进 SQL,可能产生 SQL 注入。 cursor.execute(f"SELECT '{message}'") ``` ### 8.2 单个参数为什么有逗号 ```python (message,) ``` 这是只有一个元素的元组。写成 `(message)` 只是在字符串外加括号,不是元组。 ### 8.3 with 做了什么 - 离开游标的 `with` 时关闭游标; - 离开连接的 `with` 时结束事务并关闭连接; - 正常离开连接块时提交当前事务; - 块内出现异常时回滚当前事务。 本课执行的是只读查询,但仍要建立正确的资源和事务管理习惯。 ## 九、运行方法 确认 `config.toml` 已经创建并填写完成,然后在项目根目录运行: ```powershell python .\04_数据库\4_1_PostgreSQL与Psycopg入门\connection_example.py ``` 正常情况下会看到类似结果: ```text 连接成功。 当前数据库:python_course 当前用户:python_student 参数化查询结果:Psycopg 连接成功 ``` 数据库名和用户名应以你的远程练习环境为准。 ## 十、关键执行顺序 1. `main()` 调用 `load_database_config(CONFIG_PATH)`; 2. `Path` 根据当前 Python 文件的位置定位 `config.toml`; 3. `tomllib.load()` 读取 `[postgresql]` 配置节; 4. 缺少文件、配置节或必填字段时主动抛出中文 `RuntimeError`; 5. `psycopg.connect(**database_config)` 把字典展开为连接参数; 6. `connection.cursor()` 创建游标; 7. `cursor.execute()` 将 SQL 和查询参数分别交给驱动; 8. `cursor.fetchone()` 读取一行结果; 9. 两层 `with` 依次释放游标和连接; 10. `main()` 输出结果,并分别处理配置异常和数据库异常。 ## 十一、常见错误 ### 11.1 `ModuleNotFoundError: No module named 'psycopg'` 含义:当前 Python 环境没有安装 Psycopg。 处理:激活 `.venv`,再使用本课的 `requirements.txt` 安装依赖。可以运行 `python -m pip show psycopg` 检查安装位置。 ### 11.2 未找到 `config.toml` 含义:本课目录中还没有实际配置文件。 处理:把 `config.example.toml` 复制为 `config.toml` 并填写连接参数。不要直接把真实信息写进示例模板。 ### 11.3 TOML 格式错误 含义:配置不符合 TOML 语法,例如字符串缺少引号或同一个键重复出现。 处理:对照 `config.example.toml` 检查配置节、等号、引号和字段名。 ### 11.4 `connection refused` 或连接超时 含义:程序无法到达数据库地址和端口。 检查数据库服务、主机名、端口、防火墙、白名单和 VPN,不要先假设一定是密码错误。 ### 11.5 `password authentication failed` 含义:服务器已经收到连接,但用户名或密码校验失败。 检查 `config.toml` 中的账号、密码和数据库名。分字段传参时无需手动拼接 URL,也避免了连接串中特殊字符编码问题。 ### 11.6 `execute()` 参数写错 下面两种写法都不符合本课要求: ```python cursor.execute("SELECT '%s'", (message,)) cursor.execute("SELECT %s", message) ``` 占位符外不要加引号,参数序列只有一个值时要写成 `(message,)`。 ### 11.7 使用 `fetchone()` 却没有处理空结果 `fetchone()` 在没有数据时可能返回 `None`。本课查询必然返回一行,所以可以直接解包;以后查询业务表时必须判断空结果。 ## 十二、课堂练习 打开 [practice.py](./practice.py),按照注释完成练习。练习要求你独立完成: 1. 使用 `Path` 定位本地 `config.toml`; 2. 使用 `tomllib` 读取 PostgreSQL 配置; 3. 使用两层 `with` 管理连接和游标; 4. 执行包含两个参数的只读查询; 5. 使用 `fetchone()` 保存并输出结果; 6. 分别捕获配置读取异常和数据库访问异常。 练习仍然只执行只读 SQL,不创建、修改或删除远程数据。 ## 十三、本课小结 - DB-API 是 Python 数据库驱动的通用接口约定; - Psycopg 3 是 PostgreSQL 的 Python 驱动; - Psycopg 的基础层次与 JDBC 相似; - `Connection` 表示数据库会话,`Cursor` 负责执行 SQL 和读取结果; - SQL 与参数必须分开传递; - `with` 用于可靠地结束事务和释放资源; - 数据库密码保存在被 Git 忽略的本地 TOML 配置中,不写入环境变量或源码。 ## 十四、验收标准 - 能说明 DB-API、Psycopg 和 PostgreSQL 的关系; - 能说出 Psycopg 与 JDBC 的主要对象对应关系; - 能复制模板并通过本地 `config.toml` 配置连接参数; - `connection_example.py` 可以连接练习数据库并输出三项查询结果; - `practice.py` 使用参数化查询,没有拼接 SQL; - 连接和游标都通过 `with` 管理; - 能区分网络不可达、认证失败和依赖缺失; - 程序没有读取、设置或修改环境变量; - 未把 `config.toml` 或真实数据库连接信息写入 Git 跟踪文件。