doris-python:让 SQLAlchemy 玩转 Apache Doris 多驱动生态

Doris-Python:让 SQLAlchemy 自由切换 Doris 同步/异步驱动

背景

Apache Doris 的 Python 生态里,最知名的连接库是 pydoris 官方 SDK。它功能完善、协议实现扎实,是 Doris Python 接入的"事实标准"。

但 pydoris 在 SQLAlchemy 场景下有一个很现实的限制:只支持了一种驱动。也就是说,如果你已经在项目里统一用 SQLAlchemy 来管理连接池、ORM、迁移等能力,又想接入 Doris,那么无论你之前习惯用 mysqldb、pymysql、aiomysql 还是 asyncmy,pydoris 的 SQLAlchemy 方言都会把你"绑定"到一种实现上。

现实中一个常见的搭配是:

  • 同步链路SQLAlchemy + PyMySQL,生态最成熟、文档最多;
  • 异步链路SQLAlchemy 2.0 async + aiomysqlasyncmy,需要在 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) doris+mysqldb 传统服务、ETL 脚本、Django、Flask、命令行工具,C 扩展性能最好
同步 pymysql doris+pymysql 不想编译原生依赖、追求纯 Python 部署的传统服务
异步 aiomysql doris+aiomysql FastAPI / Starlette,基于 PyMySQL 的纯 Python 异步驱动
异步 asyncmy doris+asyncmy 高并发异步场景,cython 实现的异步驱动,性能更好

安装好之后,你只需要在 SQLAlchemy 的 URL 里替换驱动前缀即可:

python 复制代码
# 同步:mysqldb
engine = create_engine("doris+mysqldb://user:pass@host:9030/mydb")

# 同步:pymysql
engine = create_engine("doris+pymysql://user:pass@host:9030/mydb")

# 异步:aiomysql
engine = create_async_engine("doris+aiomysql://user:pass@host:9030/mydb")

# 异步:asyncmy
engine = create_async_engine("doris+asyncmy://user:pass@host:9030/mydb")

不需要为每种驱动维护一份方言实现,也不需要修改业务代码------切换驱动 = 改 URL 前缀。

核心设计

仓库代码量不大,但结构清晰,全部位于 src/doris_python/sqlalchemy/ 下:

bash 复制代码
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 独有的 HLLBITMAPQUANTILE_STATEARRAY<T>MAP<K,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 时这样写:

python 复制代码
from sqlalchemy import create_engine, text

engine = create_engine("doris+pymysql://user:pass@host:9030/mydb")
with engine.connect() as conn:
    rows = conn.execute(text("SELECT version()")).all()

改成异步只是 URL 变化,业务代码变成 async:

python 复制代码
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text

engine = create_async_engine("doris+aiomysql://user:pass@host:9030/mydb")

async with engine.connect() as conn:
    rows = (await conn.execute(text("SELECT version()"))).all()

⚠️ 注意:因为 doris+aiomysql / doris+asyncmy 是 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 / DATETIME
  • Doris 特有:HLL(近似去重)、BITMAP(位图)、QUANTILE_STATE(分位数近似)、JSON / JSONBARRAY<T>MAP<K,V>STRUCT<...>
  • 布尔:BOOLEAN(在 Doris 中实际是 TINYINT,基类做了归一)

建表时的 DUPLICATE/UNIQUE/AGGREGATE KEY 模型和 DISTRIBUTED BY ... BUCKETS N 也是基类编译出来的,不需要业务侧手动拼字符串。

安装与依赖

doris-python 本身不强制安装具体驱动,按需安装即可:

bash 复制代码
# 基础
pip install doris-python

# 同步:mysqldb(mysqlclient,C 扩展)
pip install "doris-python[mysqldb]"

# 同步:pymysql(纯 Python)
pip install "doris-python[pymysql]"

# 异步:aiomysql
pip install "doris-python[aiomysql]"

# 异步:asyncmy
pip install "doris-python[asyncmy]"

# 全部驱动
pip install "doris-python[all]"

可选依赖通过 pyproject.toml 的 extras 管理,按需拉取,避免污染用户的运行时环境。

实际用法

场景一:mysqldb(同步,性能优先)

mysqlclient(即 mysqldb)是对 libmysqlclient 的 C 扩展封装,是 SQLAlchemy 同步链路里性能最好的 MySQL 驱动 。如果你已经有一份用 mysqlclient 跑 MySQL 的代码,迁移到 Doris 几乎是零改动:

python 复制代码
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import declarative_base, Session

Base = declarative_base()

class Order(Base):
    __tablename__ = "orders"
    id = Column(Integer, primary_key=True)
    sku = Column(String(64))

engine = create_engine("doris+mysqldb://user:pass@127.0.0.1:9030/demo")

# 建表(Doris 风格的 CREATE TABLE 由 dialect 渲染)
Base.metadata.create_all(engine)

with Session(engine) as s:
    s.add(Order(id=1, sku="A001"))
    s.commit()

场景二:PyMySQL(同步,部署简单)

PyMySQL 是纯 Python 实现,免编译、跨平台、部署最简单。代码与场景一完全一致,只是把 driver 换掉:

python 复制代码
engine = create_engine("doris+pymysql://user:pass@127.0.0.1:9030/demo")
# 后续 Session、ORM、查询等用法完全相同

场景三:aiomysql(异步,生态成熟)

aiomysql 是基于 PyMySQL 的纯 Python 异步实现,迁移成本最低:

python 复制代码
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy import text

engine = create_async_engine("doris+aiomysql://user:pass@127.0.0.1:9030/demo")

async def main():
    async with AsyncSession(engine) as s:
        await s.execute(text("SELECT 1"))
        rows = (await s.execute(text("SELECT version()"))).all()
        print(rows)

import asyncio; asyncio.run(main())

场景四:asyncmy(异步,性能优先)

asyncmy 是 cython 实现的异步 MySQL 客户端,相比 aiomysql 在高并发下性能更好,适合对吞吐有要求的 FastAPI 服务:

python 复制代码
engine = create_async_engine("doris+asyncmy://user:pass@127.0.0.1:9030/demo")
# 后续用法与 aiomysql 完全一致

同样地,因为 dialect 基类共享,业务代码在三个 driver 间切换是透明的------只是连接 URL 前缀不同。

选型建议

简单说怎么选:

  • 同步 + 极致性能(ETL、长任务) :选 doris+mysqldb,C 扩展吞吐最高;
  • 同步 + 不想编译原生依赖 :选 doris+pymysql,纯 Python 部署最简单;
  • 异步 + 追求纯 Python 部署 :选 doris+aiomysql
  • 异步 + 高并发吞吐 :选 doris+asyncmy,cython 实现性能更好。

业务侧推荐保持一致的 URL 写法:driver 部分只放 +xxx,业务代码不感知具体驱动,方便后续在测试或压测场景随时切换。

与 pydoris 的关系

需要强调一点:doris-python 不是一个平替,而是 pydoris SQLAlchemy 集成在多驱动场景下的扩展 。如果你的项目只用了 pydoris 原生客户端(不走 SQLAlchemy),那 pydoris 完全够用;但只要你的项目是"SQLAlchemy 一统天下",又希望 Doris 也能接进来、又希望在同步/异步驱动之间自由切换,那么 doris-python 就是为这个场景准备的。

简而言之:

需求 选型
纯 pydoris 客户端调用 pydoris
SQLAlchemy + 只用一种驱动 pydoris 或 doris-python 都可
SQLAlchemy + 同步/异步驱动可切换 doris-python

项目地址

仓库已开源,欢迎提 Issue / PR:

bash 复制代码
git clone https://github.com/<your-org>/doris-python.git
cd doris-python && pip install -e ".[all]"

如果你正在被"SQLAlchemy 接入 Doris 该用哪个驱动"困扰,不妨从 doris+pymysql 开始,三行代码跑通后,再无负担地切到 doris+asyncmy 把同步链路异步化------这就是这个仓库存在的意义。

相关推荐
RainCity1 小时前
Java Swing 自定义组件库分享(十二)
java·笔记·后端
Csvn2 小时前
Linux 系统性能监控与瓶颈排查
后端
铁皮饭盒2 小时前
Rust版Bun1.4之前, 盘点Bun1.3新特性
前端·javascript·后端
kfaino9 小时前
码农的AI翻身(五)你好,我叫 Transformer
后端·aigc
Oneslide15 小时前
机械革命 单系统纯净重装Ubuntu(全盘覆盖,清空原有Windows)
后端
GetcharZp15 小时前
告别OOM!用Go+libvips实现30000×50000超大图片的流式瓦片服务
后端·go
IT_陈寒16 小时前
JavaScript项目实战经验分享
前端·人工智能·后端