普贤教程 › 进阶教程
数据库访问
数据库能力在生态包里,不在标准库。registry/mysql 是一个纯 .px 实现的 MySQL 客户端——走 MySQL 二进制协议,不依赖 libmysqlclient。
安装
sh
pxpkg add mysql@0.2.0 pxpkg install
px
import mysql连接
px
var c = mysql_connect("127.0.0.1", 3306, "app", "password", "appdb") if c.is_err(): print("连接失败:" + c.err()) else: var conn = c.ok() # ... 用 conn mysql_close(conn)
| 函数 | 说明 |
|---|---|
mysql_connect(host, port, user, pass, db, opts) | 返回 Ok(conn) / Err;opts 可传 {"timeout_ms": 5000} |
mysql_query(conn, sql) | 文本协议,结果值一律 string |
mysql_query_params(conn, sql, params) | 预处理语句,按列类型返回值(int / float / string / null) |
mysql_close(conn) | 关闭连接 |
查询结果的结构
px
var q = mysql_query(conn, "SELECT id, name FROM users LIMIT 2") if q.is_err(): print("查询失败:" + q.err()) else: var rs = q.ok() print(str(len(rs["columns"]))) for row in rs["rows"]: print(row[0] + " " + row[1])
结果字典:{"columns": [{"name": ...}], "rows": [[值, ...], ...]}。行是列表,按列顺序取值,不是按列名。
文本协议全是字符串
mysql_query 返回的每个值都是 string,数字要自己 int()。要原始类型用 mysql_query_params——它走二进制协议,按列类型返回 int / float / string / null。预处理语句(防注入)
px
var q = mysql_query_params(conn, "SELECT id FROM users WHERE email = ?", ["a@b.com"]) if q.is_err(): print(q.err()) else: print(str(len(q.ok()["rows"])))
永远不要在 SQL 里拼用户输入字符串拼接 = 注入。
mysql_query_params 用 ? 占位并由服务端绑定参数,这是唯一安全的写法。mysql_escape(s) 只是兜底,能不用就不用。写入与事务
px
var ins = mysql_query_params(conn, "INSERT INTO events (kind, payload, created_at) VALUES (?, ?, UTC_TIMESTAMP())", ["login", json_stringify({"ip": "10.0.0.1"})]) if ins.is_err(): print("写入失败:" + ins.err()) mysql_query(conn, "START TRANSACTION") var a = mysql_query_params(conn, "UPDATE wallets SET balance = balance - ? WHERE id = ?", [100, 1]) var b = mysql_query_params(conn, "UPDATE wallets SET balance = balance + ? WHERE id = ?", [100, 2]) if a.is_err() or b.is_err(): mysql_query(conn, "ROLLBACK") print("已回滚") else: mysql_query(conn, "COMMIT")
时区:最容易踩的坑
连接用 time_zone='+00:00' 时,连接内的 NOW() 是 UTC;而表的 DEFAULT CURRENT_TIMESTAMP 用的是服务器时区(通常是 CST)。两者差 8 小时,症状是「队列里的任务永远不到期」「刚写入的记录时间显示在 8 小时前」。
| 做法 | 结果 |
|---|---|
连接固定 UTC + 写入用 UTC_TIMESTAMP() | 一致,推荐 |
| 连接固定 UTC + 依赖列默认值 | 混用两种时区,必出问题 |
| 连接不设时区 | 跟随服务器,跨机器部署时不稳 |
连接管理
PuXian 这边没有连接池对象,实践上两种做法:
- 短连接(px_routes 每次请求一个新进程):连上、用完关掉。注意首次连接在解释轨要 400 ms 左右,编译轨 20–40 ms。
- 常驻进程:进程内保持一个连接,循环处理任务;断线时重连,重连失败要退避。
px
def with_conn(dsn_user, dsn_pass, fn): var c = mysql_connect("127.0.0.1", 3306, dsn_user, dsn_pass, "appdb") if c.is_err(): return Err("连接失败:" + c.err()) var conn = c.ok() var r = fn(conn) mysql_close(conn) return r
其他数据库
| 包 | 说明 |
|---|---|
pg | PostgreSQL 客户端 |
redis | RESP 协议;缓存、计数器、分布式锁 |
mongodb | 文档数据库 |
sqlparse | SQL 解析(做审计、慢查询归类时有用) |
排错清单
| 错误 | 常见原因 |
|---|---|
Access denied for user | 密码错;或 env 文件里的值带引号没去掉 |
1045 | 同上(认证失败) |
1142 | 账号没有该表的 DDL 权限(建表要用 root) |
| 查询返回空但表里有数据 | 连接进了另一个库;或 WHERE 参数类型不匹配 |
| 毫秒级偶尔超时 | 连接建立耗时;改用常驻进程或短连接池 |