尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SQL优化的方法:用TaoToken统一API通道做慢查询定位与索引验证

SQL优化的方法:用TaoToken统一API通道做慢查询定位与索引验证 1. 慢查询日志与执行计划SQL优化的第一步很多后端同学遇到接口变慢第一反应是“加个索引试试”但加完发现没效果甚至更慢了。问题往往出在没搞清楚数据库到底在干什么。SQL优化的起点不是改SQL而是先拿到证据慢查询日志告诉你哪些SQL慢执行计划告诉你为什么慢。先说慢查询日志。MySQL默认是关闭慢查询日志的你需要手动打开。我一般会在测试环境或者预发环境先开生产环境谨慎操作因为日志写入本身有IO开销。-- 查看当前慢查询相关配置 SHOW VARIABLES LIKE slow_query%; SHOW VARIABLES LIKE long_query_time; -- 临时开启重启失效 SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1; SET GLOBAL log_queries_not_using_indexes ON; -- 永久生效修改 my.cnf -- [mysqld] -- slow_query_log 1 -- slow_query_log_file /var/log/mysql/slow.log -- long_query_time 1 -- log_queries_not_using_indexes 1long_query_time设成1秒是个折中值。如果你业务对延迟敏感可以设成0.5秒甚至0.1秒但日志量会明显上升。log_queries_not_using_indexes这个开关要小心它会把所有没走索引的查询都记下来包括那些小表全扫的日志会膨胀得很快建议只在排查阶段临时开。拿到慢查询日志后用mysqldumpslow或者pt-query-digest做聚合分析。pt-query-digest是 Percona Toolkit 里的工具输出更友好能按总耗时、执行次数、平均耗时排序。# 按总耗时排序取前10条 pt-query-digest --order-by Query_time:sum --limit 10 /var/log/mysql/slow.log # 只看某个时间段的 pt-query-digest --since 2024-01-01 00:00:00 --until 2024-01-01 12:00:00 /var/log/mysql/slow.log分析出Top SQL之后下一步就是看执行计划。EXPLAIN是基本功但很多人只看type和key两列其实rows、filtered、Extra同样关键。EXPLAIN SELECT o.id, o.amount, u.name FROM orders o INNER JOIN users u ON o.user_id u.id WHERE u.status 1 AND o.create_time 2024-01-01 ORDER BY o.create_time DESC LIMIT 20;重点看这几列type如果是ALL说明全表扫描index是全索引扫描range是范围扫描ref和eq_ref是比较理想的key显示实际使用的索引如果是NULL就要警惕rows是预估扫描行数和实际返回行数差距大说明统计信息不准Extra里出现Using filesort或Using temporary通常意味着有额外排序或临时表开销。EXPLAIN有个升级版叫EXPLAIN ANALYZEMySQL 8.0.18它会真正执行SQL并给出实际耗时和行数比预估的EXPLAIN更准。EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id 10086 AND status 2;输出里会看到actual time和rows能直接对比预估和实际。如果预估行数和实际行数差一个数量级说明索引统计信息过期了可以执行ANALYZE TABLE orders;更新统计信息。这里有个坑EXPLAIN看到的key不一定是你以为的那个索引。MySQL优化器会根据成本估算选索引有时候会选错。比如你建了idx_a和idx_b优化器可能因为统计信息不准选了idx_b但实际idx_a更快。这时候可以用FORCE INDEX强制走某个索引来验证。SELECT * FROM orders FORCE INDEX(idx_user_status) WHERE user_id 10086 AND status 2;如果强制走某个索引后明显变快说明优化器选错了可以考虑用ANALYZE TABLE更新统计信息或者调整索引顺序让优化器更容易选对。慢查询日志和执行计划是SQL优化的“眼睛”没有这两样后面所有调整都是盲猜。我见过太多人直接上来就加索引结果加了复合索引但查询条件顺序不对索引根本用不上白白浪费了写入性能。2. TaoToken统一API通道多模型辅助SQL优化的前置准备SQL优化过程中除了数据库本身的工具现在越来越多团队会借助大模型来辅助分析执行计划、生成优化建议、甚至自动生成回归测试用例。但这里有个现实问题不同模型的能力和价格差异很大有的擅长写SQL有的擅长分析执行计划有的对MySQL和PostgreSQL的语法差异把握更好。如果每个模型都单独申请Key、单独管理额度团队协作时非常混乱。TaoToken 解决的就是这个问题。它提供一个统一的API通道你用同一个Key就能调用多个主流模型不用在多个平台之间切换。对于SQL优化这种需要“多模型交叉验证”的场景特别实用——你可以让一个模型分析执行计划另一个模型生成优化后的SQL再让第三个模型写回归测试用例全部通过同一个入口调用。先说明一下TaoToken是什么它是一个大模型API聚合网关兼容OpenAI风格的接口协议。你不需要改变现有的代码调用方式只需要把base_url指向TaoToken的API地址把api_key换成TaoToken的Key就能调用背后接入的多个模型。对于已经在用OpenAI SDK的项目迁移成本几乎为零。适合谁用后端开发、DBA、数据工程团队尤其是那些需要在不同模型之间做对比测试、或者想把模型调用统一管理起来的团队。如果你只是偶尔用一下网页版对话那直接用官网的模型对话功能就够了但如果你要把模型能力集成到自己的SQL优化工具链里或者团队多人共用TaoToken的统一Key管理会省很多事。前置准备分三步第一步获取API Key。访问TaoToken的API Keys管理页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite创建一个新的Key。建议按项目或按人分配不同的Key方便后续做用量统计和权限控制。第二步确认你要调用的模型ID。TaoToken的文档页面https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite列出了当前支持的模型列表和对应的Model ID。不同模型在SQL优化场景下的表现差异挺大建议先小规模测试再确定主力模型。第三步配置调用环境。如果你用Python可以直接用openai库如果用Node.js用openai的npm包如果只是命令行测试用curl就行。下面给一个Python的最小示例from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_key你的TaoToken Key ) response client.chat.completions.create( modelclaude-sonnet-4-20250514, messages[ {role: system, content: 你是一个MySQL性能优化专家擅长分析执行计划和索引设计。}, {role: user, content: 以下SQL执行计划显示typeALLExtraUsing filesort请分析原因并给出优化建议\n\nEXPLAIN SELECT ...} ] ) print(response.choices[0].message.content)注意base_url是https://taotoken.net/api不要加UTM参数这是API调用的地址。Key放在环境变量里不要硬编码到代码中。如果你用的是Claude Code或者类似的编码助手TaoToken也支持通过配置接入。Claude Code的配置方式是在settings里指定API地址和Key具体可以参考文档里的ClaudeCodeAnthropic接入说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite。对于长期做SQL优化和编码的团队也可以考虑Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite按周期计费适合高频调用场景。这里要强调一点TaoToken是统一API通道不是数据库代理也不是SQL执行引擎。它只负责模型调用你的SQL还是在你的数据库上执行。模型给出的优化建议需要你自己验证不能直接盲信。我一般会把模型建议和实际EXPLAIN ANALYZE的结果做对比确认有效后再上线。3. 可复制配置慢查询采集、EXPLAIN对比与模型调用这一节给出一套可以直接复制使用的配置和脚本覆盖慢查询采集、EXPLAIN对比、以及通过TaoToken调用模型生成优化建议的完整流程。3.1 MySQL慢查询采集配置在my.cnf中加入以下配置然后重启MySQL[mysqld] slow_query_log 1 slow_query_log_file /var/log/mysql/slow.log long_query_time 1 log_queries_not_using_indexes 0 log_slow_admin_statements 1 log_slow_slave_statements 1 min_examined_row_limit 100min_examined_row_limit 100表示扫描行数少于100的不记录避免小表查询刷屏。log_slow_admin_statements记录慢的DDL操作log_slow_slave_statements在从库上也记录。如果你用的是云数据库比如阿里云RDS或腾讯云CDB慢查询日志通常在控制台可以下载不需要改配置文件。3.2 EXPLAIN对比脚本下面这个Python脚本可以对比优化前后的执行计划输出关键指标差异import pymysql import json def get_explain(conn, sql): with conn.cursor(pymysql.cursors.DictCursor) as cur: cur.execute(fEXPLAIN {sql}) return cur.fetchall() def get_explain_analyze(conn, sql): with conn.cursor(pymysql.cursors.DictCursor) as cur: cur.execute(fEXPLAIN ANALYZE {sql}) return cur.fetchall() def compare_explain(before_sql, after_sql, conn): before get_explain(conn, before_sql) after get_explain(conn, after_sql) print( 优化前 ) for row in before: print(ftable{row[table]}, type{row[type]}, key{row[key]}, rows{row[rows]}, Extra{row[Extra]}) print(\n 优化后 ) for row in after: print(ftable{row[table]}, type{row[type]}, key{row[key]}, rows{row[rows]}, Extra{row[Extra]}) if __name__ __main__: conn pymysql.connect( host127.0.0.1, port3306, userroot, passwordyour_password, databaseyour_db, charsetutf8mb4 ) before_sql SELECT * FROM orders WHERE user_id 10086 AND status 2 ORDER BY create_time DESC LIMIT 20 after_sql SELECT id, amount, create_time FROM orders WHERE user_id 10086 AND status 2 ORDER BY create_time DESC LIMIT 20 compare_explain(before_sql, after_sql, conn) conn.close()这个脚本会打印出优化前后每个表的访问类型、使用的索引、预估行数和Extra信息。你可以把它集成到CI流程里每次SQL变更时自动对比。3.3 TaoToken模型调用配置下面是一个完整的配置文件示例用YAML管理TaoToken的调用参数# taotoken_config.yaml taotoken: base_url: https://taotoken.net/api api_key: ${TAOTOKEN_API_KEY} # 从环境变量读取 default_model: claude-sonnet-4-20250514 timeout: 60 max_retries: 3 models: sql_analyzer: model_id: claude-sonnet-4-20250514 system_prompt: 你是一个MySQL性能优化专家。分析执行计划时重点关注type、key、rows、Extra四列。给出优化建议时必须说明预期效果和可能的风险。 sql_rewriter: model_id: gpt-4o system_prompt: 你是一个SQL改写专家。在保持语义不变的前提下重写SQL以提升性能。输出格式先给改写后的SQL再给改写理由。 test_generator: model_id: claude-sonnet-4-20250514 system_prompt: 你是一个测试工程师。根据给定的SQL和表结构生成边界测试用例包括空值、极值、重复值场景。对应的Python调用代码import os import yaml from openai import OpenAI def load_config(pathtaotoken_config.yaml): with open(path, r) as f: config yaml.safe_load(f) config[taotoken][api_key] os.environ.get(TAOTOKEN_API_KEY) return config def call_model(config, task_name, user_content): client OpenAI( base_urlconfig[taotoken][base_url], api_keyconfig[taotoken][api_key], timeoutconfig[taotoken][timeout] ) task config[models][task_name] response client.chat.completions.create( modeltask[model_id], messages[ {role: system, content: task[system_prompt]}, {role: user, content: user_content} ] ) return response.choices[0].message.content if __name__ __main__: config load_config() explain_output id: 1 select_type: SIMPLE table: orders type: ALL possible_keys: idx_user_id key: NULL key_len: NULL ref: NULL rows: 1250000 Extra: Using where; Using filesort result call_model(config, sql_analyzer, f分析以下执行计划\n{explain_output}) print(result)这个配置的好处是模型ID、系统提示词、超时参数都集中在YAML里切换模型只需要改一行配置。团队协作时把YAML纳入版本控制每个人用自己的环境变量存Key不会泄露。3.4 索引调整前后耗时验证光看执行计划还不够最终要验证实际耗时。下面这个脚本用performance_schema或者直接计时来对比import time import pymysql def measure_query(conn, sql, iterations5): times [] with conn.cursor() as cur: for _ in range(iterations): start time.perf_counter() cur.execute(sql) cur.fetchall() elapsed time.perf_counter() - start times.append(elapsed) return { min: min(times), max: max(times), avg: sum(times) / len(times) } if __name__ __main__: conn pymysql.connect(host127.0.0.1, userroot, passwordyour_password, databaseyour_db) sql_before SELECT * FROM orders WHERE user_id 10086 AND status 2 ORDER BY create_time DESC LIMIT 20 sql_after SELECT id, amount, create_time FROM orders WHERE user_id 10086 AND status 2 ORDER BY create_time DESC LIMIT 20 print(优化前:, measure_query(conn, sql_before)) print(优化后:, measure_query(conn, sql_after)) conn.close()跑5次取平均避免单次抖动。如果优化后平均耗时下降超过30%说明调整有效如果没变化甚至变慢就要回滚并重新分析。4. 验证请求与成功结果从慢查询到索引命中配置和脚本都准备好了现在走一遍完整流程看看实际效果。假设我们有一条慢查询在慢查询日志里出现了几十次平均耗时2.3秒SELECT * FROM orders WHERE user_id 10086 AND status 2 AND create_time 2024-01-01 ORDER BY create_time DESC LIMIT 20;先看执行计划EXPLAIN SELECT * FROM orders WHERE user_id 10086 AND status 2 AND create_time 2024-01-01 ORDER BY create_time DESC LIMIT 20;输出----------------------------------------------------------------------------------------- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | ----------------------------------------------------------------------------------------- | 1 | SIMPLE | orders | ALL | idx_user_id | NULL | NULL | NULL | 1250000 | Using where | -----------------------------------------------------------------------------------------typeALLkeyNULLrows1250000全表扫描125万行。possible_keys显示有idx_user_id但没被选中。为什么因为status和create_time没有索引优化器认为走idx_user_id后还要回表过滤大量数据不如直接全表扫描。现在通过TaoToken调用模型分析这个执行计划。用第3节的配置result call_model(config, sql_analyzer, f分析以下执行计划\n{explain_output}) print(result)模型返回的建议实际输出会因模型而异这里给一个典型结果执行计划显示全表扫描possible_keys有idx_user_id但未被使用。原因查询条件包含user_id、status、create_time三个字段但现有索引只覆盖user_id。优化器估算走idx_user_id后需要回表过滤status和create_time回表行数过多成本高于全表扫描。建议创建复合索引 idx_user_status_time (user_id, status, create_time)。这样查询可以完全走索引且create_time在索引中天然有序ORDER BY create_time DESC可以直接利用索引顺序避免filesort。预期效果type从ALL变为range或refrows从125万降到几十到几百Extra中的Using filesort消失。风险复合索引会增加写入开销且索引顺序必须与查询条件匹配。如果后续有只查user_id的查询这个索引也能覆盖最左前缀原则。按照建议创建索引ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time);再次查看执行计划EXPLAIN SELECT * FROM orders WHERE user_id 10086 AND status 2 AND create_time 2024-01-01 ORDER BY create_time DESC LIMIT 20;输出-------------------------------------------------------------------------------------------------------------------------------------- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | -------------------------------------------------------------------------------------------------------------------------------------- | 1 | SIMPLE | orders | range | idx_user_id,idx_user_status_time | idx_user_status_time | 16 | NULL | 45 | Using where; Using index | --------------------------------------------------------------------------------------------------------------------------------------typerangekeyidx_user_status_timerows45Extra里Using index表示覆盖索引不需要回表。从125万行降到45行提升非常明显。再用EXPLAIN ANALYZE确认实际耗时EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id 10086 AND status 2 AND create_time 2024-01-01 ORDER BY create_time DESC LIMIT 20;输出里会看到actual time0.12..0.15 rows20实际耗时0.15毫秒左右。对比优化前的2.3秒提升超过一万倍。最后用第3.4节的计时脚本做回归验证print(优化前:, measure_query(conn, sql_before)) print(优化后:, measure_query(conn, sql_after))输出优化前: {min: 2.28, max: 2.41, avg: 2.33} 优化后: {min: 0.0001, max: 0.0003, avg: 0.0002}平均耗时从2.33秒降到0.2毫秒。这个结果可以记录到优化文档里作为后续类似问题的参考。注意Using index表示覆盖索引但前提是查询的列都在索引中。如果查询是SELECT *即使走了索引也需要回表Extra里会显示Using index condition而不是Using index。所以“只检索需要的列”这条老规矩依然有效。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth在配置TaoToken和调用模型的过程中有几个报错非常典型。这一节逐个排查。5.1 401 Unauthorized这是最常见的错误意思是API Key无效或未提供。排查步骤第一确认Key是否正确复制。TaoToken的Key通常以sk-开头复制时不要带空格或换行。我见过有人从网页复制时多选了一个空格排查了半天。第二确认Key是否已激活。在API Keys页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite检查Key的状态如果是禁用状态需要启用。第三确认环境变量是否生效。在Python里打印os.environ.get(TAOTOKEN_API_KEY)看是否为空。如果为空检查.env文件是否加载或者shell里是否export了。第四确认base_url是否正确。必须是https://taotoken.net/api不要加UTM参数不要加/v1后缀除非文档明确说明。有些OpenAI兼容网关需要/v1但TaoToken的文档写的是/api以文档为准。import os from openai import OpenAI # 错误示例base_url多了/v1 # client OpenAI(base_urlhttps://taotoken.net/api/v1, api_key...) # 正确示例 client OpenAI( base_urlhttps://taotoken.net/api, api_keyos.environ[TAOTOKEN_API_KEY] )5.2 local proxy failed这个报错通常出现在你本地设置了HTTP代理但代理不可用或配置错误。注意这里说的代理是开发环境常见的网络代理配置不是让你去用什么特殊工具。很多公司内网需要走代理才能访问外网如果代理挂了或者环境变量没设对就会报这个错。排查# 检查当前代理环境变量 echo $HTTP_PROXY echo $HTTPS_PROXY echo $NO_PROXY # 如果不需要代理清空 unset HTTP_PROXY unset HTTPS_PROXY如果你确实需要代理才能访问外网确保HTTPS_PROXY指向正确的地址和端口。另外NO_PROXY里可以加上taotoken.net让TaoToken的请求不走代理避免代理层干扰。在Python代码里也可以显式控制import os os.environ[NO_PROXY] taotoken.net,localhost,127.0.0.15.3 reading choices 报错这个报错通常是响应体解析失败常见原因有两个一是模型返回了非JSON格式的内容。比如你调用的模型ID不存在网关返回了一个HTML错误页SDK尝试解析JSON就失败了。解决方法是先用curl直接请求看原始响应curl -X POST https://taotoken.net/api/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model:claude-sonnet-4-20250514,messages:[{role:user,content:test}]}如果返回的是HTML说明请求根本没到模型层可能是路径错了或者Key无效。二是流式响应处理不当。如果你用了streamTrue但代码里按非流式解析就会报reading choices相关错误。流式响应的每个chunk里choices[0].delta才有内容不是choices[0].message。# 流式调用正确写法 stream client.chat.completions.create( modelclaude-sonnet-4-20250514, messages[{role: user, content: test}], streamTrue ) for chunk in stream: if chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end)5.4 OAuth 相关报错如果你用的是Claude Code或者某些IDE插件可能会遇到OAuth认证失败。这类工具通常有自己的认证流程不是直接用API Key。排查第一确认你用的是API Key模式还是OAuth模式。TaoToken的接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里区分了这两种方式。如果用API Key就不需要走OAuth。第二如果工具强制要求OAuth检查回调地址是否配置正确。有些工具需要你在本地起一个回调服务端口被占用就会失败。第三Claude Code的配置可以参考专门的接入说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite里面给了settings.json的完整示例。关键三件套是Base URL填https://taotoken.net/apiKey填你的TaoToken KeyModel ID填文档里列出的模型ID。三者缺一不可。如果你用的是Cline或者类似的VS Code插件配置MCP时也要注意Base URL和Key的对应关系。Cline的MCP配置里baseUrl和apiKey要同时填对Model ID也要和TaoToken支持的列表一致。5.5 模型返回的SQL建议不可用这不是报错但比报错更常见。模型给出的优化建议可能不适用于你的数据库版本或数据分布。比如模型建议用EXISTS替代IN但在你的场景下IN的子查询结果集很小EXISTS反而更慢。应对方法把模型的建议当作“假设”用EXPLAIN ANALYZE验证。如果验证不通过把实际执行计划反馈给模型让它重新分析。TaoToken支持多模型你可以用不同模型交叉验证同一个问题取共识度高的建议。# 多模型交叉验证 models [claude-sonnet-4-20250514, gpt-4o] for m in models: result call_model_with_model(config, m, sql_analyzer, explain_output) print(f {m} \n{result}\n)6. 语义一致CTA把SQL优化流程固化下来SQL优化不是一次性的活而是一个持续的过程。今天优化了一条慢查询明天可能又冒出新的。把上面这套流程固化下来才能持续受益。我自己的做法是在项目里建一个sql_optimization目录放三样东西。第一是慢查询采集脚本定期从生产库拉取慢查询日志并聚合。第二是EXPLAIN对比脚本每次SQL变更时自动跑一遍输出优化前后的执行计划差异。第三是TaoToken调用配置把模型分析集成到CI流程里对新增的慢查询自动生成优化建议。具体来说可以在GitLab CI或者GitHub Actions里加一个job当sql/目录下的文件有变更时自动执行# .gitlab-ci.yml 片段 sql_optimization: stage: test script: - python scripts/collect_slow_log.py --since 1 hour ago - python scripts/compare_explain.py --before sql/old.sql --after sql/new.sql - python scripts/model_analyze.py --explain-output explain.json only: changes: - sql/**/*这样每次SQL变更都会经过慢查询采集、执行计划对比、模型分析三道关卡问题在合并前就能发现。对于长期做SQL优化和Agent开发的团队如果模型调用频率很高可以考虑Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite按周期计费比按量付费更划算。如果只是偶尔用模型分析执行计划直接用API Keys按量调用就行。最后说一个我踩过的坑不要把所有慢查询都交给模型分析。有些慢查询是因为数据量本身大比如报表类的全表聚合这种加索引也没用需要从业务层面做预聚合或者物化视图。模型可能会建议你加索引但加了之后写入性能下降得不偿失。所以模型建议一定要结合业务场景判断不能盲从。另外TaoToken的模型对话功能https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite适合快速验证单个SQL的优化思路不用写代码直接粘贴执行计划和SQL就能得到分析。对于日常排查这个入口比写脚本更快。而接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里有完整的API参数说明和模型列表配置前先过一遍能少走很多弯路。把慢查询日志、执行计划、模型分析这三样串起来SQL优化就从“凭感觉”变成了“有证据、有验证、有回归”的工程化流程。索引不是越多越好SQL不是越短越好一切以实际执行计划和耗时为准。
返回列表