Sqlite Overview

Table of Contents

1. SQLite:Overview

1.1. sqlite3_analyzer

1.2. sqlite command

1.2.1. .output

1.2.2. .mode

list|column|insert|line|tabs|tcl|csv

1.2.3. .dump

1.2.4. .read

1.2.5. .tables

1.2.6. .separator

1.2.7. .schema

1.2.8. .headers [on|off]

1.2.9. explain query plan

see also queryplanner

explain query plan select * from foo;

1.2.10. explain

explain select * from foo;

1.2.11. vacuum

1.3. sqlite SQL

1.3.1. attach database

attach database "foo.db" as db2; select * from db2.tbl_1; detach database db2;

1.3.2. create table

1.3.2.1. storage class

使用 select typeof (xx) 来查看 storage class

  • integer

61

  • real

61.0

  • text

"a"

  • blob

x'61'

1.3.2.2. constrains
1.3.2.2.1. column-level constrains
  • not null
  • unique
  • primary key
  • foreign key
  • check
create temp table foo(
x integer,
y integer check (y>x),
z integer check (z>abs(y)),
);

see also `trigger`

  • collate
    • binary
    • nocase
  • default
  • autoincrement
1.3.2.2.2. table-level constrains
  • primary key
CREATE TABLE xxx (
data1 text,
data2 text,
primary key (data1, data2)
);
  • unique
CREATE TABLE xxx (
_id integer primary key,
data1 text,
data2 text,
unique (data1, data2)
);
  • check

1.3.3. trigger

create [temp|temporary] trigger name
[before|after|instead of] [insert|delete|update|update of columns] on table
[for each row] [when expr]
begin
action
end;
  • using trigger to update view
create trigger on_update_foods_view
instead of update on foods_view
for each row
begin
   update foods set name=new.fname where id=new.fid;
   update food_types set name=new.tname where id=new.tid;
end;

注: sqlite 的 trigger 不支持 `for each statement`, 只支持 `for each row`

1.3.4. transaction

  • begin [ deferred | immediate | exclusive ] [transaction]
  • commit
  • rollback
  • savepoint
begin transaction;
insert into xxx;
...
savepoint test
...
rollback to test
...
commit

insert into xxx; save

1.3.5. confict resolution

  • replace
  • ignore 忽略本次错误, 继续执行
  • fail 结束, 但不回滚
  • abort default 回滚, 然后结束
  • rollback 回滚, 但不结束

confict resolution can be specified in

  • table or view defination
create temp table cast(name text unique on conflict rollback);
  • in `insert`, `update`
insert or replace into table values (xxx);
  • trigger

1.3.6. join

  • inner join

join

  • left outer join

left out join

  • right outer join

not supported

  • full outer join

not supported

  • cross

select from tbl1, tbl2

1.3.7. index

refers 1.2.9 to delete whether index is used for optimization

  • index
  • unique index
  • covering index covering index 是指同一个 index 中有多个字段, 查找时直接从 index 中取得了数据, 而不是从 index 取得主键, 再到主表获取数据. 例如:

    create index m3_index on foo(x,y); explain query plan select y from foo where x="a";

0|0|0|SEARCH TABLE foo USING COVERING INDEX m3_index (x=?) (~10 rows)

Note:

  • collate 会影响 index, 例如若 select 时或建表时使用的 collate 与建立 index 时指定的 collate 不一致时, index 无法起作用.
  • create index 可以指定多个字段 (以利于形成 covering index), 但多个字段是联合在一起索引的, 例如 (x,y,z), 则使用 x, x and y, x and y and z 时索引起作用, 但使用 y and z, z 时不起作用.

1.3.8. view

  • using trigger to update view

1.3.9. insert

1.3.10. update

1.3.11. select


          -+-------------------------------------------------------------------------------------+
           |       -+-----------------------------------------------------------+                |        result
           |        |                        -+---------------+                 |                |          ^
           |        |                         |               |                 |                |          |
SELECT DISTINCT heading FROM tables WHERE predicate GROUP BY columns HAVING predicate ORDER BY columns LIMIT init,int;
           | 6      | 5         | 1           | 2             | 3               | 4              | 7        | 8
           |        |           |             |               |                 |                |          |
          -+--------+          -+-------------+              -+-----------------+               -+----------+

1.3.11.1. distinct

distinct 可以同时修饰多个字段

select distinct id, name from xxx;

表示只有 (id,name) 都相同时才算相同

1.3.11.2. where
1.3.11.3. group by
1.3.11.4. having
1.3.11.5. order by
1.3.11.6. limit
1.3.11.7. sub-query
  • for `select`
select _id, (select name from xx where _id=f._id ) from xx as f;
  • for `from`
select _id from (select _id,name from xxx);
  • for `order by`
select _id from xxx as f order by (select score from xxx where _id=f._id);
  • for `where'
select _id from xxx where _id in (select _id from xxx);

Note:

  • sub-query 几乎可以用在任何地方
  • sub-query 通常需要设置别名
  • sub-query 只能返回一列.
  • sub-query 有时需要返回一行 (例如在 order by 的场合), 这时若返回多行, 则系统只会使用第一行.
1.3.11.8. compound query

compound query 要求各个查询返回相同的例, 且只能在 compound query 最后有一个 order by

1.3.11.8.1. union [all]

a | b

1.3.11.8.2. intercept

a & b

1.3.11.8.3. except

a - b

1.3.11.9. conditional result

used to transform column values

case value
  when x then value_x
  when y then value_y
  when z then value_z
  else default_value
end
select name,(select
              case
              when count(*) > 4 then 'Very High'
              when count(*) = 4 then 'High'
              when count(*) in (2,3) then 'Moderate'
              else 'Low'
              end
              from foods_episodes
              where food_id=f.id) as frequency
from foods f
where frequency like '%High'

1.3.12. functions

1.3.12.1. core functions
  • last_insert_rowid()
  • coalesce (x, y, z, …) return the first not null value, or null

    e.g. coalesce (null, 1, 2, null) returns 1

  • ifnull (x, y) ifnull (x, y) <==> coalesce(x, y) e.g. SELECT Name, IFNULL(Age, 'unknown') AS Age FROM People
  • nullif (x, y) 若 x,y 相同返回 null, 否则返回 x. e.g. SELECT COUNT(NULLIF(Bonus, 0)) FROM Employees
  • glob
  • like
  • substr
  • trim
  • ltrim
  • rtrim
  • instr
  • quote
  • length
  • lower
  • upper
  • abs
  • max
  • min
  • random
  • replace
  • hex
  • round
  • date
1.3.12.2. aggregation function
  • avg
  • sum
  • total
  • max
  • min
  • count (x)
  • count (*)

1.3.13. fts

1.3.14. Summary

1.3.14.1. cross-join != full-outer-join

例如, 若 A 表为 3 条, 其中 _id 分别为 1,2,3. B 表为 4 条, _id 分别为 5,6,7,8, 则:

  • select * from A, B where A._id = B._id 返回 0 条
  • select * from A full outer join B on A._id = B._id 返回 7 条 (此处为假设 full-outer-join 是支持的)
1.3.14.2. having 与 where 的区别
  • haveing 发生在 group by 之后, 而 where 发生在 group by 之前;
  • 可以使用 aggregation 函数
1.3.14.3. aggregation 函数只可以使用在 select 之后和 having 之后
1.3.14.4. 使用两个 left-outer-join 模拟 full-outer-join
select * from A left outer join B on A._id = B._id
UNION
select * from B left outer join A on A._id = B._id
1.3.14.5. distinct 可以使用 group by 来模拟
1.3.14.6. compound query 只允许在最后使用一个 order-by, 不过可以用 sub-query 来跳过这一限制
1.3.14.7. sub-query 几乎可以使用在任何地方, 不过只允许返回一列, 但可以返回多行 (虽然有时只有第一行有效)
1.3.14.8. sqlite 允许在有 group by 中查询中 select group-by 之外的字段, 只是结果是不可靠的. 例如:
select name, id from xxx group by id;

同理适用于 aggregation functions, 例如:

select *, count(*) from xxx;
1.3.14.9. integer primary key => autoincrement (but with filling-gaps)
1.3.14.10. autoincrement 会导致后续的值一定比之前的大 (不会有 filling-gaps 效果), 当到达最大值后, 返回 data is full error

这一点需要与 integer primary key 区别

1.3.14.11. autoincrement 必须在 integer primary key 后使用
1.3.14.12. storage class vs. sort

storage class 与列的定义无关, 它取决了输入的数据的格式: 61, 61,0, "a", x'61', null 的 storage class 分别为 integer, real, text, blob, null

不同的 storage class 的排序规则: null < integer = real < text < blob

see also

1.3.14.13. sqlite 的 view 是不可修改的, 但可以用 trigger 来模拟实现可修改的 view
1.3.14.14. index 可以同时指定多个字段, 以便使用 covering index, 但要注意查询条件, 例如

(x,y,z)三个字段的索引,使用 x, x and y, x and y and z 会使用索引, 但 y, y and z, z 不会.

1.3.14.15. 一次查询可能会使用多个索引, 例如使用 or 的情况下:

create index m1_index on foo(x); create index m1_index on foo(y); select x from foo where x="a" or y="a"; 0|0|0|SEARCH TABLE foo USING INDEX m1_index (x=?) (~10 rows) 0|0|0|SEARCH TABLE foo USING INDEX m2_index (y=?) (~10 rows)

1.3.14.16. unique index
1.3.14.17. 有些情况下 index 无法使用, 例如
  • glob "a*"
  • like (测试了下似乎 sqlite 并不会对 like 进行优化, 可能和版本有关?)
  • 多列索引时有些情况
  • select xx from xx where length(x)=5, 等
1.3.14.18. savepoint & rollback to
1.3.14.19. conflict resolution
1.3.14.20. attach database

1.4. sqlite limitation

  • 不支持 right/full outer join
  • 不完全的 alter table 支持
  • 不完全的触发器支持
  • 视图是只读的
  • 不支持物化的视图
  • 不支持 GRAND and REVOKE
  • 不支持表/行级别的锁
  • 不支持存储过程

1.5. sqlite pragma

1.5.1. auto_vacuum = 0/1

1.5.2. cache_size

cache_size 表示 writer 在真正 flush 日志文件和 page cache 之前, 最多能 cache 多少修改.

1.5.3. default_cache_size

1.5.4. case_sensitive_like = 0/1

1.5.5. count_changes = 0/1

1.5.6. encoding = "UTF-8"

1.5.7. page_size = bytes

1.5.8. max_page_count

1.5.9. synchronous = FULL/NORMAL/OFF

synchronous 表示 write 在 commit 或 cache 满后如何 flush 日志文件.

FULL 一定不会导致 playback 出错: 在 syncJournal 时, sqlite 会先将数据 sync, 然后再 sync journal 头, 包括 (nRec 字段)

NORMAL 可能会导致 playback 出错, 因为 syncJournal 时, sqlite 会先将 header 添加到日志中, 然后一次性 sync 到磁盘, 此时掉电有可能导致 journal 头显示这是一个 hot-journal, 但后续的数据确是错的.

特别的,在 WAL 模式下, synchronous 为 NORMAL 时 commit 也不再写数据库, 只有 checkout 时才刷新.

1.5.10. vdbe_trace=on/off

1.5.11. vdbe_listing=on/off

1.5.12. parser_trace=on/off

1.5.13. temp_store

临时文件保存在内存(1)还是磁盘(2)或者由编译时指定(0).临时文件包括各种临时表(视图, 子查询), 临时索引(order by, group by, distinct …), 临时数据库 (temp table), statement journal.

即使选择用磁盘保存临时文件, sqlite 也会通过 pcache 优先将它暂时保存在内存里而避免写磁盘.

1.5.14. locking_mode=normal/exclusive

在 NORMAL 模式下, (默认值), 一个数据库连接会在每次完成读或写时释放数据库文件锁。 当锁模式设为 EXCLUSIVE 时,数据库连接永远不会释放文件锁。 在该模式下,当第一次读数据库文件时,会获得并持有一个共享锁。 当第一次向数据库写时,将获得并持有一个排它锁。在 EXCLUSIVE 模式下获得的锁可以通过关闭数据库连接来释放, 也可以通过使用该 pragma 将锁模式改为 NORMAL,并且再次访问(读或写) 数据库来释放。仅仅将锁模式置为 NORMAL 是不够的, 直到下一次访问数据库文件时才会释放已持有的锁。有两种原因要求设置 EXCLUSIVE 锁模式。 一是一个应用程序确实不希望其它进程访问数据库文件。二是在这种模式下可以使用优化器,它可以节省少量的磁盘文件操作。 这在嵌入式系统中可能非常重要

1.6. optimization

1.6.1. 使用索引

1.6.2. 避免 transient table (subquery)

许多 sqlite 操作例如 order by, aggregation, subquery 等需要使用到 transient table 来暂存中间结果, 尤其是 subquery. 这些 transient table 主要的问题是无法使用 index.

  • 使用显式的 temp table 并使用索引?
  • sqlite 本身可以对一些 subquery 进行扁平化 (flattening), 例如:

    SELECT * FROM ex1 WHERE ex1.a IN (SELECT b FROM ex2);
    

    可能被扁平化为

    SELECT ex1.*, ex2.b FROM ex1 JOIN ex2 ON ex2.b=ex1.a;
    

1.6.3. automatic index

1.6.4. view 与 subquery 类似, 无法使用索引

1.6.5. join 的顺序

1.6.6. 使用 limit

1.7. references

1.8. Other

1.8.1. WAL

Write Ahead Logging (WAL) 是与 sqlite 默认的 rollback journal (或 delete journal, shadow paging) 完全不同的一种 journal mode. 通过 WAL, 数据库可以做到读和写互相完全不干扰, 并且减少 IO 操作.

1.8.1.1. How WAL works

当 commit 一个 write transaction 时, rollback journal 的作法是先刷新所有修改到 journal, 然后再刷新 cache 到数据库. 而 WAL 的作法是只将修改刷新到 wal journal 中, 而不修改数据库. 只有当用户调用 pragma wal_checkpoint 或 超过 1000 页被修改时, wal journal 中的修改才会被写回到数据库.

为了使 read transaction 能读到最新的数据, 每个 read transaction 开始时会扫描 wal journal, 并在 wal file 中记下一个 mark point, 在读数据库时, 根据一个 wal index shm 判断要读的 page 是否在 wal journal 中, 若存在, 并且不超过 mark point, 则使用 wal journal 中的数据, 否则使用数据库中的数据. 这样可以使得读写不用互相等待. (但这样有一点问题, 就是无法保证读到最新的数据, 因为有可能在读的过程中, 又有新的数据追加到 wal journal file 中 makr point 之后)

1.8.1.1.1. WAL 的优点
  • 读写互不影响
  • 降低了 IO 的使用

    commit 只需要写 wal journal file, 不需要写数据库. 特别的, 若 WAL 模式下设置 pragma synchronous 为 NORMAL 的话,则 commit 根本不会导致 IO 操作,只有 checkpoint 才会. 因此, 在 wal 模式下, commit 不进行 IO 可能会导致异常重启后数据丢失, 但不会造成数据库崩溃.

    checkpoint 进行的 IO 操作包括:

    1. 写 wal 到磁盘
    2. 写 wal 到数据库
    3. 重置 wal
1.8.1.1.2. WAL 的缺点
  • 可能无法读到"最新"的数据, 因为 read transaction 无法 block write transaction
  • 过大的 read transaction 会阻止 checkpoint (当 read transaction 正在进行时, 虽然允许 write transaction 追加数据到 wal journal, 但不允许清空, 因为 read transaction 已经在 journal 中标记了 mark point), 进而导致 wal journal 过大, 从而使 read transaction 变慢
1.8.1.1.3. WAL 的配置
  • pragma journal_mode=wal 开启 wal 模式
  • pragma journal_mode=delete 关闭 wal 模式
  • wal 模式的配置是 persistent 的
  • pragma wal_autocheckpoint=1000 设置 autocheckpoint 的 thresh-hold 特别的, 这个值越大, 则写的性能越高, 这个值越小, 则读的性能越好.
1.8.1.2. 关于 WAL journal file 的一个实验
-- these are auto-committed
insert into test values ("a", 1);
insert into test values ("a", 1);
insert into test values ("a", 1);
-- 现在, 记录应该存在 test.db-wal 中, 但还没有写到数据库中
-- case A: 不调用 pragma wal_checkpoint
  -- kill sqlite3_process
  -- case A1: 先删除 test.db-wal, 再打开 test.db
    select count(*) from test where name="a";
    0
  -- case A2: 不删除 test.db-wal, 找开 test.db
    select count(*) from test where name="a";
    3
-- case B: 调用 pragma wal_checkpoint
  -- kill sqlite3_process
  -- 无论是否删除 test.db-wal, 打开 test.db
    select count(*) from test where name="a";
    3

1.8.2. FTS

FTS (Full Text Search), 即全文检索,主要有以下几个方面:

  • stemming (词根)
  • tokenizer & word segregation (分词)
  • inverted index (倒排索引)
1.8.2.1. stemming

http://en.wikipedia.org/wiki/Stemming

A stemmer for English, for example, should identify the string "cats" (and possibly "catlike", "catty" etc.) as based on the root "cat".and "stemmer", "stemming", "stemmed" as based on "stem".

A stemming algorithm reduces the words "fishing", "fished", "fish", and "fisher" to the root word, "fish". On the other hand, "argue", "argued", "argues", "arguing", and "argus" reduce to the stem "argu"

android contacts provider 就是使用的一种叫做 porter 的 stemmer 算法, 所以用 cat 可以查找到名为 catty 的人也是正常的.

1.8.2.2. tokenizer and word segregation (分词)

全文检索是基于`关键字`匹配的检索, 与传统的 grep 等检索方法不同, 全文检索需要先建立索引文件, 建立索引文件的第一步就是将全文分解为一系列的关键字. 例如:

"A stemmer for English, for example, should identify the string "cats" (and possibly "catlike", "catty" etc.) as based on the root "cat""

这句话可能会被分解为以下的关键字:

stem, english, example, identify, string, cat, possible, base, root

tokenize 的过程主要分为三步:

  1. 分词, 对英文来说, 基本就是以空格来分词
  2. 抛弃一些 stop words, 如 a, for, should, and ,etc, as, on 等
  3. 对剩下的非 stop words 使用 stemming 算法, 例如 cats->cat, stemmer->stem
1.8.2.2.1. 分词

英文分词比较简单,就是以空格来分词, 对于中文或其他一些语言就麻烦的多, 以中文为例, 主要有以下几种分词方法:

  1. N 元分词就是简单的每 N 个字算一个词,

    • 1 元分词:

    英/文/分/词/比/较/简/单

    • 2 元交叉为例:

    英文/文分/分词/词比/比较/较简/简单

  2. 基于词典匹配的分词
    • 正向最大匹配 "市场/中国/有/企业/才能/发展"
    • 逆向最大匹配 "市场/中/国有/企业/才能/发展"
    • 双向最大匹配
  3. 基于统计的分词

从目前存在的项目看, 综合 N 元分词与基于词典匹配的分词是主流的方法, 以 Apache Lucene 为例: 它包含以下几种中文分词算法: http://blog.csdn.net/chaocy/article/details/5938741

  • StandardAnalyzer & ChineseAnalyzer (一元分词)

2008/年/8/月/8/日/晚/举/世/瞩/目/的/北/京/第/二/十/九/届/奥/林/匹/克/运/动/会/开 /幕/式/在/国/家/体/育/场/隆/重/举/行

  • CJKAnalyzer (交叉二元分词)

2008/年/8/月/8/日晚/举世/世瞩/瞩目/目的/的北/北京/京第/第二/二十/十九/九届/届奥/ 奥林/林匹/匹克/克运/运动/动会/会开/开幕/幕式/式在/在国/国家/家体/体育/育场/场隆/ 隆重/重举/举行/

  • MIK_CAnalyzer (最大匹配+二元交叉)

2008 年/8 月/8 日/晚/举世瞩目/目的/北京/第二十九届/奥林匹克运动会/开幕式/在国/国家/体育场/隆重举行/

  • etc

sqlite3 中因为支持 FTS, 所以也支持几种分词算法:

  • simple 针对英文, 根据空格分词
  • porter 针对英文, 使用 port stemmer
  • icu 使用 icu 库进行简单分词, 没有看懂 fts_icu 的源码, 从分词结果看类似于一元分词 (待确定)
1.8.2.3. inverted index (倒排索引)

通过分词算法确定关键词后, FTS 会使用倒排索引建立索引, 例如全文有两句话:

`今天天气怎么样. 今天天气不错. `

  • 分词的结果

    今天/天气/怎么样/今天/天气/不错

  • 倒排索引结果

    今天->1,0;2,0 天气->1,2;2,2 怎么样->1,4 不错->2,4

倒排索引的结果通常会以一种高效的利于查找的形式保存到索引文件中, 例如根据关键字排序, 或使用 B 树

1.8.2.4. 查找过程

根据索引文件格式的不同, 查找的过程有所区别, 以 B 树为例, 查找过程就是以查找字符串为 KEY 在 B 树中查找该关键字,查到的 VALUE 就是该关键字在文档中的行列位置.

全文查找速度很快,但有一个明显的缺点: 检索的效果依赖于关键字的选择.

例如,

  • 使用 "tty" 无法检索到 " hello kitty "这句话
  • 使用 "天天" 可能无法检索到 "今天天气不错"
1.8.2.5. FTS in sqlite3
  • create virtual table search_index using fts3(content TEXT, tokenize=porter); tokenize 可以为 simple, porter, icu, 但默认情况下 icu tokenizer 功能没有被编译到 sqlite3 中
  • select * from search_index where content match "token1 token2"
  • select * from search_index where content match "tok*"

1.8.3. ICU

1.8.4. R-Tree

Author: [email protected]
Date: 2017-09-09 Sat 00:00
Last updated: 2021-07-23 Fri 14:22

知识共享许可协议