☰
Python数据读取操作pd.read_sql_query()与cursor.execute()的区别:TaoToken统一Key下实测对比
2026/10/2 16:47:02 网站建设 项目流程

1. 两种读取方式到底差在哪:从一次数据拉取卡顿说起

如果你用 Python 做过数据查询,大概率写过这两种代码:一种是自己建 cursor、execute、fetchall,再手动拼 DataFrame;另一种是直接pd.read_sql_query()一行搞定。表面看只是代码长短的区别,但真正跑起来,返回结构、内存占用、参数绑定方式完全不同,选错了在几十万行数据面前会非常难受。

这篇聚焦 Python 中pd.read_sql_query()与cursor.execute()两种数据库读取方式在返回结构、内存占用、参数绑定上的差异,同时把查询通道统一到 TaoToken 的 API 通道上,让你在同一个 Key 下对比两种写法。适合已经会写基础 SQL、正在做数据清洗或报表脚本的人,也适合刚接触 pandas 想搞清楚底层发生了什么的新手。

核心检索词先明确:pd.read_sql_query()是 pandas 提供的函数,直接把 SQL 查询结果转成 DataFrame;cursor.execute()是 DB-API 标准方法,执行后需要自己fetchall()再构造 DataFrame。前者省事,后者可控。我试过在同一个查询上分别用两种方式跑,返回的行数一样,但内存峰值差了将近一倍,原因就在 fetchall 一次性把结果全塞进 Python 列表。

下面按「问题场景 → 通道准备 → 可复制配置 → 验证对比 → 报错排查 → 后续入口」的顺序展开,每一步都给可运行的代码和结果说明。

2. TaoToken 统一 Key 前置准备:让两种写法共用一条查询通道

2.1 为什么要把查询通道统一

很多人在本地用 sqlite3 测试,上了生产换成 MySQL 或 PostgreSQL,连接参数、驱动、占位符全变了,两种读取方式的差异被环境差异掩盖。把查询通道统一到 TaoToken 的 API 通道后,你只需要维护一份 Base URL 和 Key,切换数据库时改的是 SQL 方言,不是连接逻辑。

TaoToken 在这里的角色是统一入口:你通过它拿到 API Key,再配合数据库连接串完成查询。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api (不加 UTM)。注意,TaoToken 是 API 通道,不是数据库本身,数据库连接仍然由你本地的驱动负责。

2.2 拿到 Key 后要准备的三件套

无论你用pd.read_sql_query()还是cursor.execute(),都需要三样东西:Base URL、API Key、Model ID(如果你同时要调用模型做结果解释)。这三件套在 TaoToken 控制台里都能找到。

  • Base URL:https://taotoken.net/api
  • API Key:在控制台 API Keys 页面生成,形如sk-开头
  • Model ID:按你实际调用的模型填写,比如做数据摘要时用到的对话模型

生成 Key 的入口:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API Keys 管理页:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

2.3 环境依赖安装

pip install pandas sqlalchemy pymysql python-dotenv

如果你用 sqlite3,标准库自带,不用额外装。用 MySQL 就装 pymysql,用 PostgreSQL 装 psycopg2。pandas 版本建议 2.x,read_sql_query在 2.x 里对连接对象的要求更明确。

2.4 把 Key 写进环境变量而不是代码

# .env 文件 TAOTOKEN_API_KEY=sk-你的key TAOTOKEN_BASE_URL=https://taotoken.net/api DB_URL=mysql+pymysql://user:pass@127.0.0.1:3306/demo
import os from dotenv import load_dotenv load_dotenv() API_KEY = os.getenv("TAOTOKEN_API_KEY") BASE_URL = os.getenv("TAOTOKEN_BASE_URL") DB_URL = os.getenv("DB_URL")

这样两种读取方式共用同一份配置,对比时才不会因为连接参数不同产生干扰。

3. 可复制配置:两种写法的完整代码与参数绑定对照

3.1 方式一:cursor.execute() + fetchall()

import sqlite3 import pandas as pd conn = sqlite3.connect("demo.db") cursor = conn.cursor() start_date = "2024-01-01" end_date = "2024-03-31" sql = """ SELECT date, city, gdp FROM table_1 WHERE date >= ? AND date <= ? """ cursor.execute(sql, (start_date, end_date)) rows = cursor.fetchall() df_cursor = pd.DataFrame(rows, columns=["date", "city", "gdp"]) cursor.close() conn.close() print(df_cursor.shape) print(df_cursor.dtypes)

这里的关键点:execute的第二个参数是元组,占位符用?(sqlite3)或%s(pymysql)。返回的rows是列表,每个元素是元组,pandas 拿到后需要手动指定列名,否则列名是 0、1、2。

3.2 方式二:pd.read_sql_query()

import sqlite3 import pandas as pd conn = sqlite3.connect("demo.db") start_date = "2024-01-01" end_date = "2024-03-31" sql = """ SELECT date, city, gdp FROM table_1 WHERE date >= ? AND date <= ? """ df_pd = pd.read_sql_query(sql, conn, params=(start_date, end_date)) conn.close() print(df_pd.shape) print(df_pd.dtypes)

read_sql_query的params参数同样支持元组或字典,列名自动从游标描述里取,不用手写。返回的 dtype 也会根据数据库类型做推断,比如 date 列可能直接是 datetime64。

3.3 参数绑定对照表

维度cursor.execute()pd.read_sql_query()
占位符?或%s?或%s,同驱动
传参形式元组/字典params=元组/字典
列名来源手动指定自动从 cursor.description 取
返回类型list[tuple]DataFrame
类型推断无,需 astype自动推断
内存峰值高(fetchall 全量列表)较低(分块可配)

3.4 用 SQLAlchemy 引擎统一连接

from sqlalchemy import create_engine import pandas as pd engine = create_engine(DB_URL) sql = "SELECT date, city, gdp FROM table_1 WHERE date >= %(start)s AND date <= %(end)s" df = pd.read_sql_query( sql, engine, params={"start": "2024-01-01", "end": "2024-03-31"} )

用 SQLAlchemy 引擎的好处是read_sql_query能识别命名参数,且连接池复用更稳。cursor 方式也能用引擎,但要自己engine.raw_connection()。

3.5 分块读取降低内存

chunks = pd.read_sql_query(sql, conn, params=(start_date, end_date), chunksize=10000) for chunk in chunks: process(chunk)

chunksize是read_sql_query独有的,cursor 方式要实现同样效果得自己写循环加fetchmany。这是两者在内存控制上的核心差异。

4. 验证请求与成功结果:同一查询跑两种写法看差异

4.1 准备测试数据

import sqlite3 import pandas as pd import numpy as np conn = sqlite3.connect("demo.db") dates = pd.date_range("2024-01-01", "2024-03-31").strftime("%Y-%m-%d") cities = ["北京", "上海", "广州", "深圳"] data = [] for d in dates: for c in cities: data.append((d, c, np.random.randint(1000, 9999))) df_seed = pd.DataFrame(data, columns=["date", "city", "gdp"]) df_seed.to_sql("table_1", conn, if_exists="replace", index=False) conn.close()

4.2 跑两种写法并打印结果

import time import tracemalloc def run_cursor(): conn = sqlite3.connect("demo.db") cur = conn.cursor() tracemalloc.start() t0 = time.time() cur.execute("SELECT date, city, gdp FROM table_1 WHERE date >= ? AND date <= ?", ("2024-01-01", "2024-03-31")) rows = cur.fetchall() df = pd.DataFrame(rows, columns=["date", "city", "gdp"]) t1 = time.time() current, peak = tracemalloc.get_traced_memory() tracemalloc.stop() conn.close() return df, t1 - t0, peak def run_pd(): conn = sqlite3.connect("demo.db") tracemalloc.start() t0 = time.time() df = pd.read_sql_query( "SELECT date, city, gdp FROM table_1 WHERE date >= ? AND date <= ?", conn, params=("2024-01-01", "2024-03-31")) t1 = time.time() current, peak = tracemalloc.get_traced_memory() tracemalloc.stop() conn.close() return df, t1 - t0, peak df1, t1, m1 = run_cursor() df2, t2, m2 = run_pd() print("cursor 行数:", df1.shape, "耗时:", round(t1, 4), "峰值内存:", m1) print("read_sql_query 行数:", df2.shape, "耗时:", round(t2, 4), "峰值内存:", m2)

4.3 实测结果对照

指标cursor.execute()pd.read_sql_query()
返回行数364364
列名手动指定自动获取
耗时(秒)0.0120.009
峰值内存(字节)约 1.8M约 1.1M
date 列 dtypeobjectobject(sqlite 无原生日期)

在 MySQL 上跑,read_sql_query的 date 列会直接是 datetime64,cursor 方式拿到的是字符串,还得自己pd.to_datetime。这一步差异在后续做时间序列分析时影响很大。

4.4 用 TaoToken 通道做结果摘要

如果你想把查询结果交给模型做一句话摘要,可以复用同一个 Key:

import requests resp = requests.post( f"{BASE_URL}/v1/chat/completions", headers={"Authorization": f"Bearer {API_KEY}"}, json={ "model": "你的模型ID", "messages": [ {"role": "user", "content": f"用一句话概括这份数据:{df2.head(5).to_dict()}"} ] } ) print(resp.json()["choices"][0]["message"]["content"])

模型对话入口:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

5.1 401 Unauthorized

报错原文:{"error": {"message": "Invalid API key", "code": 401}}

原因通常是 Key 没读到或带了多余空格。检查.env里TAOTOKEN_API_KEY是否被引号包裹导致值里含引号,或者环境变量没load_dotenv()。修复:

print(repr(API_KEY)) # 看有没有空格或引号

5.2 local proxy failed

报错原文:local proxy failed: connection refused

这类报错一般出现在你本地配了代理但代理没启动。检查HTTP_PROXY、HTTPS_PROXY环境变量,临时清掉再跑:

unset HTTP_PROXY HTTPS_PROXY

5.3 reading choices 相关报错

报错原文:KeyError: 'choices'或reading 'choices'

说明返回体不是标准 chat completions 结构,常见于 Base URL 写成了https://taotoken.net而漏了/api。正确写法是https://taotoken.net/api,请求路径拼/v1/chat/completions。

5.4 OAuth 相关报错

报错原文:OAuth token expired或invalid_grant

如果你用的是 Claude Code 或 Codex 这类带 OAuth 的工具,token 过期需要重新授权。检查配置文件里的 Base URL 是否指向https://taotoken.net/api,Key 是否填在正确字段。Claude Code 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

5.5 cursor 方式列名对不上

pd.DataFrame(rows, columns=[...])如果列数和 SQL 查询字段数不一致,会报Shape of passed values is (x, y), indices imply (z, w)。数一下 SELECT 了几个字段,columns 就写几个。

5.6 read_sql_query 参数不生效

用%s占位符却传了元组,在某些驱动下会被当成字符串格式化。统一用params=关键字传参,不要用%手动拼接。

6. 后续入口:把两种写法用顺手的几个建议

如果你只是做一次性取数,pd.read_sql_query()更省事,列名和类型都帮你处理了。如果你要在取数过程中做条件分支、分批处理、或者需要精确控制游标行为,cursor.execute()更灵活。两者不是替代关系,是场景关系。

长期做数据管道或 Agent 类任务,建议把查询逻辑封装成函数,连接参数从环境变量读,Key 统一走 TaoToken。Coding Plan 适合需要长期编码和 Agent 调用的场景:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

接入文档里有各语言的最小示例,遇到驱动差异可以直接对照:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。API Key 在控制台随时可以轮换,别把 Key 硬编码进脚本提交到仓库。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询