跳转至

v2.1.0

新功能 (New Features)

1. 连接初始化 SQL(init_sql

新增 init_sql 参数,支持在每条新 TCP 连接首次使用时自动执行预设 SQL。适用于 ClickHouse 异步插入配置、MySQL Session 变量设置等场景。

背景

在 ClickHouse 等数据库中,SET 语句的作用域为 TCP 连接级别。之前每次执行 INSERT 前都需要手动执行 SET 语句来配置异步插入等参数,冗余且低效。init_sql 特性将这一能力下沉到引擎层,实现连接级自动化。

工作原理

  • init_sql 的执行在 Result.__call__() 中实现
  • 使用 weakref.WeakKeyDictionary 以 Pool 为 key 追踪已初始化的连接 ID(id(conn)
  • 新连接首次执行 SQL 时自动执行 init_sql,后续复用同一连接时跳过
  • 连接被 pool_recycle 回收重建后,新连接会重新执行 init_sql
  • init_sql 执行后会立即 commit(),确保 SET 语句生效

使用方式

from asmysql import Engine

# 通过关键字参数设置
engine = Engine(
    host="192.168.62.195",
    port=9004,
    user="default",
    password="",
    init_sql=(
        "SET async_insert = 1, wait_for_async_insert = 0, "
        "async_insert_busy_timeout_ms = 1000, "
        "max_execution_time = 30"
    ),
)
await engine.connect()

# 通过 URL 参数设置
engine = Engine(
    url="mysql://user:pass@127.0.0.1:3306/?init_sql=SET SESSION wait_timeout=600"
)
await engine.connect()

向后兼容

init_sql 默认值为 None,不传时行为与 v2.0.0 完全一致,不影响现有代码。

重大变更 (Breaking Changes)

1. 自动提交机制重构

移除了 auto_commit Engine 参数和 commit execute/Result 参数,改为基于 SQL 语句类型的自动提交机制:

  • Result 内部通过 _should_commit() 方法分析 SQL 语句的首关键词
  • 写操作关键词(INSERT、UPDATE、DELETE、REPLACE、CREATE、ALTER、DROP 等)执行后自动 COMMIT
  • 读操作(SELECT)不触发 COMMIT
  • stream=True 模式下不自动 COMMIT(避免中断流)

迁移指南

# v2.0.0 写法(已废弃)
engine = Engine(url="...", auto_commit=False)
result = await engine.execute("INSERT ...", commit=False)

# v2.1.0 写法
engine = Engine(url="...")
async with engine.execute("INSERT ...") as result:
    pass  # 写操作自动提交

2. 移除 Engine 的 stream 参数

stream 不再是 Engine 类属性或构造参数,仅在 execute() / execute_many() 方法级别使用。

3. 移除 Engine 的 result_class 参数

result_class 不再是 Engine 类属性或构造参数,仅在 execute() / execute_many() 方法级别使用,默认值为 tuple

迁移指南

# v2.0.0 写法(已废弃)
engine = Engine(url="...", result_class=dict)
async with engine.execute("SELECT ...") as result:
    data = await result.fetch_one()  # data is dict

# v2.1.0 写法
engine = Engine(url="...")
async with engine.execute("SELECT ...", result_class=dict) as result:
    data = await result.fetch_one()  # data is dict

缺陷修复 (Bug Fixes)

  • 修复 fetch_all() 自定义结果类型连接泄漏:当 result_class 为自定义类(如 Pydantic BaseModel)时,return _dataclose() 之前退出,每次调用泄漏一个连接。已统一 return 路径确保 close() 始终执行
  • 修复查询错误后 close() 双重释放__call__() 的 except 分支关闭 cursor 并释放连接后未将 __cursor 置空,后续 close() 会再次释放同一连接。已在 except 中添加 self.__cursor = None
  • 修复 async for + break 连接泄漏__aiter__ 返回 self 导致 break 后连接无法释放。已重写为 async generator + try/finally 模式
  • 修复 Result.__call__() 仅捕获 MySQLError 导致连接泄漏:非 MySQL 异常未被捕获,已获取的连接不会释放。已改为 except Exception 并在非 MySQL 错误时 re-raise
  • 修复 __del__ 安全网崩溃__del__ 在 GC 期间无错误处理,self.pool 可能为 None。已添加 try/except 和 pool 空值检查
  • 修复 echo_sql_log URL 参数无法设为 False?echo_sql_log=false 被解析为 True。已改为正确解析布尔字符串
  • 修复 or 运算符覆盖合法 falsy 值Engine(min_pool_size=0)0or 覆盖为默认值。已统一改为 x if x is not None else default
  • 修复 connect()/disconnect() 并发竞态条件:多个协程同时调用可创建多个连接池或双重关闭。已添加 asyncio.Lock 保护
  • 修复 execute() 传入 self.__pool(可能为 None):未连接时 Result 获得 None pool。已改用 self.pool 属性
  • 修复 Result[type[T]] 泛型参数语义错误:应为 Result[T]。已修正泛型参数
  • 修复 release_connections() 缺少空值检查:未连接时调用抛出 AttributeError。已添加空值检查
  • 移除 @lru_cache 装饰器__repr__/__str__ 上的 @lru_cache 持有对 self 的强引用,阻止 GC。已移除缓存
  • 修复 error_msg 未使用 err_msg() 格式化:丢失了对空字符串的回退处理。已改为调用 err_msg() 函数
  • 修复 iterate() 使用 truthiness 检查不一致if data: 将空元组视为 falsy。已统一为 is not None 检查
  • 修复 row_count/last_rowid/row_number 在 close 后 AttributeError:cursor 被置空后访问崩溃。已添加空值检查
  • DDL/DML 语句自动释放连接cursor.description is None 时(DDL/INSERT/UPDATE 等),自动关闭游标并释放连接回连接池,同时缓存 rowcount / lastrowid

文档 (Documentation)

  • 修正首页示例代码:修复中英文 index.md 中使用了不存在的 API,统一使用正确的 async with engine.execute() + result.iterate() 模式。execute() / execute_many() 返回 Result 对象(非协程),必须通过 async withasync for 使用
  • 新增 init_sql 文档:在 connection.mdapi.mdexamples.md 中同步添加 init_sql 参数说明和使用示例(中英文版本)
  • 修正事务控制文档:移除 auto_commit/commit 参数描述,改为描述基于 SQL 语句类型的自动提交机制
  • 修正 API 文档签名:移除 fetch_one()/fetch_many()close 参数,移除 execute()/execute_many()commit 参数

测试 (Testing)

  • 新增 test_engine_init_sql.py 测试文件,覆盖 init_sql 核心功能与边界场景
  • 重写集成测试 engine fixture,通过 init_sql=USE <test_db> 在每条连接池连接上自动选择测试数据库
  • 移除 test_mock_engine.py 中与 Engine result_class 相关的测试用例