mssql-python 驱动是 Microsoft 为 Microsoft SQL 开发的第一方 Python 驱动程序。 如果你更喜欢Microsoft维护的驱动选项,它提供:
- 没有外部 ODBC 驱动依赖。
- 内置连接池功能。
- 支持现代 Python 3.10+ 版本。
- 原生 Microsoft Entra 认证。
主要差异
| Feature | pyodbc | mssql-python |
|---|---|---|
| 参数样式 |
qmark(?) |
qmark (?) 和 pyformat (%(name)s) |
| 需要ODBC驱动 | 是的 | 否 |
| 连接池 | 外部 | 内置 |
| 最低限度的Python | 3.6 | 3.10 |
callproc() |
支持 | 未实现 |
| 自动提交默认 | Off | Off |
基本迁移步骤
以下步骤涵盖了将 pyodbc 应用迁移到 mssql-python 的关键变更。
1. 更新导入
将 pyodbc 导入替换为 mssql_python:
变更前(pyodbc):
import pyodbc
在(mssql-python)之后:
import mssql_python
2. 更新连接字符串
移除 DRIVER= 关键词并更新认证方法:
之前(pyodbc,需要 ODBC 驱动):
conn = pyodbc.connect(
"DRIVER={ODBC Driver 18 for SQL Server};"
"SERVER=localhost;"
"DATABASE=AdventureWorks2022;"
"Trusted_Connection=yes;"
)
之后(mssql-python,无需驱动程序,使用 Microsoft Entra 身份验证):
conn = mssql_python.connect(
"Server=<server>.database.windows.net;"
"Database=<database>;"
"Authentication=ActiveDirectoryDefault;"
"Encrypt=yes;"
)
3. 保持你的查询原样
mssql-python驱动支持 ? (qmark)和 %(name)s (pyformat)参数样式。 你现有的 ? 查询无需更改即可正常运行:
变更前(pyodbc):
cursor.execute("SELECT * FROM Production.Product WHERE ProductID = ? AND Color = ?", (1, "Red"))
之后(mssql-python,同一查询):
cursor.execute("SELECT * FROM Production.Product WHERE ProductID = ? AND Color = ?", (1, "Red"))
4. 保持 executemany 不变
现有的带有元组和 ? 标记的 executemany 调用无需更改:
变更前(pyodbc):
cursor.execute("CREATE TABLE #MigrateDemo (ID INT, Name NVARCHAR(50))")
data = [(1, "Alice"), (2, "Bob"), (3, "Carol")]
cursor.executemany("INSERT INTO #MigrateDemo (ID, Name) VALUES (?, ?)", data)
之后(mssql-python,同样的代码):
cursor.execute("DROP TABLE IF EXISTS #MigrateDemo")
cursor.execute("CREATE TABLE #MigrateDemo (ID INT, Name NVARCHAR(50))")
data = [(1, "Alice"), (2, "Bob"), (3, "Carol")]
cursor.executemany("INSERT INTO #MigrateDemo (ID, Name) VALUES (?, ?)", data)
存储过程迁移
mssql-python驱动不实现 callproc()。 以下各节将介绍如何改为使用 EXECUTE。
对于存储过程使用EXECUTE
pyodbc 驱动支持 ,但 mssql-python 驱动不支持 callproc()。 请改用 EXECUTE :
变更前(pyodbc):
cursor.callproc("dbo.uspGetEmployeeManagers", (5,))
results = cursor.fetchall()
在(mssql-python)之后:
cursor.execute(
"EXECUTE dbo.uspGetEmployeeManagers @BusinessEntityID = %(id)s",
{"id": 5}
)
results = cursor.fetchall()
print(f"Got {len(results)} rows")
输出参数
使用T-SQL变量捕捉输出值,而非依赖 callproc() 输出参数:
之前(pyodbc,使用callproc):
params = (category_id, pyodbc.SQL_INTEGER)
cursor.callproc("dbo.GetProductCount", params)
count = params[1].value
之后(mssql-python,使用T-SQL变量):
cursor.execute(
"""
DECLARE @count INT;
SELECT @count = COUNT(*) FROM Production.Product
WHERE ProductSubcategoryID = %(cat_id)s;
SELECT @count AS ProductCount;
""",
{"cat_id": 1}
)
product_count = cursor.fetchval()
print(f"Product count: {product_count}")
特征特定迁移
以下章节将介绍pyodbc的具体特性及其mssql-python等价物。
连接字符串
| pyodbc 关键字 | MSSQL-Python 关键字 | 注释 |
|---|---|---|
DRIVER={...} |
不需要 | ODBC驱动集成在内部。 |
SERVER= |
Server= |
无行为更改。 |
DATABASE= |
Database= |
无行为更改。 |
Trusted_Connection= |
Trusted_Connection= |
无行为更改。 |
UID= / PWD= |
UID= / PWD= |
无行为更改。 |
Authentication= |
Authentication= |
接受相同的价值观。 |
自动提交
这两个驱动程序的自动提交行为完全一致:
pyodbc:
conn.autocommit = True
pyodbc.connect(connection_string, autocommit=True)
MSSQL-python:
conn.autocommit = True
批量插入
为了加快较大的 INSERT 批次处理速度,pyodbc 用户会设置 fast_executemany = True。 mssql-python驱动已经针对参数化批处理进行了 executemany 优化,因此适度插入不需要特殊标记。 对于大批量数据加载,优先使用 bulkcopy(),它通过批量复制协议以流式方式传输行数据,比逐条执行 INSERT 语句快得多。 完整工作流程请参见 使用批量复制。
pyodbc:
cursor.fast_executemany = True
cursor.executemany(query, data)
在(mssql-python)之后,使用中等批量:executemany
cursor.execute("DROP TABLE IF EXISTS #BulkTarget")
cursor.execute("CREATE TABLE #BulkTarget (ID INT, Name NVARCHAR(50))")
data = [(i, f"Item {i}") for i in range(100)]
cursor.executemany("INSERT INTO #BulkTarget (ID, Name) VALUES (?, ?)", data)
conn.commit()
在(mssql-python)之后,大量加载(首选 bulkcopy):
cursor.execute("IF OBJECT_ID('##BulkTarget') IS NOT NULL DROP TABLE ##BulkTarget")
cursor.execute("CREATE TABLE ##BulkTarget (ID INT, Name NVARCHAR(50))")
conn.commit() # Commit DDL before bulkcopy
data = [(i, f"Item {i}") for i in range(100)]
result = cursor.bulkcopy("##BulkTarget", data)
print(f"Bulk copied {result['rows_copied']} rows")
cursor.execute("DROP TABLE ##BulkTarget")
conn.commit()
行生成器
mssql-python驱动默认返回 Row 支持属性访问的对象,无需自定义行工厂:
pyodbc(自定义行工厂函数):
def namedtuple_row_factory(cursor):
from collections import namedtuple
columns = [col[0] for col in cursor.description]
Row = namedtuple("Row", columns)
return Row
MSSQL-python(默认属性访问):
cursor.execute("SELECT Name, ListPrice FROM Production.Product")
row = cursor.fetchone()
print(row.Name) # Attribute access works directly
print(row[0]) # Index access also works
错误处理
mssql-python 驱动使用与 pyodbc 相同的异常层级结构,因此大多数异常处理程序只需更改模块名称。
异常层次结构
例外类名称直接映射到驱动程序之间:
pyodbc:
try:
cursor.execute(query)
except pyodbc.Error as e:
pass
except pyodbc.DatabaseError as e:
pass
except pyodbc.OperationalError as e:
pass
MSSQL-python:
try:
cursor.execute("SELECT TOP 1 * FROM Production.Product")
print(cursor.fetchone())
except mssql_python.Error as e:
pass
except mssql_python.DatabaseError as e:
pass
except mssql_python.OperationalError as e:
pass
错误详细信息
这两个驱动程序都通过例外参数暴露错误细节:
pyodbc:
try:
cursor.execute(query)
except pyodbc.Error as e:
sqlstate = e.args[0]
message = e.args[1]
MSSQL-python:
try:
cursor.execute("SELECT TOP 1 * FROM NonExistentTable_XYZ")
except mssql_python.Error as e:
# Error message contains SQLSTATE and details
print(str(e))
连接池
mssql-python驱动默认支持连接池,因此不再需要外部池化库。
移除外部池化
如果你用过 pyodbc 的外部池化,mssql-python 驱动里就内置了:
之前(pyodbc 外部池):
from dbutils.pooled_db import PooledDB
pool = PooledDB(pyodbc, 5, driver="{ODBC Driver 18 for SQL Server}",
server="your_server", database="your_database",
uid="your_username", pwd="your_password")
conn = pool.connection()
之后(mssql-python 内置连接池):
conn = mssql_python.connect(connection_string)
conn.close()
配置资源池
使用 mssql_python.pooling() 覆盖默认的池大小和超时时间:
import mssql_python
mssql_python.pooling()
完整迁移示例
下面展示了用 pyodbc 编写并用 mssql-python 重写的同一函数。
之前(pyodbc)
该版本使用包含 DRIVER 关键字的 pyodbc 连接字符串:
import pyodbc
from datetime import date
def get_orders(customer_id: int, start_date: date):
conn = pyodbc.connect(
"DRIVER={ODBC Driver 18 for SQL Server};"
"SERVER=localhost;"
"DATABASE=AdventureWorks2022;"
"Trusted_Connection=yes;"
)
cursor = conn.cursor()
cursor.execute("""
SELECT SalesOrderID, OrderDate, TotalDue
FROM Sales.SalesOrderHeader
WHERE CustomerID = ? AND OrderDate >= ?
ORDER BY OrderDate DESC
""", (customer_id, start_date))
orders = []
for row in cursor:
orders.append({
"id": row.SalesOrderID,
"date": row.OrderDate,
"total": row.TotalDue
})
cursor.close()
conn.close()
return orders
之后 (mssql-python)
此版本去除了关键 DRIVER 词。 所有查询、参数和行访问模式保持相同:
import mssql_python
from datetime import date
def get_orders(customer_id: int, start_date: date):
conn = mssql_python.connect(
"Server=localhost;"
"Database=AdventureWorks2022;"
"Trusted_Connection=yes;"
)
cursor = conn.cursor()
cursor.execute("""
SELECT SalesOrderID, OrderDate, TotalDue
FROM Sales.SalesOrderHeader
WHERE CustomerID = ? AND OrderDate >= ?
ORDER BY OrderDate DESC
""", (customer_id, start_date))
orders = []
for row in cursor:
orders.append({
"id": row.SalesOrderID,
"date": row.OrderDate,
"total": row.TotalDue
})
cursor.close()
conn.close()
return orders
唯一的变化是导入语句和连接字符串(不需要DRIVER关键词)。 每个查询、参数、提取模式和行访问都完全一致。
迁移测试
在完成迁移前,对两个驱动运行相同的查询并比较结果,以确认行为是否等效。
验证等效行为
使用一个比较函数,对两个驱动运行相同的查询,并断言结果匹配:
import pyodbc
import mssql_python
def compare_results(pyodbc_conn_str: str, mssql_conn_str: str, query: str):
"""Compare results from both drivers."""
# pyodbc query
pyodbc_conn = pyodbc.connect(pyodbc_conn_str)
pyodbc_cursor = pyodbc_conn.cursor()
pyodbc_cursor.execute(query)
pyodbc_results = pyodbc_cursor.fetchall()
pyodbc_conn.close()
# mssql-python query
mssql_conn = mssql_python.connect(mssql_conn_str)
mssql_cursor = mssql_conn.cursor()
mssql_cursor.execute(query)
mssql_results = mssql_cursor.fetchall()
mssql_conn.close()
# Compare
assert len(pyodbc_results) == len(mssql_results)
for p_row, m_row in zip(pyodbc_results, mssql_results):
assert tuple(p_row) == tuple(m_row)
print(f"Results match: {len(pyodbc_results)} rows")
清单
- [ ] 将导入从
pyodbc更新为mssql_python。 - [ ] 从连接线中取出
DRIVER=。 - [ ] 保留现有
?参数查询(它们可按原样使用)。 - [ ] 使用
EXECUTE语句来调用存储过程调用。 - [ ] 移除外部连接池配置。
- [ ] 更新异常处理类名。
- [ ] 测试所有查询和存储过程。
- [ ] 验证数据类型的处理(尤其是小数和日期)。
- [ ] 将ODBC驱动从部署要求中移除。