看表上有哪些索引用 pragma index_list(表),
看索引用哪几列用 pragma index_info(索引名)。
origin 那一列最有用:c 是手工建的、u 是唯一约束带来的、
pk 是主键——后两种是约束的一部分,删不得。
索引也记在 sqlite_master 里,tbl_name 告诉你它属于哪张表;
自动索引的 sql 列是 NULL。
建完索引,下一步是「确认它真的在、真的被用上」。
这一篇给三处看索引的地方,加一个验证的地方。
排查慢查询的顺序永远是三步:先看表上有没有相关索引(index_list),
再看索引的列对不对(index_info),最后看执行计划走没走(explain)。
三步走完,问题基本就定位了,不用瞎猜。
一、表上有哪些索引:index_list
pragma index_list(big),五列:
序号、索引名、是否唯一(unique)、来源(origin)、是否部分索引(partial)。
origin 那一列最有用:
c 是 create index 建的,u 是唯一约束自动建的,pk 是主键。
三种来源,处理方式完全不同。
所以 origin 是 u 或 pk 的索引,别想着删——
那是约束带来的,删了约束本身就没了。 要动它,得动约束,不是动索引。
partial 是「部分索引」:只给满足条件的行建索引,
比如「只给未删除的行建索引」。 这里显示 0 就是普通索引。
二、索引用了哪些列:index_info
pragma index_info(idx_big_name),三列:
序号、列序号(cid,对应表里的第几列)、列名。
复合索引会给出多行,行序就是索引里的列序。
所以 (city, age) 的索引,第 0 行是 city、第 1 行是 age——
这个顺序就是「最左前缀」里的那个顺序。
Demo 里还查了一个自动索引 sqlite_autoindex_goods_1:
它对应 goods 表上那句 name text unique,所以 origin 是 u,sql 列是 NULL。
这类索引删不掉,也不该删。 它是约束的实现方式——
约束负责「不许重复」,索引负责「快速判断重不重复」,两件事是一体的。
三、索引也记在 sqlite_master 里
select name, tbl_name, sql from sqlite_master where type = 'index',一行一个索引。
sql 列对自动索引是 NULL(因为它不是一句 create index 建的),
对手工建的就存着原句。 所以「忘了当初建的是什么索引」,查这一列就够了。
tbl_name 告诉你它属于哪张表。 这是「索引名全局唯一」却还能知道归属的原因——
名字里看不出,系统表里记着。
type = 'index' 这个条件不能省:sqlite_master 里还堆着表、视图、触发器,
不加条件全查出来会乱。
四、MySQL 那边用 SHOW
show index from 表名;——
一行一个索引列,Key_name 是索引名、Column_name 是列名、Non_unique 是「是否不唯一」。
show create table 表名; 也能看到索引:
KEY idx_name (name) 这种行就是索引定义。 两条一起看,能对上「索引名」和「建它的语句」。
本沙箱没有 show,就用 pragma index_list 加 index_info 对着看。
信息量够,只是要查两次。
MySQL 8 还有几张现成的视图:sys.schema_unused_indexes 列「从没用过的索引」,
information_schema.statistics 是 show index 的原始表。
本沙箱没有这些,只能靠 explain 一条条试。
五、验证「到底走没走索引」:explain
explain query plan 是这一篇里唯一「验证」的动作,前面三处都只是「看有什么」,
它才是「看用没用」。
SEARCH … USING INDEX 是走了,SCAN 是没走。
这一条是索引排查的硬证据,比任何猜测都可靠。
MySQL 那边是 explain select …;,输出十几列,关键看两列:
type 是 all 就是全表扫,ref/range 是走了索引;key 直接写用了哪个索引。
排查顺序建议固定下来:
先 index_list 看有没有、再 index_info 看列对不对、最后 explain 看用没用。
三步都不长,但能省掉大量「我觉得应该是索引问题」的瞎猜。
六、索引太多怎么看出问题
一个信号是「同前缀的索引有好几个」。
(a)、(a, b)、(a, b, c) 三个同时存在时,前两个基本多余——
因为最长的那个能覆盖它们的用途(最左前缀)。 删掉短的能省空间、少拖慢写入。
另一个信号是「从来没被用过的索引」。 建的时候想着「以后可能要用」,结果两年没人用——
它只贡献成本,不贡献收益。
所以「建索引时记一笔为什么建」很值得:
在注释里、在文档里、或者干脆在索引名里带上业务含义。 以后才有依据判断「这个能不能删」。
没记录的索引,谁也不敢删。
七、动手 Demo:三处看索引,一处验索引
Demo 四段:index_list 看清单、index_info 看列(包括一个自动索引)、sqlite_master 看定义、
最后用执行计划验证。
第三段那张表里有一行 sql 是 NULL——那一行就是「约束带来的索引」的样子。
import _q46 as Q
c = Q.blank()
c.execute("CREATE TABLE big (id INTEGER PRIMARY KEY, name TEXT, "
"city TEXT, age INTEGER)")
c.execute("CREATE TABLE goods (id INTEGER PRIMARY KEY, "
"name TEXT UNIQUE)")
c.execute("CREATE INDEX idx_big_name ON big (name)")
c.execute("CREATE INDEX idx_big_city ON big (city)")
# 1) 表上有哪些索引
Q.show(c, "PRAGMA index_list(big)")
# 2) 每个索引用的是哪一列
Q.show(c, "PRAGMA index_info(idx_big_name)")
Q.show(c, "PRAGMA index_info(sqlite_autoindex_goods_1)")
# 3) 索引也记在 sqlite_master 里
Q.show(c, "SELECT name, tbl_name, sql FROM sqlite_master "
"WHERE type = 'index'")
# 4) 到底走没走索引:看执行计划
sql = "SELECT id FROM big WHERE name = '用户30000'"
Q.sqlprint("EXPLAIN QUERY PLAN " + sql)
for row in c.execute("EXPLAIN QUERY PLAN " + sql):
print(" " + row[3])
实跑输出:
--------------------------------------------------------------------
第六十五课:查看索引——有三处能看到
--------------------------------------------------------------------
看表上有哪些索引:index_list
SQL> PRAGMA index_list(big)
seq name unique origin partial
-------------------------------------------------------
0 idx_big_city 0 c 0
1 idx_big_name 0 c 0
(2 行)
**五列:序号、索引名、是否唯一(`unique`)、来源(`origin`)、是否部分索引(`partial`)。**
**`origin` 那一列最有用**:
**`c` 表示 `CREATE INDEX` 建的,`u` 表示唯一约束自动建的,`pk` 表示主键**——
**三种来源,用途不同。**
**所以 `origin` 是 `u` 或 `pk` 的索引,别想着删——那是约束带来的,删了约束就没了。**
看索引用了哪些列:index_info
SQL> PRAGMA index_info(idx_big_name)
seqno cid name
------------------------
0 1 name
(1 行)
**三列:序号、列序号(`cid`,对应表里的第几列)、列名。**
**复合索引会给出多行,顺序就是索引里的顺序。**
SQL> PRAGMA index_info(sqlite_autoindex_goods_1)
seqno cid name
------------------------
0 1 name
(1 行)
**`goods` 表上这个索引不是 `create index` 建的**——
**它是建表时那句 `name TEXT UNIQUE` 自动带来的。**
**所以 `sqlite_master` 里它的 `sql` 列是 NULL。**
**这类索引删不掉,也不该删。** **它的 `origin` 是 `u`,意思是「跟着约束来的」。**
索引也记在 sqlite_master 里
SQL> SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'index'
name tbl_name sql
----------------------------------------------------------------------------------
idx_big_name big CREATE INDEX idx_big_name ON big (name)
idx_big_city big CREATE INDEX idx_big_city ON big (city)
sqlite_autoindex_goods_1 goods NULL
(3 行)
**`sql` 列对自动索引是 NULL**(因为它不是一句 `create index` 建的),
**对 `CREATE INDEX` 建的则存着原句。** **`tbl_name` 告诉你它属于哪张表**——
**这就是「索引名全局唯一」却还能知道归属的原因。**
MySQL 那边的两条语句
**`SHOW INDEX FROM 表名;`**——
**一行一个索引列,`Key_name` 是索引名、`Column_name` 是列名、`Non_unique` 是「是否不唯一」。**
**`SHOW CREATE TABLE 表名;`** 也能看到索引:
**`KEY idx_name (name)` 这种行就是索引定义。**
**两条一起看,能对上「索引名」和「建它的语句」。**
**本沙箱没有 `SHOW`,就用 `PRAGMA index_list` 加 `index_info` 对着看。**
**信息量够,只是要查两次。**
验证「到底走没走索引」:EXPLAIN QUERY PLAN
SQL> EXPLAIN QUERY PLAN SELECT id FROM big WHERE name = '用户30000'
SEARCH big USING COVERING INDEX idx_big_name (name=?)
**`SEARCH … USING INDEX` 是走了;`SCAN` 是没走。** **这一条是索引排查的唯一硬证据**——
**别靠「感觉快了」判断,看执行计划。**
**MySQL 那边是 `EXPLAIN SELECT …;`**,输出更宽(十几列),**关键看 `type` 和 `key` 两列**:
**`type` 是 `ALL` 就是全表扫,`ref`/`range` 是走了索引;`key` 那一列直接写用了哪个索引。**
**第 12 章这一篇给的方法,在 MySQL 上一样能用,只是语句不同。**
索引太多怎么看出问题
**一个信号是「同前缀的索引有好几个」**:
**`(a)`、`(a, b)`、`(a, b, c)` 三个索引同时存在时,前两个基本是多余的**——
**因为最长的那个能覆盖它们的用途。** **删掉短的能省空间、少拖慢写入。**
**另一个信号是「从来没被用过的索引」**:**MySQL 8 的 `sys.schema_unused_indexes` 能列出来**,
**本沙箱没有这个视图,只能靠 `EXPLAIN` 一条条试。**
**所以「建索引时记一笔为什么建」,以后才有依据删。**
第一段两行:idx_big_city、idx_big_name,origin 都是 c。
第二段两个索引各一列:idx_big_name 是 name,自动索引也是 name(来自 unique)。
第三段三行:两个 create index 建的带原句,自动索引那行 sql 是 NULL。
第四段一条 SEARCH:USING COVERING INDEX idx_big_name。
30 秒实验
pragma index_list(big)里partial全是 0,这个值是干什么用的?- 为什么自动索引的
sql列是 NULL?它到底有没有「建它的语句」? (a)、(a, b)、(a, b, c)三个索引并存,删哪几个不影响性能?
常见误区
- 以为
index_list能看到「索引用了哪些列」。 它只给名字,列要用index_info另查。 - 看到
sql是 NULL 以为是坏数据。 那是约束自动建的索引,本来就没有create index语句。 - 靠「感觉快」判断索引生效。 只有执行计划能证明,
SCAN就是没用上。
自测三问(点开对答案)
1. `origin` 的三个值分别是什么意思? —— `c` 是 `create index` 手工建的,`u` 是唯一约束自动建的,`pk` 是主键带来的。 后两种是约束的一部分,删不得。 2. 想知道复合索引的列顺序,看哪个? —— `pragma index_info(索引名)`,返回的行序就是索引的列序,也就是最左前缀里的顺序。 3. 自动索引为什么删不掉? —— 它是 `UNIQUE` 或主键约束的实现方式,删了约束就失效。要动只能动约束本身。会看也会验了,下一篇讲最后一件:12.4 删除索引——删对了省空间,删错了掉性能。
- ☐ (本篇完,下一篇 12.4 删除索引)