索引基本原理
B-Tree 索引把索引列排序后建成树结构,让数据库可以用很少的 I/O 定位到目标行,而不必扫描整表。代价是每次 DML 都要维护索引,所以索引不是越多越好。
- 适合建索引:高选择性(不同值多)的列、常用于 WHERE/JOIN/ORDER BY 的列
- 不适合:性别这类只有几个值的列(此时位图索引可能更合适)
- 复合索引遵循最左前缀:索引 (a, b) 能用于 a 或 a+b,不能单独用于 b
- 索引列上使用函数或隐式类型转换会导致索引失效
-- 普通索引:employees.department_id 在 HR 方案里已有 emp_department_ix,
-- 直接再建会报 ORA-01408,所以换个没有索引的列演示
CREATE INDEX idx_emp_salary ON employees (salary);
-- 复合索引:注意列顺序,把等值条件列放前面
CREATE INDEX idx_emp_dept_job ON employees (department_id, job_id);
-- 唯一索引:email 上已有 emp_email_uk,这里用组合列演示
CREATE UNIQUE INDEX uk_emp_email_dept ON employees (email, department_id);
-- 函数索引:解决「列上用函数导致索引失效」
CREATE INDEX idx_emp_hire_year ON employees (TRUNC(hire_date, 'YYYY'));
-- 查看索引信息
SELECT index_name, index_type, uniqueness, status
FROM user_indexes WHERE table_name = 'EMPLOYEES';只作示例阅读:5 行写入限制下只开放单独创建普通空表;CTAS、删表、清空表、改结构、触发器和存储程序不允许执行。请在独立学习数据库中练习。
看懂执行计划
- TABLE ACCESS FULL:全表扫描,行数大且无可用索引时出现
- INDEX RANGE SCAN:索引范围扫描,通常是我们想要的
- INDEX UNIQUE SCAN:唯一索引等值查询,效率最高
- NESTED LOOPS:小结果集驱动大表时效果好
- HASH JOIN:大表之间连接,通常出现在数据仓库场景
- 看计划先看预估行数(Rows)和实际行数差别,差得多说明统计信息过期
EXPLAIN PLAN FOR
SELECT e.last_name, d.department_name
FROM employees e
JOIN departments d ON d.department_id = e.department_id
WHERE e.department_id = 60;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- 更实用:查看真实执行统计(需要实际执行语句)
SELECT /*+ GATHER_PLAN_STATISTICS */
e.last_name, d.department_name
FROM employees e
JOIN departments d ON d.department_id = e.department_id
WHERE e.department_id = 60;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST'));只作示例阅读:受限模式只开放查询、常规增删改及单独创建空表。请在独立学习数据库中练习。
索引失效的常见写法
-- ✗ 列上使用函数
SELECT * FROM employees WHERE TO_CHAR(hire_date, 'YYYY') = '2005';
-- ✓ 改成范围条件,或建函数索引
SELECT * FROM employees
WHERE hire_date >= DATE '2005-01-01' AND hire_date < DATE '2006-01-01';
-- ✗ 隐式类型转换(employee_id 是 NUMBER,传字符串)
SELECT * FROM employees WHERE employee_id = '100';
-- ✓ 传正确类型
SELECT * FROM employees WHERE employee_id = 100;
-- ✗ 前导通配符无法用索引
SELECT * FROM employees WHERE last_name LIKE '%mith';
-- ✓ 后置通配符可以用索引
SELECT * FROM employees WHERE last_name LIKE 'Sm%';
-- ✗ 对索引列做运算
SELECT * FROM employees WHERE salary * 12 > 100000;
-- ✓ 把运算移到右边
SELECT * FROM employees WHERE salary > 100000 / 12;在你的专用沙箱里执行打开演练场
统计信息
-- 表与索引一起收集
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(
ownname => USER,
tabname => 'EMPLOYEES',
cascade => TRUE, -- 连带收集索引统计
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
degree => 4
);
END;
/
-- 查看统计信息的新鲜度
SELECT table_name, num_rows, last_analyzed, stale_stats
FROM user_tab_statistics
WHERE table_name = 'EMPLOYEES';只作示例阅读:5 行写入限制下不执行任意 PL/SQL、循环写入或动态 SQL;只开放字面量 DBMS_OUTPUT 打印。请在独立学习数据库中练习。