首先我们要知道SQLite的索引特点
它会给每一个记录自动添加ROWID 字段,并建立B+树
如果指明integer类型的数据是primary key, 那么这个attribute实际是ROWID的别名
对于不是Integer类型的主键,建立B-树
考虑下面表
create table T(
word text primary key,
cnt integer
);
我们对上边的表进行查询
select * from T where word='JAVA'
当然我们可以避免使用上边双重索引。
create table wordcount(
word text primary key,
cnt integer
) without ROWID;
这样,SQLite只会为word建立B树索引。但是这样只有记录很少的时候才会有益处,一般我们不会这么做。
选择正确的索引非常重要,可以显著提高查询性能。在SQLite中,有三种类型的索引可供选择:B树、B+树和Hash索引。
B树和B+树适用于范围查询和顺序访问。在SQLite中,主键总是使用B+树索引,而其他索引可以使用B树或B+树。
Hash索引适用于等值查询,但不支持范围查询。在SQLite中,Hash索引不常用,因为B树和B+树已经能够很好地满足需求
在SQLite中,建立复合索引可以显著提高查询性能。复合索引可以覆盖多个列,从而使查询更加高效。
例如,如果有一个包含“FirstName”和“LastName”列的表,并且经常按照这两个列进行查询,那么建立复合索引就非常有用。这可以减少查询所需的时间,因为SQLite可以同时搜索两个列。
删除不必要的索引可以减少查询的时间。如果表的列已经被索引,并且这些索引已经满足了大多数查询需求,那么删除其他索引可能是一个好主意。这将减少查询所需的时间和磁盘空间,并且还可以提高查询性能
我们可以使用那个Explain查看具体SQLite怎么执行查询命令的。
explain query plan select * from wordcount
where word='JAVA'
0|0|0|SEARCH TABLE wordcount USING INDEX sqlite_autoindex_wordcount_1 (word=?)
CREATE TABLE Employee (
id INTEGER PRIMARY KEY,
age INTEGER,
salary INTEGER
);
CREATE INDEX Age_Index ON Employee(age);
EXPLAIN QUERY PLAN SELECT *
FROM Employee
WHERE Age>=30 AND salary=2000;
0|0|0|SEARCH TABLE employee USING INDEX Age_Index (age>?)
用关系代数可以表示为
SELECT *
FROM Employee
WHERE Age>=30
INTERSECT
SELECT *
FROM Employee
WHERE salary=2000;
1|0|0|SCAN TABLE employee
2|0|0|SEARCH TABLE employee USING INDEX age_index (age>?)
0|0|0|COMPOUND SUBQUERIES 1 AND 2 USING TEMP B-TREE (INTERSECT)
很显然对salary=2000
没有使用索引
现在我们看一个复合索引
CREATE TABLE words (
occid INTEGER PRIMARY KEY,
word TEXT,
location INTEGER,
cnt INTEGER
);
CREATE INDEX wl ON words(word,location);
SELECT location
FROM words
WHERE word = "Java";
0|0|0|SEARCH TABLE words USING COVERING INDEX wl (word=?)
注意covering
,这意味着我们只从索引中就得到了我们要得查询结果。这也不难理解,因为符合索引中有location信息。
CREATE TABLE TA (
id INTEGER PRIMARY KEY,
a INTEGER
);
CREATE TABLE TB (
id INTEGER PRIMARY KEY,
b INTEGER
);
SELECT *
FROM TA A, TB B
WHERE A.id = B.id;
0|0|0|SCAN TABLE TA AS A
0|1|1|SEARCH TABLE TB AS B USING INTEGER PRIMARY KEY (rowid=?)
第一行告诉我们会全盘查询A
第二行告诉我们使用索引对B进行查询。
为什么选择扫描A表呢?这是因为优化器发现A表中的数据更好,扫描更节省时间。
那么优化器是怎么判断数据量多少呢?
对于每一个索引,Sqlite会存储一条如下记录在sqlite_stat1表中
Test,sqlite_autoindex_Test_1,”12 6 3 1”
- 12:表中记录数量
- 6: 对于复合索引第一个字段或者独立索引,平均能找到的数据数量
- 3: 复合索引第二个字段能找到的数量
- 1: 复合索引第三个字段能找到的数量
对于下表
CREATE TABLE TA (
id INTEGER PRIMARY KEY,
a INTEGER
);
CREATE TABLE TB (
id INTEGER PRIMARY KEY,
b INTEGER
);
有这样的statistics:
TA,sqlite_autoindex_TA_1,”100 1”
TB,sqlite_autoindex_TB_1,”10 1”
现在要执行如下查询语句
SELECT *
FROM TA A, TB B
WHERE A.id = B.id;
优化器会判断扫描B表,因为B表更加小,扫描时间更短。
有时候哦我们查询的语句没有索引,SQLite可能会建立临时索引
CREATE TABLE TA (
id INTEGER PRIMARY KEY,
a INTEGER
);
CREATE TABLE TB (
id INTEGER PRIMARY KEY,
b INTEGER
);
SELECT B.id
FROM TA A, TB B
WHERE A.a = B.b;
0|0|0|SCAN TABLE TA AS A
0|1|1|SEARCH TABLE TB AS B USING AUTOMATIC COVERING INDEX (b=?)
数据库会选择对大表建立临时索引,但是建立临时索引是动态生成的,我们应当避免这种情况的发生。
在SQLite中,索引的选择和使用对查询性能有很大的影响。通过选择正确的索引类型,建立复合索引,删除不必要的索引,使用统计数据和避免使用临时索引,可以显著提高查询性能。
B±tree: B+树
B-tree: B树, B-树