px普贤教程PuXian
普贤教程 › 生态包

数据库实战

四个数据库客户端,全是纯 PuXian 实现、不依赖任何 C 驱动:mysql(文本+预处理协议)、pg(v3 wire,含 SCRAM)、mongodb(OP_MSG)、redis(RESP)。本页给连接方式与三条最容易踩的坑。

本页口径接口表来自包源码签名;Redis 段落有完整实测输出(单独一页),MySQL 段落的「连接耗时」「时区」两处来自生产实测记录。

MySQL

px
import mysql

def main():
    var c = mysql_connect("127.0.0.1", 3306, "app", "密码", "appdb")
    if c.is_err():
        print("连不上:" + str(c.err()))
        return
    var conn = c.ok()

    var r = mysql_query(conn, "SELECT id, name FROM users LIMIT 3")
    if r.is_ok():
        for row in r.ok()["rows"]:
            print(row[0] + " " + row[1])

    # 带参数的查询:用预处理,不要拼字符串
    var one = mysql_query_params(conn, "SELECT name FROM users WHERE id = ?", [1001])
    print(one.ok()["rows"])

    mysql_close(conn)
函数说明
mysql_connect(host, port, user, password, dbname, opts)→ Ok(conn) / Err
mysql_query(conn, sql)文本协议,一次性简单查询
mysql_query_params(conn, sql, [..])预处理绑定,防注入用这个
mysql_begin / mysql_commit / mysql_rollback事务
mysql_select_db / mysql_ping / mysql_close切库、探活、关闭

坑 1:mysql_escape(s) 只收一个参数

写成 mysql_escape(conn, s) 会报参数不匹配。要防注入,正确做法不是 escape,而是 mysql_query_params——把值交给协议绑定,别自己拼 SQL。

坑 2:连接里 NOW() 是 UTC

DSN 带 time_zone='+00:00' 时,连接内的 NOW() 返回 UTC,而表字段 DEFAULT CURRENT_TIMESTAMP 用的是服务器时区(东八区)。表现是「刚插进去的行,时间显示在未来 8 小时」或「队列永远取不到任务,且不报错」。

修法要么在连接里 SET time_zone='+08:00',要么所有时间列显式写 UTC_TIMESTAMP(),别让「连接时区」和「表默认值时区」各说各话。

坑 3:首次连库在解释轨要 0.4 秒

实测:tcp_connect 0 ms、SELECT 1 25 ms、SET time_zone 8 ms,剩下约 420–450 ms 全是纯解释开销(协议帧的字节级拼装)。每次请求新建连接的接口,在解释轨上首包就慢半秒——编译轨或连接复用能消掉。

PostgreSQL

px
import pg

def main():
    var c = pg_connect("127.0.0.1", 5432, "app", "密码", "appdb", {"tls": false})
    var conn = c.ok()
    var r = pg_query_params(conn, "SELECT id, name FROM users WHERE id = $1", [1001])
    print(r.ok()["columns"])       # [{name: id, type_oid: 23}, …]
    print(r.ok()["rows"])          # [["1001", "张三"]] ← 文本格式,类型自己转
    pg_close(conn)

认证支持 md5 / SCRAM-SHA-256 / cleartext / trust;结果统一是文本格式(NULL → null),所以数字也是字符串,要自己 int()。

MongoDB

px
import mongodb

def main():
    var c = mg_connect("127.0.0.1", 27017, 3000)
    var conn = c.ok()
    print(mg_ping(conn))
    mg_insert(conn, "appdb", "users", {"name": "张三", "age": 28})
    print(mg_find(conn, "appdb", "users", {"age": {"$gt": 18}}, 10))
    print(mg_count(conn, "appdb", "users", {"age": {"$gt": 18}}))
    mg_close(conn)

函数前缀是 mg_,参数依次带 db 与 coll(Mongo 一个连接可以跨库)。OP_MSG 协议子集:ping、insert、find、count、update_one/many、delete_one/many、distinct、create_index、aggregate、drop。find 的 limit 是位置参数,不是 opts 字典。

Redis

连接与全套命令见 Redis 专页:redis_connect(host, port, opts),SET 返回 Ok(true),mset 收 [[k,v],…]。这三条是初学最容易写错的地方。

选型建议

场景包理由
业务主库、事务mysql / pg预处理绑定 + 事务齐全
缓存、限流、会话redis单命令原子;SET NX EX 一把梭
文档型、无固定结构mongodb免迁移,但弱事务
一条纪律连接对象是带 fd 的 dict,不是线程安全的。每请求建连(配合 Ma 的进程模型)或每次用完关闭,别把连接塞进全局共享。