慢 SQL 加索引能快多少?Python 标准库 sqlite3 实测全流程

发布时间:2026/10/29 20:13:55

慢 SQL 加索引能快多少?Python 标准库 sqlite3 实测全流程 一条按user_id查订单的 SQL在 50 万行上没建索引时全表扫描要 9.5 毫秒建完索引降到 0.22 毫秒。单看这个数字不大但它随数据量线性放大表涨到五千万行差距会是几千倍。本文用 Python 标准库sqlite3在本地把这套对比完整跑一遍所有数字都是本机实测不靠记忆。环境准备不需要装任何第三方包Python 3.13 自带sqlite3模块即可。先确认版本python -c import sqlite3, sys; print(sys.version.split()[0]); print(sqlite, sqlite3.sqlite_version)本机实测输出3.13.12与sqlite 3.46.0具体版本取决于你的 Python 构建不影响结论。完整基准测试脚本下面这段脚本造 50 万行订单数据分别测「无索引点查 / 有索引点查」「对列套函数 / 范围写法」「覆盖索引 / 回表」三组对比。直接复制保存为benchmark.py运行即可#!/usr/bin/env python3 # -*- coding: utf-8 -*- 本地实测慢查询 索引的差距标准库 sqlite3零三方依赖。 import sqlite3 import time N 500_000 # 50 万行 con sqlite3.connect(:memory:) cur con.cursor() cur.execute( CREATE TABLE orders ( order_id INTEGER PRIMARY KEY, user_id INTEGER, order_date TEXT, amount REAL, status TEXT) ) rows [ ( i, (i % 2000) 1, # 2000 个用户每人约 250 单 f2026-{1 (i % 12):02d}-{(i % 28) 1:02d}, float(i % 1000), paid if i % 4 else cancelled, ) for i in range(1, N 1) ] cur.executemany(INSERT INTO orders VALUES (?,?,?,?,?), rows) con.commit() print(f[数据] 已写入 {N} 行 orders) def best_time(sql, params(), repeat20): for _ in range(3): # 预热排除首次编译/缓存噪声 cur.execute(sql, params).fetchall() best float(inf) for _ in range(repeat): t0 time.perf_counter() cur.execute(sql, params).fetchall() best min(best, time.perf_counter() - t0) return best * 1000.0 def plan(sql, params()): cur.execute(EXPLAIN QUERY PLAN sql, params) return | .join(r[3] for r in cur.fetchall()) print(\n 1) 点查无索引 vs 有索引 ) q SELECT * FROM orders WHERE user_id ? print(无索引 plan:, plan(q, (1234,))) t_no best_time(q, (1234,)) print(f无索引 耗时: {t_no:.3f} ms) cur.execute(CREATE INDEX idx_user ON orders(user_id)) print(有索引 plan:, plan(q, (1234,))) t_yes best_time(q, (1234,)) print(f有索引 耗时: {t_yes:.3f} ms) print(f- 加速比: {t_no / t_yes:.1f}x) print(\n 2) 对列套函数索引直接失效 ) qf SELECT COUNT(*) FROM orders WHERE strftime(%Y-%m, order_date) ? qr SELECT COUNT(*) FROM orders WHERE order_date ? AND order_date ? cur.execute(CREATE INDEX idx_date ON orders(order_date)) print(函数写法 plan:, plan(qf, (2026-03,))) tf best_time(qf, (2026-03,), repeat10) print(f函数写法 耗时: {tf:.3f} ms) print(范围写法 plan:, plan(qr, (2026-03-01, 2026-04-01))) tr best_time(qr, (2026-03-01, 2026-04-01), repeat10) print(f范围写法 耗时: {tr:.3f} ms) print(f- 函数 vs 范围 加速比: {tf / tr:.1f}x) print(\n 3) 覆盖索引少回表 ) cur.execute(CREATE INDEX idx_cover ON orders(user_id, status)) qc SELECT status FROM orders WHERE user_id ? qa SELECT * FROM orders WHERE user_id ? print(只取列 plan:, plan(qc, (1234,))) print(全取 plan:, plan(qa, (1234,))) tc best_time(qc, (1234,)) ta best_time(qa, (1234,)) print(f覆盖索引(只取列) 耗时: {tc:.4f} ms) print(f回表(SELECT *) 耗时: {ta:.4f} ms) print(f- 覆盖索引加速比: {ta / tc:.1f}x)本机实测输出取 20 次最优值:memory:存储 1) 点查无索引 vs 有索引 无索引 plan: SCAN orders 无索引 耗时: 9.528 ms 有索引 plan: SEARCH orders USING INDEX idx_user (user_id?) 有索引 耗时: 0.222 ms - 加速比: 42.9x 2) 对列套函数索引直接失效 函数写法 plan: SCAN orders USING COVERING INDEX idx_date 函数写法 耗时: 87.147 ms 范围写法 plan: SEARCH orders USING COVERING INDEX idx_date (order_date? AND order_date?) 范围写法 耗时: 0.766 ms - 函数 vs 范围 加速比: 113.8x 3) 覆盖索引少回表 只取列 plan: SEARCH orders USING COVERING INDEX idx_cover (user_id?) 全取 plan: SEARCH orders USING INDEX idx_user (user_id?) 覆盖索引(只取列) 耗时: 0.0676 ms 回表(SELECT *) 耗时: 0.2170 ms - 覆盖索引加速比: 3.2x结果一点查从全表扫描到索引查找没索引时规划器只能SCAN orders从第 1 行扫到第 50 万行逐条比对user_id建了idx_user后计划变成SEARCH orders USING INDEX idx_user (user_id?)通过 B 树直接定位。本机实测 9.528ms 对 0.222ms约42.9 倍。差距的本质是复杂度全表扫描是 O(N)索引查找是 O(log N)。所以数据量翻 100 倍全表扫描耗时大致翻 100 倍索引查找只多几层树高。这正是「大表上一句慢 SQL 能把连接池打满」的底层原因——它不是慢一点是随表膨胀线性变慢。结果二对列套函数索引直接失效第二组最值得记。给order_date建了索引却写出WHERE strftime(%Y-%m, order_date) 2026-03。规划器返回SCAN orders USING COVERING INDEX idx_date——注意它仍然是个 SCAN只是扫的是索引而不是整表并没有 SEEK 定位。原因是strftime(order_date, ...)这个表达式对每一行都要现算规划器没法用它去索引里「跳」。改写成范围写法order_date 2026-03-01 AND order_date 2026-04-01列本身干干净净出现在比较符号左边计划立刻变成SEARCH ... USING COVERING INDEX本机实测 87.147ms 降到 0.766ms约113.8 倍。MySQL 里一模一样WHERE DATE(create_time)2026-03-15会让create_time索引失效必须改成 AND 。结果三覆盖索引少回表SELECT *时规划器虽然走了idx_user但索引里只有user_id要的其他列得拿rowid回主表再捞一次这趟叫回表。改成SELECT status并建联合索引(user_id, status)计划里多了COVERING关键字——要查的status和定位用的user_id都在索引里不用回表。本机实测 0.2170ms 对 0.0676ms约3.2 倍。列表页、统计接口这类只读少量列的场景覆盖索引收益最直接。怎么判断你的 SQL 走没走索引不用猜一条命令看执行计划EXPLAIN QUERY PLAN SELECT * FROM orders WHERE user_id 1234; -- 健康: SEARCH orders USING INDEX idx_user (user_id?) -- 异常: SCAN ordersSCAN是全扫SEARCH ... USING INDEX才是走索引定位。MySQL 看EXPLAIN的type列ALL等价于 SCANref/range才是用上索引Extra里出现Using filesort或Using temporary也要警惕。三个会咬人的坑深翻页LIMIT 100000, 20偏移量越大越慢数据库得先数出前 10 万行再扔掉。改成游标分页记住上一页最大id下页用WHERE id ? LIMIT 20索引直接定位不数废行。隐式类型转换user_id是整型接口传进来个字符串123数据库会在比较时做类型转换等于对列套了函数索引当场失效退化成全表扫描。接口层务必保证入参类型和字段类型一致。索引不是越多越好每插一行、改一行所有相关索引都得跟着更新。写多读少的流水表如日志加一堆索引写入直接被拖死。只给高频查询条件建定期清理用不上的冗余索引。小结加索引不是魔法它把 O(N) 的逐行扫描换成 O(log N) 的定位。上线前先跑一条EXPLAIN QUERY PLAN看到SCAN就排查——这比上线后被慢查询拖垮连接池便宜得多。
延伸阅读

更多相关文章

2026/10/29 20:13:55

MySQL中INT与VARCHAR长度参数的本质区别

1. 数据类型长度问题的本质 在MySQL中, INT 和 VARCHAR 的"长度"概念经常让开发者感到困惑。很多人会想当然地认为这两个长度参数是相同的含义,但实际上它们代表了完全不同的概念。这种误解可能导致数据库设计上的严重错误。 我第一次遇到…

2026/10/29 20:13:55

Node.js构建高性能篮球实时资讯平台实战

1. 项目背景与核心需求去年夏天,我在维护一个体育社区时发现一个有趣的现象:超过60%的用户访问集中在NBA季后赛期间,但市面上大多数篮球资讯平台都存在两个痛点——实时数据更新延迟和互动体验单一。这促使我萌生了用Node.js技术栈构建一个轻…

2026/10/29 20:13:55

9款AI学术工具实测对比:无编程基础学生首选

1. 项目概述作为一名长期关注AI工具应用的科技博主,我最近花了整整两周时间深度测试了市面上9款号称能"降低AI使用门槛"的热门工具。这些工具主要面向没有编程基础的本科生群体,承诺能让学术研究、论文写作、数据分析等工作变得更高效。测试过…

2026/10/29 21:14:36

智慧农业生菜生长状态检测数据集解析与应用

1. 项目概述:智慧农业生菜生长状态检测数据集 这个数据集是专门为智慧农业场景下生菜生长状态检测任务设计的标准化数据资源包。它包含了4406张高质量的生菜生长过程图像,采用VOC和YOLO两种主流标注格式,覆盖了生菜生长周期的3个关键状态类别…

2026/10/29 21:14:36

ASP图书馆管理代码:借还书事务、并发与权限校验实战

简介:这份ASP图书馆管理代码面向Web开发初学者、课程设计或小型图书馆信息化场景,基于ASP与Access构建一套可运行的图书查询、借阅、归还及后台管理示例,帮助读者理解动态网页与关系型数据库的协作方式,适合课程设计、毕业设计参考…

2026/10/29 21:14:36

Java遥感识别源码解析:地物分类与高标准农田监管实战

简介:基于Java语言实现的高标准农田监管平台遥感监测源码,面向农业IT开发者、遥感算法工程师及智慧农业研究者,解决传统农田监管效率低、地物分类依赖人工等问题。项目采用Maven标准结构,压缩包共26个文件、约28.23MB,…

2026/10/29 21:14:36

eMMC 5.1物理层深度解析:HS400、Boot Partition与EXT_CSD工程实践

简介:本资源为JEDEC官方发布的eMMC 5.1嵌入式存储标准核心文档JESD84-B51(2015年2月修订版),面向嵌入式系统工程师、存储驱动开发人员及硬件设计从业者,解决eMMC接口协议理解、高速模式实现、低功耗管理与ECC/安全机制…

2026/10/29 21:14:36

MAI-Image-2图像生成模型技术解析与应用实践

1. MAI-Image-2技术解析:照片级写实背后的黑科技微软研究院最新发布的MAI-Image-2图像生成模型,在写实类图像生成领域实现了质的飞跃。作为长期从事计算机视觉研究的从业者,我第一时间对官方技术白皮书进行了深度解读。这款模型的核心突破在于…

2026/10/29 21:09:35

ponytail插件:浏览器标签页临时聚合与效率管理指南

1. 从“ponytail”这个标题说起:它到底是什么第一次看到“ponytail”这个词,很多人脑子里浮现的是发型——马尾辫。但在技术圈和效率工具圈里,ponytail 已经悄悄变成了一个代称,指向的是一类“把零散信息扎成一束”的工具思路。它…

2026/10/29 17:05:52

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/29 17:06:02

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/29 17:06:13

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/29 0:03:42

学生选课系统DFD图绘制指南:边界划分与分层校验实战

简介:文档以顶层与第一层DFD图为核心,完整刻画学生选课系统的逻辑模型,面向软件工程课程设计、系统分析学习及毕设前期建模人群。顶层图界定系统边界,外部实体包括管理员、教师和学生三类角色;第一层图进一步细化用户登…

2026/10/29 0:03:42

JIRA敏捷落地:Scrum角色权限、Sprint与燃尽图避坑指南

简介:围绕“基于JIRA的敏捷开发项目管理”这一主题,面向产品负责人、Scrum Master及研发测试成员,梳理Scrum角色分工、迭代流程与JIRA工具配置的对应关系,适合初、中级团队导入敏捷管理时参考。压缩包共1个docx文件,大…

2026/10/29 0:03:42

SEMI S8人机工程验收实战:从条款拆解到现场实测的完整指南

简介:面向半导体设备设计、制造与运维工程师及安全合规人员,这份资料聚焦SEMI标准体系下的设备安全规范,重点收录SEMI S8-0308《半导体制造设备人体工程学工程安全指南》。内容围绕设备设计原则、错误与事故预防、人体工程学适配三大主线展开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑