一、执行摘要
每个网页 200 个 SQL 语句对于客户端/服务器数据库引擎(如 MySQL、PostgreSQL 或 SQL Server)来说过多了。
但是对于 SQLite,每个网页 200 条或更多的 SQL 语句不是问题。
SQLite 还可以高效地执行大型和复杂的查询,就像客户端/服务器数据库一样。但 SQLite 也可以高效地执行许多较小的查询。应用程序开发人员可以使用最适合手头任务的技术。
2.感知到的问题
SQLite 页面的适当用途表示,SQLite 网站上的动态页面通常每个执行大约 200 个 SQL 语句。这引起了读者的批评。例子:
“200 个 SQL 语句对于单个页面来说是一个高得离谱的数字”
“对于大多数网站来说,200 次查询太多太多了。”
“[这是]糟糕的设计”
对于传统的客户端/服务器数据库引擎,例如 MySQL、PostgreSQL 或 SQL Server,这样的批评是有根据的。在客户端/服务器数据库中,每个 SQL 语句都需要从应用程序到数据库服务器再返回到应用程序的消息往返。按顺序执行 200 多个往返消息可能会严重拖累性能。这有时称为“N+1 查询问题”或“N+1 选择问题”,它是一种反模式。
3. N+1 查询不是 SQLite 的问题
然而, SQLite不是客户端/服务器。SQLite 数据库与应用程序运行在相同的进程地址空间中。查询不涉及消息往返,仅涉及函数调用。SQLite 中单个 SQL 查询的延迟要小得多。因此,对 SQLite 使用大量查询不是问题。
4.每个网页需要超过 200 条 SQL 语句
SQLite 网站上的动态网页大多是由Fossil 版本控制系统生成的。典型的动态页面是时间线,例如https://www.sqlite.org/src/timeline。时间线使用的所有 SQL 的日志如下所示。
日志中的第一组查询是从 Fossil 数据库的“config”和“global_config”表中提取显示选项。然后是一个复杂的查询,它提取要在时间线上显示的所有元素的列表。这个“时间线”查询表明,SQLite 可以轻松处理涉及多表、子查询和复杂 WHERE 子句约束的复杂关系数据库查询,并且可以有效地利用索引以最少的磁盘 I/O 解决查询。
在单个大“时间线”查询之后,还有针对每个时间线元素的其他查询。Fossil 使用的是“N+1 查询”模式,而不是试图在尽可能少的查询中获取所有信息。但这没关系,因为没有不必要的 IPC 开销。在每个时间线页面的底部,Fossil 会显示生成该页面大约花费的时间。对于 50 个条目的时间线,延迟通常小于 25 毫秒。分析表明,这些毫秒数中很少有花费在数据库引擎内部的。
在 Fossil 中使用 N+1 查询模式不会损害应用程序。但是 N+1 查询模式确实有好处。首先,创建时间线查询的代码部分可以与准备每个时间线条目以供显示的部分完全分开。这提供了职责分离,有助于保持代码简单且易于维护。其次,显示所需的信息以及提取该信息所需的查询根据要显示的对象类型而有所不同。签到需要一组查询。票证需要另一组查询。Wiki 页面需要不同的查询。等等。通过按需在处理各种实体的代码部分实现这些查询,可以进一步分离责任并简化整个代码库。
因此,SQLite 能够执行一两个大型和复杂的查询,或者它可以执行许多更小和更简单的查询。两者都很有效。应用程序可以使用其中一种或两种技术,具体取决于最适合手头情况的技术。
以下是用于生成一个特定时间线的所有 SQL 的日志(捕获于 2016-09-16):
-- sqlite3_open: /home/drh/sqlite/sqlite/.fslckout PRAGMA foreign_keys=OFF; SELECT sql FROM localdb.sqlite_schema WHERE name=='vfile'; -- sqlite3_open: /home/drh/.fossil PRAGMA foreign_keys=OFF; SELECT value FROM vvar WHERE name='repository'; ATTACH DATABASE '/home/drh/www/repos/sqlite.fossil' AS 'repository' KEY ''; SELECT value FROM config WHERE name='allow-symlinks'; SELECT value FROM global_config WHERE name='allow-symlinks'; SELECT value FROM config WHERE name='aux-schema'; SELECT 1 FROM config WHERE name='baseurl:http://'; SELECT value FROM config WHERE name='ip-prefix-terms'; SELECT value FROM global_config WHERE name='ip-prefix-terms'; SELECT value FROM config WHERE name='localauth'; SELECT value FROM vvar WHERE name='default-user'; SELECT uid FROM user WHERE cap LIKE '%s%'; SELECT login FROM user WHERE uid=1; SELECT cap FROM user WHERE login = 'nobody'; SELECT cap FROM user WHERE login = 'anonymous'; SELECT value FROM config WHERE name='public-pages'; SELECT value FROM global_config WHERE name='public-pages'; SELECT value FROM config WHERE name='header'; SELECT value FROM config WHERE name='project-name'; SELECT value FROM config WHERE name='th1-setup'; SELECT value FROM global_config WHERE name='th1-setup'; SELECT value FROM config WHERE name='redirect-to-https'; SELECT value FROM global_config WHERE name='redirect-to-https'; SELECT value FROM config WHERE name='index-page'; SELECT mtime FROM config WHERE name='css'; SELECT mtime FROM config WHERE name='logo-image'; SELECT mtime FROM config WHERE name='background-image'; CREATE TEMP TABLE IF NOT EXISTS timeline( rid INTEGER PRIMARY KEY, uuid TEXT, timestamp TEXT, comment TEXT, user TEXT, isleaf BOOLEAN, bgcolor TEXT, etype TEXT, taglist TEXT, tagid INTEGER, short TEXT, sortby REAL ) ; INSERT OR IGNORE INTO timeline SELECT blob.rid AS blobRid, uuid AS uuid, datetime(event.mtime,toLocal()) AS timestamp, coalesce(ecomment, comment) AS comment, coalesce(euser, user) AS user, blob.rid IN leaf AS leaf, bgcolor AS bgColor, event.type AS eventType, (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid AND tagxref.rid=blob.rid AND tagxref.tagtype>0) AS tags, tagid AS tagid, brief AS brief, event.mtime AS mtime FROM event CROSS JOIN blob WHERE blob.rid=event.objid AND NOT EXISTS(SELECT 1 FROM tagxref WHERE tagid=5 AND tagtype>0 AND rid=blob.rid) ORDER BY event.mtime DESC LIMIT 50; -- SELECT value FROM config WHERE name='timeline-utc'; SELECT count(*) FROM timeline WHERE etype!='div'; SELECT min(timestamp) FROM timeline; SELECT julianday('2016-09-15 14:54:51',fromLocal()); SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob WHERE blob.rid=event.objid AND mtime<=2457647.121412037); SELECT max(timestamp) FROM timeline; SELECT julianday('2016-09-24 17:42:43',fromLocal()); SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob WHERE blob.rid=event.objid AND mtime>=2457656.238009259); SELECT value FROM config WHERE name='search-ci'; SELECT value FROM vvar WHERE name='checkout'; SELECT value FROM config WHERE name='timeline-max-comment'; SELECT value FROM global_config WHERE name='timeline-max-comment'; SELECT value FROM config WHERE name='timeline-date-format'; SELECT value FROM config WHERE name='timeline-truncate-at-blank'; SELECT value FROM global_config WHERE name='timeline-truncate-at-blank'; SELECT * FROM timeline ORDER BY sortby DESC; SELECT value FROM config WHERE name='hash-digits'; SELECT value FROM global_config WHERE name='hash-digits'; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=68028; SELECT pid FROM plink WHERE cid=68028 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM tagxref WHERE rid=68028 AND tagid=9 AND tagtype>0; SELECT value FROM config WHERE name='timeline-block-markup'; SELECT value FROM config WHERE name='timeline-plaintext'; SELECT value FROM config WHERE name='wiki-use-html'; SELECT value FROM global_config WHERE name='wiki-use-html'; SELECT 1 FROM private WHERE rid=68028; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=68026; SELECT pid FROM plink WHERE cid=68026 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=68026; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=68024; SELECT pid FROM plink WHERE cid=68024 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=68024; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=68018; SELECT pid FROM plink WHERE cid=68018 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=68018; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=68012; SELECT pid FROM plink WHERE cid=68012 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=68012; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=68011; SELECT value FROM config WHERE name='details'; SELECT pid FROM plink WHERE cid=68011 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM tagxref WHERE rid=68011 AND tagid=9 AND tagtype>0; SELECT 1 FROM private WHERE rid=68011; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=68008; SELECT pid FROM plink WHERE cid=68008 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=68008; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=68006; SELECT pid FROM plink WHERE cid=68006 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=68006; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=68000; SELECT pid FROM plink WHERE cid=68000 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=68000; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67997; SELECT pid FROM plink WHERE cid=67997 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67997; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67992; SELECT pid FROM plink WHERE cid=67992 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67992; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67990; SELECT pid FROM plink WHERE cid=67990 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67990; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67989; SELECT pid FROM plink WHERE cid=67989 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67989; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67984; SELECT pid FROM plink WHERE cid=67984 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67984; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67983; SELECT pid FROM plink WHERE cid=67983 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67983; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67979; SELECT pid FROM plink WHERE cid=67979 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67979; SELECT value FROM config WHERE name='ticket-closed-expr'; SELECT status='Closed' OR status='Fixed' FROM ticket WHERE tkt_uuid>='1ec41379c9c1e400' AND tkt_uuid<'1ec41379c9c1e401'; SELECT 1 FROM private WHERE rid=67980; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67977; SELECT pid FROM plink WHERE cid=67977 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT status='Closed' OR status='Fixed' FROM ticket WHERE tkt_uuid>='1ec41379c9c1e400' AND tkt_uuid<'1ec41379c9c1e401'; SELECT 1 FROM private WHERE rid=67977; SELECT status='Closed' OR status='Fixed' FROM ticket WHERE tkt_uuid>='1ec41379c9c1e400' AND tkt_uuid<'1ec41379c9c1e401'; SELECT 1 FROM private WHERE rid=67974; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67971; SELECT pid FROM plink WHERE cid=67971 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67971; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67972; SELECT pid FROM plink WHERE cid=67972 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67972; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67969; SELECT pid FROM plink WHERE cid=67969 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67969; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67966; SELECT pid FROM plink WHERE cid=67966 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67966; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67962; SELECT pid FROM plink WHERE cid=67962 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67962; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67960; SELECT pid FROM plink WHERE cid=67960 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67960; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67957; SELECT pid FROM plink WHERE cid=67957 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67957; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67955; SELECT pid FROM plink WHERE cid=67955 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67955; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67953; SELECT pid FROM plink WHERE cid=67953 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT status='Closed' OR status='Fixed' FROM ticket WHERE tkt_uuid>='5990a1bdb4a073' AND tkt_uuid<'5990a1bdb4a074'; SELECT 1 FROM blob WHERE uuid>='5990a1bdb4a073' AND uuid<'5990a1bdb4a074'; SELECT 1 FROM private WHERE rid=67953; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67941; SELECT pid FROM plink WHERE cid=67941 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67941; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67940; SELECT pid FROM plink WHERE cid=67940 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67940; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67938; SELECT pid FROM plink WHERE cid=67938 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67938; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67935; SELECT pid FROM plink WHERE cid=67935 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67935; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67934; SELECT pid FROM plink WHERE cid=67934 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67934; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67932; SELECT pid FROM plink WHERE cid=67932 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67932; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67930; SELECT pid FROM plink WHERE cid=67930 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67930; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67928; SELECT pid FROM plink WHERE cid=67928 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM tagxref WHERE rid=67928 AND tagid=9 AND tagtype>0; SELECT 1 FROM private WHERE rid=67928; SELECT status='Closed' OR status='Fixed' FROM ticket WHERE tkt_uuid>='0eab1ac7591f511d' AND tkt_uuid<'0eab1ac7591f511e'; SELECT 1 FROM private WHERE rid=67919; SELECT status='Closed' OR status='Fixed' FROM ticket WHERE tkt_uuid>='01874d252ac44861' AND tkt_uuid<'01874d252ac44862'; SELECT 1 FROM blob WHERE uuid>='01874d252ac44861' AND uuid<'01874d252ac44862'; SELECT 1 FROM private WHERE rid=67918; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67916; SELECT pid FROM plink WHERE cid=67916 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT status='Closed' OR status='Fixed' FROM ticket WHERE tkt_uuid>='0eab1ac759' AND tkt_uuid<'0eab1ac75:'; SELECT 1 FROM private WHERE rid=67916; SELECT status='Closed' OR status='Fixed' FROM ticket WHERE tkt_uuid>='a49bc0a8244feb08' AND tkt_uuid<'a49bc0a8244feb09'; SELECT 1 FROM blob WHERE uuid>='a49bc0a8244feb08' AND uuid<'a49bc0a8244feb09'; SELECT 1 FROM private WHERE rid=67914; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67913; SELECT pid FROM plink WHERE cid=67913 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT status='Closed' OR status='Fixed' FROM ticket WHERE tkt_uuid>='0eab1ac7591f' AND tkt_uuid<'0eab1ac7591g'; SELECT 1 FROM private WHERE rid=67913; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67911; SELECT pid FROM plink WHERE cid=67911 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67911; SELECT status='Closed' OR status='Fixed' FROM ticket WHERE tkt_uuid>='0eab1ac7591f511d' AND tkt_uuid<'0eab1ac7591f511e'; SELECT 1 FROM private WHERE rid=67909; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67907; SELECT pid FROM plink WHERE cid=67907 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67907; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67899; SELECT pid FROM plink WHERE cid=67899 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67899; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67897; SELECT pid FROM plink WHERE cid=67897 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67897; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67895; SELECT pid FROM plink WHERE cid=67895 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67895; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67893; SELECT pid FROM plink WHERE cid=67893 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67893; SELECT value FROM tagxref WHERE tagid=8 AND tagtype>0 AND rid=67891; SELECT pid FROM plink WHERE cid=67891 AND pid NOT IN phantom ORDER BY isprim DESC; SELECT 1 FROM private WHERE rid=67891; SELECT count(*) FROM plink WHERE pid=67928 AND isprim AND coalesce((SELECT value FROM tagxref WHERE tagid=8 AND rid=plink.pid), 'trunk') =coalesce((SELECT value FROM tagxref WHERE tagid=8 AND rid=plink.cid), 'trunk') ; SELECT count(*) FROM plink WHERE pid=68011 AND isprim AND coalesce((SELECT value FROM tagxref WHERE tagid=8 AND rid=plink.pid), 'trunk') =coalesce((SELECT value FROM tagxref WHERE tagid=8 AND rid=plink.cid), 'trunk') ; SELECT count(*) FROM plink WHERE pid=68028 AND isprim AND coalesce((SELECT value FROM tagxref WHERE tagid=8 AND rid=plink.pid), 'trunk') =coalesce((SELECT value FROM tagxref WHERE tagid=8 AND rid=plink.cid), 'trunk') ; SELECT value FROM config WHERE name='show-version-diffs'; SELECT value FROM config WHERE name='adunit-omit-if-admin'; SELECT value FROM global_config WHERE name='adunit-omit-if-admin'; SELECT value FROM config WHERE name='adunit-omit-if-user'; SELECT value FROM global_config WHERE name='adunit-omit-if-user'; SELECT value FROM config WHERE name='adunit'; SELECT value FROM global_config WHERE name='adunit'; SELECT value FROM config WHERE name='auto-hyperlink-delay'; SELECT value FROM global_config WHERE name='auto-hyperlink-delay'; SELECT value FROM config WHERE name='footer'; PRAGMA database_list; PRAGMA database_list; PRAGMA localdb.freelist_count; PRAGMA localdb.page_count;