目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

索引

存储过程生成100W数据

1.创建数据库oldboy

mysql> create database oldboy;

2.创建存储过程,并且创建100W条数据use oldboy

use oldboy

create table t100w (id int,num int,k1 char(2),k2 char(4),dt timestamp) charset utf8mb4 collate utf8mb4_bin;

delimiter //
create  procedure rand_data(in num int)
begin
declare str char(62) default 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
declare str2 char(2);
declare str4 char(4);
declare i int default 0;
while i<num do
set str2=concat(substring(str,1+floor(rand()*61),1),substring(str,1+floor(rand()*61),1));
set str4=concat(substring(str,1+floor(rand()*61),2),substring(str,1+floor(rand()*61),2));
set i=i+1;
insert into t100w values (i,floor(rand()*num),str2,str4,now());
end while;
end;
//
delimiter ;

插入100w条数据:
call rand_data(1000000);
commit;

压力测试

[root@localhost ~]# mysqlslap --defaults-file=/etc/my.cnf \
> --concurrency=100 --iterations=1 --create-schema='oldboy' \
> --query="select * from oldboy.t100w where k2='xyMN'" engine=innodb \
> --number-of-queries=2000 -uroot -p123 -verbose
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
	Running for engine rbose
	Average number of seconds to run all queries: 339.064 seconds
	Minimum number of seconds to run all queries: 339.064 seconds
	Maximum number of seconds to run all queries: 339.064 seconds
	Number of clients running queries: 100
	Average number of queries per client: 20


索引的作用

提供类似于书中目录的作用,目的是为了优化查询

索引的种类(算法)

  • B树索引
  • Hash索引
  • R树索引
  • Full text
  • GIS

B树索引的算法

Mysql现在用的是B*Tree。

辅助索引(S)构建B树结构

Secondary Index

(1)辅助索引是基于表的列进行生成的
(2)取出索引列的所有值(取出所有键值),临时表中存储
(3)进行所有键值的排序
(4)将所有的键值按顺序落到BTree索引的叶子节点上
(5)进而生成枝节点和根节点
(6)叶子节点除了存储键值之外,还存储了相邻叶子节点的指针,另外还会保存原表数据的指针

聚集索引(C)构建B树结构

Cluster Index

(1) 建表时有主键列(ID)
(2) 表中进行数据存储,会按照ID列的顺序,有序的存储一行一行的数据到数据页上(这个动作叫做聚集索引组织表)
(3) 表的数据页被作为聚集索引的叶子节点
(4) 把叶子节点的主键值生成上层枝节点和根节点。

聚集索引和辅助索引构成区别总结

聚集索引只能有一个,非空唯一,一般时主键
辅助索引,可以有多个,是配合聚集索引使用的
聚集索引叶子节点,就是磁盘的数据行存储的数据页
MySQL是根据聚集索引,组织存储数据,数据存储时就是按照聚集索引的顺序进行存储数据
辅助索引,只会提取索引键值,进行自动排序生成B树结构

辅助索引细分

  • 联合多列辅助索引(覆盖索引)
  • 单列的辅助索引
  • 唯一索引

索引树的高度

关于索引树的高度受什么影响?

(1)数据行多, 分表
(2)索引列字符长度 ,前缀索引
(3)char varchar ,表设计
(4)enum 优化索引高度,能用则用。

索引的管理

辅助索引:单列的辅助索引

# 查看t100w表的结构
mysql> desc t100w;
+-------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type      | Null | Key | Default           | Extra                       |
+-------+-----------+------+-----+-------------------+-----------------------------+
| id    | int(11)   | YES  |     | NULL              |                             |
| num   | int(11)   | YES  |     | NULL              |                             |
| k1    | char(2)   | YES  |     | NULL              |                             |
| k2    | char(4)   | YES  |     | NULL              |                             |
| dt    | timestamp | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-----------+------+-----+-------------------+-----------------------------+
5 rows in set (0.00 sec)

# 查看t100w表在系统中占用的空间 (查询条件为k2)
[root@localhost ~]# ll -h /data/mysql/data/oldboy/
total 57M
-rw-r-----. 1 mysql mysql   65 Apr 28 22:03 db.opt
-rw-r-----. 1 mysql mysql 8.5K Apr 28 22:06 t100w.frm
-rw-r-----. 1 mysql mysql  56M Apr 28 22:24 t100w.ibd

# 并发100,2000次查询消耗时间 339.064 seconds
[root@localhost ~]# mysqlslap --defaults-file=/etc/my.cnf \
--concurrency=100 --iterations=1 --create-schema='oldboy' \
--query="select * from oldboy.t100w where k2='xyMN'" engine=innodb \
--number-of-queries=2000 -uroot -p123 -verbose
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
	Running for engine rbose
	Average number of seconds to run all queries: 339.064 seconds
	Minimum number of seconds to run all queries: 339.064 seconds
	Maximum number of seconds to run all queries: 339.064 seconds
	Number of clients running queries: 100
	Average number of queries per client: 20

# 建立索引 (会锁表,短暂的锁表,最好在业务不繁忙的时候建立索引)
mysql> alter table t100w add index idx_k2(k2);
Query OK, 0 rows affected (1.41 sec)
Records: 0  Duplicates: 0  Warnings: 0

# 再次进行压测
[root@localhost ~]# mysqlslap --defaults-file=/etc/my.cnf \
> --concurrency=100 --iterations=1 --create-schema='oldboy' \
> --query="select * from oldboy.t100w where k2='xyMN'" engine=innodb \
> --number-of-queries=2000 -uroot -p123 -verbose
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
	Running for engine rbose
	Average number of seconds to run all queries: 0.488 seconds
	Minimum number of seconds to run all queries: 0.488 seconds
	Maximum number of seconds to run all queries: 0.488 seconds
	Number of clients running queries: 100
	Average number of queries per client: 20

# 优化效果大约在800倍左右
有无索引查询效果很明显

# 查看表中的索引(MUL是辅助索引)
mysql> desc t100w;
+-------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type      | Null | Key | Default           | Extra                       |
+-------+-----------+------+-----+-------------------+-----------------------------+
| id    | int(11)   | YES  |     | NULL              |                             |
| num   | int(11)   | YES  |     | NULL              |                             |
| k1    | char(2)   | YES  |     | NULL              |                             |
| k2    | char(4)   | YES  | MUL | NULL              |                             |
| dt    | timestamp | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-----------+------+-----+-------------------+-----------------------------+
5 rows in set (0.00 sec)

# 更详细的查看表中的索引
mysql> show index from t100w\G
*************************** 1. row ***************************
        Table: t100w
   Non_unique: 1
     Key_name: idx_k2
 Seq_in_index: 1
  Column_name: k2
    Collation: A
  Cardinality: 3880
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE
      Comment: 
Index_comment: 
1 row in set (0.00 sec)

辅助索引:唯一索引

列中的数据不能重复

# 判断想建索引的列中有没有重复的数据:使用distinct,对比两次的查询行数是否一致。
mysql> select count(k1) from t100w;
+-----------+
| count(k1) |
+-----------+
|   1000000 |
+-----------+
1 row in set (0.29 sec)

mysql> select count(distinct(k1)) from t100w;
+---------------------+
| count(distinct(k1)) |
+---------------------+
|                3721 |
+---------------------+
1 row in set (0.58 sec)


# 当然,分组统计也可以,大于1的就代表重复。
mysql> select k1,count(k1) from t100w group by k1 having count(k1)>1 order by count(k1) desc limit 5;
+------+-----------+
| k1   | count(k1) |
+------+-----------+
| OH   |       328 |
| K4   |       326 |
| A3   |       322 |
| kd   |       319 |
| N6   |       318 |
+------+-----------+
5 rows in set (0.56 sec)

# 其实直接创建唯一索引也行,因为如果有重复的行,创建会失败,哈哈。
mysql> alter table t100w add unique index idx_k1(k1);
ERROR 1062 (23000): Duplicate entry 'pi' for key 'idx_k1'

辅助索引:前缀索引

# 建立前缀索引:前5个字符(从左到右)
mysql> alter table city add index idx_name(name(5));
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

辅助索引:联合索引

(1) where A、GROUP BY B、ORDER BY C ---> (A,B,C)
(2) where A B C
(2.1) 都是等值 ,在5.5 以后无关索引顺序,创建索引的原则:唯一值多的列放在联合索引的最左侧
(2.2) 如果有不等值,例如以下情况
select where A= and B> and C=
索引顺序,ACB ,语句改写为 ACB

# 创建联合索引
mysql> alter table city add index idx_co_po(countrycode,population);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

删除索引

# 删除city表中的idx_co_po索引
mysql> alter table city drop index idx_co_po;
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

执行计划

作用

  • 上线新的查询语句之前,进行提前预估语句的性能
  • 在出现性能问题时,找到合理的解决思路

获取执行计划(desc = explain 功能一样)

mysql> desc  select * from oldboy.t100w where k2='EF12'\G
mysql> explain  select * from oldboy.t100w where k2='EF12'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t100w
   partitions: NULL
         type: ref
possible_keys: idx_k2
          key: idx_k2
      key_len: 17
          ref: const
         rows: 257
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

# 解析
table: t100w	       # 多表查询的时候,特别有用
type: ref              # 索引的应用级别
possible_keys: idx_k2  # 可能会使用到的索引  
key: idx_k2	       # 实际上使用的索引
key_len: 17 	       # 联合索引覆盖长度
rows: 293	       # 查询的行数(越少越好)
Extra: NULL	       # 额外的信息




type

索引的应用级别

ALL : 全表扫描,不走索引

# 没建立索引!!

# 建立索引不走的()!!!!
mysql> desc select * from t100w;
mysql> desc select * from t100w where k1='aa';	# k2做的索引列,拿k1去查询,当然走不了索引
mysql> desc select * from t100w where k2 != 'aaaa'; # 对于辅助索引来说,不等于是不走索引的
mysql> desc select * from t100w where k2 like '%xt%'; # 模糊查找不走索引。 除非'xt%'才走索引。

Index :全索引扫描

mysql> desc select k2 from t100w;

range :索引范围扫描

# 辅助索引 : > < >= <= like ,   in   or
mysql> desc select * from world.city where countrycode like 'C%'
mysql> desc select * from world.city where countrycode in ('CHN','USA');
改写为:
desc
select * from world.city where countrycode='CHN'
union all 
select * from world.city where countrycode='USA';

# 主键: !=
mysql> desc select * from world.city where id!=3000;
mysql> desc select * from world.city where id>3000;

ref : 辅助索引等值查询

mysql> desc select * from city where countrycode='CHN'

eq_ref :在多表连接查询是on的条件列是唯一索引或主键

# 左边 ALL  右边 eq_ref
mysql> desc select a.name,b.name ,b.surfacearea 
from city as a 
join country as b 
on a.countrycode=b.code 
where a.population <100;

const,system : 主键或唯一键等值查询

mysql> DESC SELECT * from city where id=10

null:查询不到数据的时候,就是性能最好的时候

mysql> desc select * from city where id=1000000000;

Extra

额外信息

# Using filesort 
mysql> desc select * from city where countrycode="CHN" order by population\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: city
   partitions: NULL
         type: ref
possible_keys: CountryCode
          key: CountryCode
      key_len: 12
          ref: const
         rows: 363
     filtered: 100.00
        Extra: Using index condition; Using filesort
1 row in set, 1 warning (0.00 sec)

# where 和 orderby 同时出现需要使用联合索引进行优化
mysql> alter table city add index idx_co_po(countrycode,population);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

# 再次查询发现 Extra 中已经没有 Using filesort
mysql> desc select * from city where countrycode="CHN" order by population\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: city
   partitions: NULL
         type: ref
possible_keys: CountryCode,idx_co_po
          key: idx_co_po
      key_len: 12
          ref: const
         rows: 363
     filtered: 100.00
        Extra: Using index condition
1 row in set, 1 warning (0.00 sec)

explain(desc)使用场景

公司业务慢,请你从数据库的角度分析原因:

(1)应急性的慢:突然夯住

处理过程:
1.show processlist;  获取到导致数据库hang的语句
2. explain 分析SQL的执行计划,有没有走索引,索引的类型情况
3. 建索引,改语句

(2)一段时间慢(持续性的):

1. 记录慢日志slowlog,分析slowlog,获取到导致数据库slow的语句
2. explain 分析SQL的执行计划,有没有走索引,索引的类型情况
3. 建索引,改语句

索引管理原则

为了使索哪些引的使用效率更高,在创建索引时,必须考虑在字段上创建索引和创建什么类型的索引。

  • 建表时一定要有主键,一般是个无关列

  • 选择唯一性索引

    唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
    例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。
    如果使用姓名的话,可能存在同名现象,从而降低查询速度。
    
    优化方案:
    (1) 如果非得使用重复值较多的列作为查询条件(例如:男女),可以将表逻辑拆分
    (2) 可以将此列和其他的查询类,做联和索引
    select count(*) from world.city;
    select count(distinct countrycode) from world.city;
    select count(distinct countrycode,population ) from world.city;
    
    
  • 为经常需要where 、ORDER BY、GROUP BY,join on等操作的字段是创建索引,排序操作会浪费很多时间。

where  A B C      ----》 A  B  C
in 
where A   group by B  order by C
A,B,C

where A、GROUP BY B、ORDER BY C
1. 联合索引 
2. (A,B,C) 

如果为其建立索引,优化查询
注:如果经常作为条件的列,重复值特别多,可以建立联合索引。
  • 尽量使用前缀来索引:如果索引字段的值很长,最好使用值的前缀来索引。
  • 限制索引的数目:索引的数目不是越多越好。
可能会产生的问题:	percona-toolkit中有个工具,专门分析索引是否有用
(1) 每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。
(2) 修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。
(3) 优化器的负担会很重,有可能会影响到优化器的选择.
  • 删除不再使用或者很少使用的索引(percona toolkit):pt-duplicate-key-checker
表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理
员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
  • 大表加索引,要在业务不繁忙期间操作

  • 尽量少在经常更新值的列上建索引

不走索引的情况

  • 没有查询条件,或者查询条件没有建立索引
select * from tab;       全表扫描。
select  * from tab where 1=1;
  • 查询结果集是原表中的大部分数据,应该是25%以上。(查询的结果集,超过了总数行数25%,优化器觉得就没有必要走索引了。)

  • 索引本身失效,统计数据不真实

索引有自我维护的能力。
对于表内容变化比较频繁的情况下,有可能会出现索引失效。
一般是删除重建

现象:
有一条select语句平常查询时很快,突然有一天很慢,会是什么原因
select?  --->索引失效,,统计数据不真实
DML ?   --->锁冲突
  • 查询条件使用函数在索引列上,或者对索引列进行运算,运算包括(+,-,*,/,! 等)
错误的例子:select * from test where id-1=9;
正确的例子:select * from test where id=10;
算术运算
函数运算
子查询
  • 隐式转换导致索引失效.

  • <> ,not in 不走索引(辅助索引)

  • like "%_" 百分号在最前面不走

EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '31%'  走range索引扫描
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '%110'  不走索引
%linux%类的搜索需求,可以使用elasticsearch+mongodb 专门做搜索服务的数据库产品

作者:Soulboy