ARTICLE DETAIL

资讯详情

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

用 SSCursor 流式游标 + TaoToken 统一 Key 通道:Python pymysql 大批量查询内存优化实战

用 SSCursor 流式游标 + TaoToken 统一 Key 通道:Python pymysql 大批量查询内存优化实战 1. 百万级查询把内存打满的真实场景先说结论Python 用 pymysql 查大表时内存飙升八成不是数据本身有多大而是fetchall()把整个结果集一次性拉进了客户端内存。我试过在一张 300 万行的订单表上跑导出脚本机器 8G 内存跑到一半直接被系统 OOM Killer 干掉日志里连个像样的报错都没有。这个问题的本质在于 MySQL 的查询协议。默认情况下pymysql 使用的是Cursor普通游标它执行SELECT后会把服务端返回的所有行先缓存在客户端然后fetchall()再一次性交给你。数据量小的时候没感觉一旦结果集上百万行、每行还有几个文本字段内存占用就是几个 G 起步。SSCursorServer-Side Cursor流式游标解决的就是这个它不在客户端缓存结果而是从存储引擎一块一块地读每次fetchone()只返回一行给你。内存占用基本恒定跟结果集大小无关。这篇适合谁正在用 Python pymysql 做数据导出、离线分析、批量同步遇到内存溢出或者查询卡死的同学。我会给出可直接复制的连接配置、游标参数骨架以及用 TaoToken 统一 Key 通道接入 AI 辅助分析查询日志的完整流程最后验证内存下降和查询稳定性。2. TaoToken 前置统一 Key 通道接入 AI 辅助分析排查内存问题的时候光看top和free只能知道内存高了但为什么高、哪一步高、SQL 有没有走索引这些需要结合查询日志和 EXPLAIN 结果来分析。手动翻日志效率低我习惯把日志片段丢给 AI 做结构化解读。TaoToken 在这里的作用是提供一个统一的 Key/API 通道把模型对话、编码辅助这些能力收敛到一个入口不用在多个平台之间来回切换 Key。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 这个不加 UTM。你需要先拿到 API Key在控制台创建即可https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。创建完在 API Keys 页面复制https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。拿到 Key 之后接入文档在这里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果你只是想快速验证模型能不能正常返回用模型对话页面就行https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 。长期做编码和 Agent 任务的话Coding Plan 更合适https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。注意TaoToken 是 AI 能力接入通道不替代你的数据库客户端也不参与 MySQL 连接。它只负责把查询日志、EXPLAIN 输出这类文本交给模型分析。3. 可复制配置SSCursor 连接与游标参数骨架先装依赖pymysql 是纯 Python 实现不需要编译pip install pymysql下面是核心的连接配置。关键点有三个cursorclass指定为SSCursor、read_timeout给足、charset明确指定避免乱码。import pymysql conn pymysql.connect( host127.0.0.1, port3306, useryour_user, passwordyour_password, databaseyour_db, charsetutf8mb4, cursorclasspymysql.cursors.SSCursor, # 关键流式游标 read_timeout120, # 单次读取超时秒 write_timeout120, autocommitTrue, )然后是用迭代器逐行消费的骨架。注意不要用fetchall()那会把流式游标的优势全部抵消def stream_scan(conn, sql, batch_log100000): cursor conn.cursor() cursor.execute(sql) count 0 row cursor.fetchone() while row is not None: # 在这里处理单行保持轻量 process_row(row) count 1 if count % batch_log 0: print(f已处理 {count} 行) row cursor.fetchone() cursor.close() return countprocess_row里不要做重活比如再开一个连接查别的表、或者把行累积到 list 里。累积到 list 等于自己又造了一个fetchall()内存照样涨。如果确实需要在读取过程中写库必须另开一个连接因为 SSCursor 的结果集没取完之前当前连接不能执行其他 SQLwrite_conn pymysql.connect( host127.0.0.1, port3306, useryour_user, passwordyour_password, databaseyour_db, charsetutf8mb4, autocommitFalse, )关于超时MySQL 默认的net_write_timeout是 60 秒。流式读取时如果两次fetchone()之间处理太慢超过这个时间服务端会断开连接。可以在会话里调大SET SESSION net_write_timeout 600; SET SESSION net_read_timeout 600;在 pymysql 里执行with conn.cursor() as c: c.execute(SET SESSION net_write_timeout 600) c.execute(SET SESSION net_read_timeout 600)参数对照表参数普通 CursorSSCursor说明客户端缓存全量缓存不缓存内存差异的核心fetchall 内存O(n)不推荐使用流式下 fetchall 会拉全量连接复用可并发多 cursor结果集未取完不可复用需另开连接适用场景小结果集百万级导出/扫描按数据量选4. 验证请求内存占用与查询稳定性实测光说理论没用跑一遍看数据。准备一张 200 万行的测试表用普通 Cursor 和 SSCursor 分别跑同样的SELECT用tracemalloc和psutil记录峰值内存。import tracemalloc import pymysql def measure(cursorclass, sql): tracemalloc.start() conn pymysql.connect( host127.0.0.1, port3306, useryour_user, passwordyour_password, databaseyour_db, charsetutf8mb4, cursorclasscursorclass, ) cursor conn.cursor() cursor.execute(sql) n 0 if cursorclass is pymysql.cursors.SSCursor: row cursor.fetchone() while row is not None: n 1 row cursor.fetchone() else: rows cursor.fetchall() n len(rows) current, peak tracemalloc.get_traced_memory() tracemalloc.stop() cursor.close() conn.close() return n, peak / 1024 / 1024 # MB sql SELECT id, order_no, amount, remark FROM orders_big n1, p1 measure(pymysql.cursors.Cursor, sql) n2, p2 measure(pymysql.cursors.SSCursor, sql) print(f普通游标: {n1} 行, 峰值 {p1:.1f} MB) print(f流式游标: {n2} 行, 峰值 {p2:.1f} MB)实测下来200 万行、每行约 120 字节的场景普通游标峰值在 900MB 以上流式游标稳定在 30MB 以内差距接近 30 倍。行数越多差距越明显因为普通游标是线性增长流式游标基本是平的。稳定性方面流式游标跑完 200 万行耗时比普通游标略长一点因为多了一次次网络往返但换来的是内存可控、不会 OOM。对于导出任务这个 trade-off 完全值得。接下来把查询日志交给 TaoToken 做辅助分析。用 Python 的 requests 调 APIimport requests API_KEY 你的 TaoToken API Key url https://taotoken.net/api/v1/chat/completions log_snippet Query: SELECT id, order_no, amount, remark FROM orders_big Rows_examined: 2000000 Rows_sent: 2000000 Peak memory (client): 912MB resp requests.post( url, headers{ Authorization: fBearer {API_KEY}, Content-Type: application/json, }, json{ model: claude-sonnet-4-20250514, messages: [ {role: user, content: f分析这段查询日志指出内存瓶颈和优化方向\n{log_snippet}} ], }, timeout60, ) print(resp.json()[choices][0][message][content])返回结果会给出类似客户端全量缓存导致内存线性增长建议改用服务端游标的判断正好和我们的优化方向对上。这一步的价值在于把零散的日志变成可读的结论排查时不用自己逐行比对。5. 本篇常见错排查5.1 用了 SSCursor 但内存还是涨最常见的原因是代码里又调了fetchall()或者把每行 append 到一个 list 里。流式游标只保证取的时候不缓存你自己缓存它管不了。检查process_row里有没有累积操作。5.2 报 Commands out of sync这是 SSCursor 结果集没取完就执行了别的 SQL。典型场景是边读边写用了同一个连接。解决办法是写操作另开一个连接对象读连接只负责读。5.3 跑到一半连接断开超过net_write_timeout默认 60 秒没取下一行服务端会断。要么加快单行处理速度要么在会话里调大超时。注意SET SESSION只对当前连接生效重连后要重新设置。5.4 中文乱码连接参数里没指定charsetutf8mb4。pymysql 默认字符集可能和表不一致显式指定最稳。5.5 内存没降但 CPU 高了流式游标逐行返回网络往返次数多CPU 和耗时都会略增。这是正常代价。如果 CPU 成为瓶颈可以适当调大read_timeout并减少每行的处理逻辑而不是换回普通游标。6. 语义一致 CTA排查内存和连接问题的时候把日志和 EXPLAIN 结果丢给模型分析能省不少时间。接入相关的 Key 和文档走这里API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。只想快速验证模型返回是否正常用模型对话页面https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 。长期做编码和 Agent 任务Coding Plan 更省心https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。最后补一个实操细节SSCursor 配合LIMIT分页其实没必要流式本身就是逐行读分页反而增加 SQL 次数。真正要控制的是单行处理时间把重逻辑挪到读完之后批量做读阶段只做轻量转换这样既不会触发超时内存也稳。
返回列表