我有一个基于 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 倍?
最佳答案
考虑到您正在以毫秒为单位计时操作,您观察到的行为非常有意义。以这种时间粒度运行单个查询的基准测试通常没有意义,除非您只对延迟感兴趣,而不是吞吐量。
例如,对于您的特定查询,您会看到显着差异,具体取决于 t1
中是否存在数学行。 , 因为这将决定 SQLite 是否应该费心查看 t2
一点也不。
即使运行完全相同的查询也会产生不同的结果,具体取决于操作系统文件系统缓存、进程调度程序、SQLite 缓存、硬盘板和磁头的位置以及各种其他因素。
两个更具体,有两种可能:
答:t1.id
和 t2.id
被索引
这是最可能的情况——我希望有一个表列恰本地命名为 id
被索引。
大多数 SQL 引擎,包括 SQLite,都使用 B-tree 的一些变体。对于每个索引。在 SQLite 上,每个树节点都是 DB 文件中的一个页面。对于您的特定查询,SQLite 必须经过:
t1.id
的一些页面索引 t2.id
的一些页面索引 根据您的硬件以及页面在物理介质(例如硬盘)上的位置,加载页面很容易增加几毫秒的延迟。这在页面既不在 OS 文件系统缓存中也不在 SQLite3 缓存中的大型或新加载的数据库上尤其明显。
此外,除非您的数据库非常小,否则它通常不适合 SQLite3 缓存,并且单独的缓存命中和未命中可能会导致单个查询需要完成的时间相当严重:SQLite 缓存未命中会强制读取文件系统,这很容易导致操作系统重新调度数据库进程以支持另一个进程。
B.
t1.id
和 t2.id
未编入索引这可能更容易可视化:没有索引,SQLite 必须扫描整个表。假设您的
SELECT
中有一个限制。语句(您的示例中没有),是否会立即或在遍历整个表后找到匹配条目取决于运气,因此查询完成时间的严重变化。
关于performance - SQLite 性能不一致,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5570686/