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