我有一个在Qt对象系统之上构建的手写ORM.我正在用SQLite后端测试它,我看到了奇怪的性能问题.数据库中存储了大约10k个对象.使用单独的查询逐个加载对象.
其中一个查询显示执行时间的变化:从1毫秒到10,取决于主键ID.这次还包括Qt Sql模块完成的一些操作.
查询非常简单,看起来像这样(查询之间的id = 100不同):
SELECT * FROM t1, t2 WHERE t1.id = 100 AND t2.id = 100
什么可能导致相同的查询执行10次更糟,具体取决于行ID?
最佳答案 考虑到您是以毫秒为单位的计时操作,您观察到的行为非常有意义.使用这种时间粒度对单个查询运行进行基准测试通常没有意义,除非您只对延迟而不是吞吐量感兴趣.
例如,根据您的特定查询,您会看到显着差异,具体取决于t1中是否存在mathing行,因为这将决定SQLite是否应该费心去查看t2.
即使运行完全相同的查询也会产生不同的结果,具体取决于OS文件系统缓存,进程调度程序,SQLite缓存,硬盘板和磁头的位置以及各种其他因素.
两个更具体,有两种可能性:
A. t1.id和t2.id被编入索引
这是最可能的情况 – 我希望将一个名为id的表列编入索引.
大多数SQL引擎(包括SQLite)对每个索引使用B-tree的一些变体.在SQLite上,每个树节点都是DB文件中的单个页面.根据您的特定查询,SQLite必须通过:
> t1.id索引的某些页面
> t2.id索引的某些页面
>包含两个表中匹配行的DB页面.
根据您的硬件以及页面在物理介质(例如硬盘驱动器)上的位置,加载页面可以轻松添加几毫秒的延迟.这在大型或新加载的数据库中尤其明显,其中页面既不在OS文件系统高速缓存中也不在SQLite3高速缓存中.
此外,除非您的数据库非常小,否则它通常不适合SQLite3缓存,并且单独的缓存命中和未命中可以解决单个查询需要完成的相当严重的变化:SQLite缓存未命中强制读取文件系统,它很容易导致操作系统重新安排数据库进程,转而支持另一个进程.
B. t1.id和t2.id未编入索引
这可能更容易可视化:没有索引,SQLite必须扫描整个表.假设您的SELECT语句中有一个限制(在您的示例中没有一个限制),是否立即找到匹配的条目或者在完成整个表之后是否运气,因此查询完成时间的严重变化.