doris-python:让 SQLAlchemy 玩转 Apache Doris 多驱动生态
Doris-Python让 SQLAlchemy 自由切换 Doris 同步/异步驱动背景Apache Doris 的 Python 生态里最知名的连接库是 pydoris 官方 SDK。它功能完善、协议实现扎实是 Doris Python 接入的事实标准。但 pydoris 在 SQLAlchemy 场景下有一个很现实的限制只支持了一种驱动。也就是说如果你已经在项目里统一用 SQLAlchemy 来管理连接池、ORM、迁移等能力又想接入 Doris那么无论你之前习惯用 mysqldb、pymysql、aiomysql 还是 asyncmypydoris 的 SQLAlchemy 方言都会把你绑定到一种实现上。现实中一个常见的搭配是同步链路SQLAlchemy PyMySQL生态最成熟、文档最多异步链路SQLAlchemy 2.0 async aiomysql或asyncmy需要在 Web 框架FastAPI / Starlette / aiohttp里跑高并发查询。如果你想用一份统一的 SQLAlchemy 代码同时跑同步和异步又或者想从 PyMySQL 平滑迁移到异步驱动pydoris 的单一驱动模式就会成为绊脚石。这正是doris-python这个仓库要解决的问题在 pydoris 基础上把 SQLAlchemy 体系下 Doris 的同步/异步驱动支持扩展到主流的几个实现使用者按需选择。—— 也就是说doris-python并不是要把 pydoris 的能力重写一遍而是只把驱动选择权这件事补齐。doris-python 是什么doris-python是一个 SQLAlchemy Dialect 扩展包它通过entry_points注册多个 Doris 方言覆盖目前 Python 生态里最常见的三种驱动同步/异步数据库驱动对应 dialect 名典型场景同步mysqldb(mysqlclient)dorismysqldb传统服务、ETL 脚本、Django、Flask、命令行工具C 扩展性能最好同步pymysqldorispymysql不想编译原生依赖、追求纯 Python 部署的传统服务异步aiomysqldorisaiomysqlFastAPI / Starlette基于 PyMySQL 的纯 Python 异步驱动异步asyncmydorisasyncmy高并发异步场景cython 实现的异步驱动性能更好安装好之后你只需要在 SQLAlchemy 的 URL 里替换驱动前缀即可# 同步mysqldbenginecreate_engine(dorismysqldb://user:passhost:9030/mydb)# 同步pymysqlenginecreate_engine(dorispymysql://user:passhost:9030/mydb)# 异步aiomysqlenginecreate_async_engine(dorisaiomysql://user:passhost:9030/mydb)# 异步asyncmyenginecreate_async_engine(dorisasyncmy://user:passhost:9030/mydb)不需要为每种驱动维护一份方言实现也不需要修改业务代码——切换驱动 改 URL 前缀。核心设计仓库代码量不大但结构清晰全部位于src/doris_python/sqlalchemy/下doris_python/ └── sqlalchemy/ ├── __init__.py # 暴露各 dialect ├── dialect.py # DorisDialect 基类Doris/MySQL 方言适配、类型系统、Schema/Compiler ├── datatype.py # Doris 类型映射DECIMAL/HLL/BITMAP/JSON/ARRAY/MAP 等 ├── mysqldb.py # 同步基于 mysqlclient(mysqldb) 的 Dialect ├── pymysql.py # 同步基于 pymysql 的 Dialect ├── aiomysql.py # 异步基于 aiomysql 的 Dialect └── asyncmy.py # 异步基于 asyncmy 的 Dialect几个关键点1. 共享一个 DorisDialect 基类dialect.py是核心。它继承自sqlalchemy.dialects.mysql.base.MySQLDialect复用 SQLAlchemy MySQL 方言的语法/类型能力再针对 Doris 做减法和特化适配 MySQL 协议但避免 MySQL 专属语法例如对SHOW VARIABLES、某些信息查询路径做替换数据类型补齐Doris 独有的HLL、BITMAP、QUANTILE_STATE、ARRAYT、MAPK,V、JSONB等都在datatype.py中显式声明Schema/建表语句渲染Doris 的UNIQUE KEY / DUPLICATE KEY / AGGREGATE KEY模型、DISTRIBUTED BY … BUCKETS …、PROPERTIES 等都有对应的编译逻辑。mysqldb / pymysql / aiomysql / asyncmy 四个 driver 文件都是薄包装只负责切换执行层所有 Doris 特有的行为都在基类里共享。这意味着新增一种驱动 新建一个几十行的 dialect.py 文件。2. 驱动切换的代价 ≈ 0因为 dialect 基类共享三个 driver 之间行为是一致的。举个例子你在用pymysql时这样写fromsqlalchemyimportcreate_engine,text enginecreate_engine(dorispymysql://user:passhost:9030/mydb)withengine.connect()asconn:rowsconn.execute(text(SELECT version())).all()改成异步只是 URL 变化业务代码变成 asyncfromsqlalchemy.ext.asyncioimportcreate_async_enginefromsqlalchemyimporttext enginecreate_async_engine(dorisaiomysql://user:passhost:9030/mydb)asyncwithengine.connect()asconn:rows(awaitconn.execute(text(SELECT version()))).all()⚠️ 注意因为dorisaiomysql/dorisasyncmy是 async engine所有 DBAPI 调用都要await并使用AsyncConnection/AsyncSession。3. Doris 数据类型的完整映射Doris 相对 MySQL 有不少独有的类型datatype.py给出了显式映射避免落到 SQLAlchemy 默认的NULL兜底。重点支持数值TINYINT / SMALLINT / INT / BIGINT / LARGEINT / FLOAT / DOUBLE / DECIMAL字符串CHAR / VARCHAR / STRING / TEXT日期DATE / DATETIMEDoris 特有HLL近似去重、BITMAP位图、QUANTILE_STATE分位数近似、JSON / JSONB、ARRAYT、MAPK,V、STRUCT...布尔BOOLEAN在 Doris 中实际是TINYINT基类做了归一建表时的DUPLICATE/UNIQUE/AGGREGATE KEY模型和DISTRIBUTED BY ... BUCKETS N也是基类编译出来的不需要业务侧手动拼字符串。安装与依赖doris-python本身不强制安装具体驱动按需安装即可# 基础pipinstalldoris-python# 同步mysqldbmysqlclientC 扩展pipinstalldoris-python[mysqldb]# 同步pymysql纯 Pythonpipinstalldoris-python[pymysql]# 异步aiomysqlpipinstalldoris-python[aiomysql]# 异步asyncmypipinstalldoris-python[asyncmy]# 全部驱动pipinstalldoris-python[all]可选依赖通过pyproject.toml的 extras 管理按需拉取避免污染用户的运行时环境。实际用法场景一mysqldb同步性能优先mysqlclient即mysqldb是对libmysqlclient的 C 扩展封装是 SQLAlchemy 同步链路里性能最好的 MySQL 驱动。如果你已经有一份用mysqlclient跑 MySQL 的代码迁移到 Doris 几乎是零改动fromsqlalchemyimportcreate_engine,Column,Integer,Stringfromsqlalchemy.ormimportdeclarative_base,Session Basedeclarative_base()classOrder(Base):__tablename__ordersidColumn(Integer,primary_keyTrue)skuColumn(String(64))enginecreate_engine(dorismysqldb://user:pass127.0.0.1:9030/demo)# 建表Doris 风格的 CREATE TABLE 由 dialect 渲染Base.metadata.create_all(engine)withSession(engine)ass:s.add(Order(id1,skuA001))s.commit()场景二PyMySQL同步部署简单PyMySQL是纯 Python 实现免编译、跨平台、部署最简单。代码与场景一完全一致只是把 driver 换掉enginecreate_engine(dorispymysql://user:pass127.0.0.1:9030/demo)# 后续 Session、ORM、查询等用法完全相同场景三aiomysql异步生态成熟aiomysql是基于 PyMySQL 的纯 Python 异步实现迁移成本最低fromsqlalchemy.ext.asyncioimportcreate_async_engine,AsyncSessionfromsqlalchemyimporttext enginecreate_async_engine(dorisaiomysql://user:pass127.0.0.1:9030/demo)asyncdefmain():asyncwithAsyncSession(engine)ass:awaits.execute(text(SELECT 1))rows(awaits.execute(text(SELECT version()))).all()print(rows)importasyncio;asyncio.run(main())场景四asyncmy异步性能优先asyncmy是 cython 实现的异步 MySQL 客户端相比 aiomysql 在高并发下性能更好适合对吞吐有要求的 FastAPI 服务enginecreate_async_engine(dorisasyncmy://user:pass127.0.0.1:9030/demo)# 后续用法与 aiomysql 完全一致同样地因为 dialect 基类共享业务代码在三个 driver 间切换是透明的——只是连接 URL 前缀不同。选型建议简单说怎么选同步 极致性能ETL、长任务选dorismysqldbC 扩展吞吐最高同步 不想编译原生依赖选dorispymysql纯 Python 部署最简单异步 追求纯 Python 部署选dorisaiomysql异步 高并发吞吐选dorisasyncmycython 实现性能更好。业务侧推荐保持一致的 URL 写法driver 部分只放 xxx业务代码不感知具体驱动方便后续在测试或压测场景随时切换。与 pydoris 的关系需要强调一点doris-python不是一个平替而是 pydoris SQLAlchemy 集成在多驱动场景下的扩展。如果你的项目只用了 pydoris 原生客户端不走 SQLAlchemy那 pydoris 完全够用但只要你的项目是SQLAlchemy 一统天下又希望 Doris 也能接进来、又希望在同步/异步驱动之间自由切换那么doris-python就是为这个场景准备的。简而言之需求选型纯 pydoris 客户端调用pydorisSQLAlchemy 只用一种驱动pydoris 或 doris-python 都可SQLAlchemy 同步/异步驱动可切换doris-python项目地址仓库已开源欢迎提 Issue / PRgit clone https://github.com/your-org/doris-python.git cd doris-python pip install -e .[all]如果你正在被SQLAlchemy 接入 Doris 该用哪个驱动困扰不妨从dorispymysql开始三行代码跑通后再无负担地切到dorisasyncmy把同步链路异步化——这就是这个仓库存在的意义。