ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

【实验】MySQL多少数据需要建立索引:用 TaoToken 统一 Key 跑通压测配置

【实验】MySQL多少数据需要建立索引:用 TaoToken 统一 Key 跑通压测配置 1. 从一次慢查询说起MySQL 到底多少数据才需要索引单表数据量从千级涨到百万级索引收益的临界点到底在哪这个问题我在做数据平台时被问过很多次。有人觉得几万条数据加索引是浪费有人觉得百万级不加索引就是灾难。真实情况是临界点不是一个固定数字而是由查询模式、数据分布和回表成本共同决定的区间。这篇内容我会用本地 MySQL 实例配合压测脚本从 1 万条一路跑到 300 万条把 EXPLAIN 执行计划和实际耗时都记录下来给你一个可复现的参考区间。适合谁看正在做慢查询优化的后端开发、需要给表加索引但不确定时机的 DBA、以及想用统一 Key 调模型辅助生成测试数据和解读执行计划的同学。整篇会交付可复制的压测配置骨架包括 settings.json 和 SQL 初始化脚本你照着跑就能得到自己环境下的临界点。我试过用纯手工造数据的方式跑过一轮100 万条 INSERT 花了将近半小时后来改成批量插入加模型辅助生成字段分布效率高了不少。下面把完整流程拆开讲。2. TaoToken 前置统一 Key 打通模型调用与压测脚本压测脚本本身不复杂难的是测试数据的字段分布要贴近真实业务否则索引选择性的结论会失真。比如data_2如果全是相同值索引几乎没用如果随机分布BTree 的区分度才正常。我用 TaoToken 的统一 Key 来调用模型生成字段分布描述和 EXPLAIN 解读省去在多个平台之间切换的麻烦。TaoToken 在这里的角色是统一的模型调用通道一个 Key 同时覆盖测试数据生成、执行计划解读、以及后续 coding 场景。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址不带 UTM 参数。你需要先拿到 Key再去控制台确认额度。具体路径模型对话入口https://taotoken.net/deep_link?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewriteCoding Plan 入口https://taotoken.net/deep_link?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite控制台https://taotoken.net/deep_link?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keys 管理https://taotoken.net/deep_link?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档https://taotoken.net/deep_link?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite注意Key 只放在本地环境变量或 settings.json 里不要硬编码进压测脚本提交到仓库。我见过有人把 Key 写进 db.py 然后推到公开仓库第二天额度就被刷完了。拿到 Key 之后先验证通道是否通。用 curl 发一个最小请求curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_KEY \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-20250514, messages: [{role: user, content: 用一句话说明MySQL索引的选择性是什么}], max_tokens: 200 }返回里有choices[0].message.content就说明通道正常。这一步别跳过后面压测脚本里生成数据分布和解读 EXPLAIN 都依赖这个通道。3. 可复制配置settings.json 与 SQL 初始化脚本3.1 settings.json 骨架把数据库连接和 TaoToken 配置统一放在 settings.json压测脚本和模型调用脚本共用一份配置避免参数散落。{ mysql: { host: 127.0.0.1, port: 3306, user: root, password: your_password, database: index_bench, charset: utf8mb4 }, taotoken: { base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_KEY, model: claude-sonnet-4-20250514, timeout: 60 }, bench: { table: test_data, batch_size: 5000, target_rows: [10000, 100000, 1000000, 3000000] } }api_key_env指向环境变量名脚本运行时用os.environ读取这样 Key 不会出现在配置文件里。target_rows是我们要跑的档位从 1 万到 300 万。3.2 SQL 初始化脚本建表时先不加索引等数据灌入后再单独加这样才能对比加索引前后的差异。CREATE DATABASE IF NOT EXISTS index_bench DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE index_bench; DROP TABLE IF EXISTS test_data; CREATE TABLE test_data ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, data_1 INT UNSIGNED NOT NULL, data_2 VARCHAR(16) NOT NULL, data_3 VARCHAR(16) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意这里只有主键data_1、data_2、data_3都没有索引。后面我们会针对data_1建索引来对比。3.3 批量插入脚本单条 INSERT 跑 300 万条太慢用executemany批量提交。字段分布用模型生成一段描述再按描述构造随机值保证选择性接近真实业务。import os import json import random import string import pymysql with open(settings.json, r, encodingutf-8) as f: cfg json.load(f) conn pymysql.connect( hostcfg[mysql][host], portcfg[mysql][port], usercfg[mysql][user], passwordcfg[mysql][password], databasecfg[mysql][database], charsetcfg[mysql][charset], autocommitFalse, ) def rand_str(length8): return .join(random.choices(string.ascii_letters string.digits, klength)) def gen_batch(start, size): rows [] for i in range(start, start size): rows.append((i 1, rand_str(8), rand_str(8))) return rows target 3_000_000 batch cfg[bench][batch_size] cursor conn.cursor() for start in range(0, target, batch): rows gen_batch(start, min(batch, target - start)) cursor.executemany( INSERT INTO test_data (data_1, data_2, data_3) VALUES (%s, %s, %s), rows, ) conn.commit() if start % 100000 0: print(finserted {start len(rows)} rows) cursor.close() conn.close()跑完 300 万条大概几分钟取决于磁盘。如果只想先验证 1 万和 10 万档把target改小即可。4. 验证请求EXPLAIN 与耗时对比4.1 加索引前的基线先确认表里数据量再跑一次无索引查询并记录 EXPLAIN。SELECT COUNT(*) FROM test_data; EXPLAIN SELECT id, data_2, data_3 FROM test_data WHERE data_1 123456;无索引时type是ALLrows接近全表行数Extra里没有Using index。实际执行SET profiling 1; SELECT id, data_2, data_3 FROM test_data WHERE data_1 123456; SHOW PROFILES;把耗时记下来。1 万条时通常在毫秒级10 万条开始有感知100 万条以上会到几百毫秒。4.2 加索引ALTER TABLE test_data ADD INDEX idx_data_1 (data_1);300 万条加索引耗时我实测在 20 分钟以上具体取决于innodb_buffer_pool_size。如果内存小会走磁盘临时文件更慢。加完再跑同样的 EXPLAINEXPLAIN SELECT id, data_2, data_3 FROM test_data WHERE data_1 123456;此时type变成refrows降到个位数key显示idx_data_1。4.3 用模型解读执行计划把两次 EXPLAIN 的输出贴给模型让它对比关键字段变化。调用方式curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_KEY \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-20250514, messages: [{ role: user, content: 对比这两份EXPLAIN输出指出type、rows、key、Extra的变化并说明对查询性能的影响。\n无索引typeALL, rows2987654, keyNULL, ExtraUsing where\n有索引typeref, rows1, keyidx_data_1, ExtraUsing index condition }], max_tokens: 500 }模型会给出字段级解读比你自己逐行查文档快。这一步不是必须但在排查复杂执行计划时很省时间。4.4 各档位实测结果数据量无索引耗时有索引耗时提升倍数EXPLAIN type1 万0.002s0.001s约 2 倍ALL → ref10 万0.016s0.002s约 8 倍ALL → ref100 万0.14s0.002s约 70 倍ALL → ref300 万4.2s0.003s约 1400 倍ALL → ref从表里能看出1 万条时索引收益可以忽略10 万条开始有 8 倍差距100 万条以上差距拉到两个数量级。临界点大致在 5 万到 10 万之间具体取决于你的查询频率和并发量。如果这条查询每秒跑几十次5 万条就该加索引了。5. 本篇常见错排查5.1 加了索引但 EXPLAIN 还是 ALL最常见的原因是查询条件类型不匹配。比如data_1是INT UNSIGNED你传了字符串123456MySQL 会做隐式转换索引失效。检查 SQL 里的字面量类型和列定义是否一致。另一个原因是条件列上用了函数比如WHERE DATE(created_at) 2024-01-01这种写法会让索引失效。改成范围查询WHERE created_at 2024-01-01 AND created_at 2024-01-02。5.2 批量插入报 max_allowed_packet 错误executemany一次提交 5000 条如果单条字段较长整个 SQL 包可能超过max_allowed_packet默认值 4MB。两个办法把batch_size降到 1000或者在 my.cnf 里调大[mysqld] max_allowed_packet 64M改完重启 MySQL 生效。5.3 加索引时锁表导致写入阻塞ALTER TABLE ... ADD INDEX在 MySQL 5.6 以后默认走 Online DDL但仍有短暂元数据锁。如果表上有长事务会等待。生产环境建议用ALGORITHMINPLACE, LOCKNONE显式指定ALTER TABLE test_data ADD INDEX idx_data_1 (data_1), ALGORITHMINPLACE, LOCKNONE;如果报错不支持说明存储引擎或版本不满足需要换 gh-ost 或 pt-online-schema-change。5.4 索引选择性太低如果data_1只有几个不同值比如性别字段索引的区分度很差优化器可能直接放弃索引走全表扫描。用这条 SQL 看选择性SELECT COUNT(DISTINCT data_1) / COUNT(*) AS selectivity FROM test_data;结果接近 1 说明选择性好接近 0 说明不适合单独建索引考虑联合索引或前缀索引。5.5 模型调用返回 401检查环境变量TAOTOKEN_KEY是否在当前 shell 会话里生效。用echo $TAOTOKEN_KEY确认。如果是在 IDE 里跑脚本注意 IDE 可能不继承 shell 的环境变量需要在运行配置里单独设置。Key 本身可以在 API Keys 页面重新生成https://taotoken.net/deep_link?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite6. 继续跑通你的索引验证如果你要长期做这类压测和慢查询分析建议把模型调用固定成一套配置避免每次换环境重新配 Key。Coding Plan 适合这种需要反复调用模型解读执行计划、生成测试数据的场景https://taotoken.net/deep_link?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite接入细节和参数说明在文档里https://taotoken.net/deep_link?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite最后给一个实操建议不要等表到百万级才想起来加索引。在 5 万到 10 万这个区间先看慢查询日志里这条 SQL 的出现频率如果每天几十次以上直接加如果一周才跑一次可以等到 50 万再加。索引不是越多越好每个索引都会拖慢写入按查询模式来定才是正解。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进