px普贤教程PuXian
普贤教程 › 进阶教程

数据库访问

数据库能力在生态包里,不在标准库。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

其他数据库

包说明
pgPostgreSQL 客户端
redisRESP 协议;缓存、计数器、分布式锁
mongodb文档数据库
sqlparseSQL 解析(做审计、慢查询归类时有用)

排错清单

错误常见原因
Access denied for user密码错;或 env 文件里的值带引号没去掉
1045同上(认证失败)
1142账号没有该表的 DDL 权限(建表要用 root)
查询返回空但表里有数据连接进了另一个库;或 WHERE 参数类型不匹配
毫秒级偶尔超时连接建立耗时;改用常驻进程或短连接池