SQLite索引构建:告别查询卡顿,让你的SQL语句更加高效

首先我们要知道SQLite的索引特点

它会给每一个记录自动添加ROWID 字段,并建立B+树

如果指明integer类型的数据是primary key, 那么这个attribute实际是ROWID的别名

对于不是Integer类型的主键,建立B-树

为什么会增加ROWID

  1. 数据库内部可以使用这个字段索引记录,这很高效。
  2. 数据库设计的时候,往往需要自增的ROWID,SQLite帮我们提前实现了。

考虑下面表

create table T(
  word text primary key,
  cnt integer
);

我们对上边的表进行查询

select * from T where word='JAVA'
  1. 首先,如果使用B-树找到word='JAVA’的rowid
  2. 使用rowid 找打记录

避免双重索引

当然我们可以避免使用上边双重索引。

create table wordcount(
  word text primary key,
  cnt integer
) without ROWID;

这样,SQLite只会为word建立B树索引。但是这样只有记录很少的时候才会有益处,一般我们不会这么做。

为什么不使用hash索引

选择正确的索引非常重要,可以显著提高查询性能。在SQLite中,有三种类型的索引可供选择:B树、B+树和Hash索引。

B树和B+树适用于范围查询和顺序访问。在SQLite中,主键总是使用B+树索引,而其他索引可以使用B树或B+树。

Hash索引适用于等值查询,但不支持范围查询。在SQLite中,Hash索引不常用,因为B树和B+树已经能够很好地满足需求

建立复合索引

在SQLite中,建立复合索引可以显著提高查询性能。复合索引可以覆盖多个列,从而使查询更加高效。

例如,如果有一个包含“FirstName”和“LastName”列的表,并且经常按照这两个列进行查询,那么建立复合索引就非常有用。这可以减少查询所需的时间,因为SQLite可以同时搜索两个列。

删除不必要的索引

删除不必要的索引可以减少查询的时间。如果表的列已经被索引,并且这些索引已经满足了大多数查询需求,那么删除其他索引可能是一个好主意。这将减少查询所需的时间和磁盘空间,并且还可以提高查询性能

Explaining Queryies

我们可以使用那个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没有使用索引

实例2

现在我们看一个复合索引

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信息。

实例3: join

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表中的数据更好,扫描更节省时间。

Statistics

那么优化器是怎么判断数据量多少呢?

对于每一个索引,Sqlite会存储一条如下记录在sqlite_stat1表中

Test,sqlite_autoindex_Test_1,”12 6 3 1”

  1. test: 表名
  2. sqlite_autoindex_test_1: 索引名
  3. “12 6 3 1”: statistics
  • 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-树

你可能感兴趣的:(sqlite,sql,数据库,数据库索引)