ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python数据读取操作pd.read_sql_query()与cursor.execute()的区别:TaoToken统一Key下实测对比

Python数据读取操作pd.read_sql_query()与cursor.execute()的区别:TaoToken统一Key下实测对比 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 查询结果转成 DataFramecursor.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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_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 URLhttps://taotoken.net/apiAPI Key在控制台 API Keys 页面生成形如sk-开头Model ID按你实际调用的模型填写比如做数据摘要时用到的对话模型生成 Key 的入口https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Keys 管理页https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。2.3 环境依赖安装pip install pandas sqlalchemy pymysql python-dotenv如果你用 sqlite3标准库自带不用额外装。用 MySQL 就装 pymysql用 PostgreSQL 装 psycopg2。pandas 版本建议 2.xread_sql_query在 2.x 里对连接对象的要求更明确。2.4 把 Key 写进环境变量而不是代码# .env 文件 TAOTOKEN_API_KEYsk-你的key TAOTOKEN_BASE_URLhttps://taotoken.net/api DB_URLmysqlpymysql://user:pass127.0.0.1:3306/demoimport 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或%spymysql。返回的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), chunksize10000) 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_existsreplace, indexFalse) 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.1Mdate 列 dtypeobjectobjectsqlite 无原生日期在 MySQL 上跑read_sql_query的 date 列会直接是 datetime64cursor 方式拿到的是字符串还得自己pd.to_datetime。这一步差异在后续做时间序列分析时影响很大。4.4 用 TaoToken 通道做结果摘要如果你想把查询结果交给模型做一句话摘要可以复用同一个 Keyimport requests resp requests.post( f{BASE_URL}/v1/chat/completions, headers{Authorization: fBearer {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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth5.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_PROXY5.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/apiKey 是否填在正确字段。Claude Code 接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。接入文档里有各语言的最小示例遇到驱动差异可以直接对照https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。API Key 在控制台随时可以轮换别把 Key 硬编码进脚本提交到仓库。
返回列表