|
索引的使用
首先建立库之后,并创建表,表结构如下:
mysql> createdatabase test1;
Query OK, 0 rowsaffected (0.01 sec)
mysql> use test1;
Database changed
mysql> createtable yw (
->> -> c1 int not null default '0',
-> c2 int not null default '0',
-> c3 int not null default '0',
-> c4 int not null default '0',
-> c5 timestamp not null,
-> c6 varchar(200) not null default '',
-> primary key(id)
-> );
Query OK, 0 rowsaffected (0.01 sec)
导入sql文件
内容如下
[root@mysql_node1test]# cat suoyin_test.sql
drop table yw; #已将刚才创建的库删除了,然后又重新创建了一个库
create table yw (
id int unsigned notnull primary key auto_increment,
c1 int not nulldefault '0',
c2 int not nulldefault '0',
c3 int not nulldefault '0',
c4 int not nulldefault '0',
c5 timestamp notnull,
c6 varchar(200) notnull default ''
);
delimiter $$
drop procedure ifexists `insert_yw` $$
create procedure`insert_yw`(in row_num int )
begin
declare i int default 0;
while i < row_num do
insert into yw(c1, c2, c3,c4, c5,c6) values(floor(rand()*row_num),floor(rand()*row_num),floor(rand()*row_num),floor(rand()*row_num),now(),repeat('wubx', floor(rand()*20)));
set i = i+1;
END while;
end$$
delimiter ;
#插入300W条数据
callinsert_yw(3000000);
delimiter $$
drop procedure ifexists `update_yw` $$
create procedure`update_yw`(in row_num int )
begin
declare i int default 0;
while i < row_num do
update yw set c3= floor(rand()*row_num) whereid=i;
set i = i+1;
END while;
end$$
delimiter ;
更改参数
mysql> set globalinnodb_flush_log_at_trx_commit=2
导入数据表
mysql> source/root/test/suoyin.sql
Query OK, 0 rowsaffected (0.11 sec)
Query OK, 0 rowsaffected (0.01 sec)
Query OK, 0 rowsaffected (0.00 sec)
Query OK, 0 rowsaffected (0.00 sec)
Query OK, 1 row affected(4 min 20.75 sec)
Query OK, 0 rowsaffected (0.00 sec)
Query OK, 0 rowsaffected (0.00 sec)
我们会发现导入很慢,当然300W条数据也不小,所以我们的问题来了:
为什么这个查询这么慢?
mysql> select *from yw a, (select c2 from yw where> +---------+---------+--------+---------+---------+---------------------+------------------------------------------------------------------+--------+
|> +---------+---------+--------+---------+---------+---------------------+------------------------------------------------------------------+--------+
| 10 | 2833881 | 185188 | 1424297 | 565924 | 2014-09-24 14:30:31 |wubxwubxwubxwubxwubxwubxwubxwubxwubxwubx | 185188 |
| 1530223 | 1345871 |185188 | 2888330 | 1886085 | 2014-09-24 14:32:44 | wubxwubxwubxwubxwubx |185188 |
| 1623964 | 1289414 |185188 | 57699 | 2732932 | 2014-09-2414:32:52 | wubxwubxwubxwubxwubxwubxwubxwubxwubx | 185188 |
| 2825263 | 729557 | 185188 | 1737273 | 2130798 |2014-09-24 14:34:37 | wubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubx| 185188 |
+---------+---------+--------+---------+---------+---------------------+------------------------------------------------------------------+--------+
4 rows in set (7.28 sec)
经过最后查看,显示的是7.28秒执行完成,一个很简单的查询但是执行完后会很慢,
那么这里我们看到一个2825263, 那么我们将sql改为一个简单的sql并查看
这是一个非常简单的sql,如果在有索引的300w的数据,应该是非常快的,但实际上的表结构跑这样的sql还是很慢的,如下所示,总共用了7.96秒,如下所示:
mysql> select * from yw where c1 = 2825263 ;
+---------+---------+---------+---------+---------+---------------------+------------------------------------------------------+
|> +---------+---------+---------+---------+---------+---------------------+------------------------------------------------------+
| 1421241 | 2825263 |2015825 | 1603339 | 1969218 | 2014-09-24 14:32:35 |wubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubx |
+---------+---------+---------+---------+---------+---------------------+------------------------------------------------------+
1 row in set (7.96 sec)
之所以慢,是因为在全表扫描而造成的
这种情况下可以对其进行添加索引进行优化
再次追加300万条数据
使用call insert_表名 进行添加
mysql> call insert_yw(3000000);
Query OK, 1 rowaffected (4 min 21.74 sec)
大概在7分钟将索引添加将300万条数据加载完毕
PS:在生产环境中都要模拟百万条的数据去进行测试
创建完后查看索引大小,大概476M左右
[root@mysql_node1test1]# ll -th
总用量 477M
-rw-rw----. 1 mysqlmysql 476M 9月 24 14:49 yw.ibd
-rw-rw----. 1 mysqlmysql 8.6K 9月 24 14:30 yw.frm
-rw-rw----. 1 mysqlmysql 61 9月 24 14:00 db.opt
mysql> desc select* from yw a, (select c2 from yw where> +----+-------------+------------+--------+---------------+---------+---------+------+---------+-------------+
|> +----+-------------+------------+--------+---------------+---------+---------+------+---------+-------------+
| 1 | PRIMARY | | system | NULL | NULL | NULL | NULL | 1 | |
| 1 | PRIMARY | a | ALL | NULL | NULL | NULL | NULL | 5936589 | Using where |
| 2 | DERIVED | yw | const | PRIMARY | PRIMARY | 4 | | 1 | |
+----+-------------+------------+--------+---------------+---------+---------+------+---------+-------------+
3 rows in set (0.00sec)
索引的使用
索引的简介
索引实际上是Btree结构
有些生产环境上尤其是在主从环境下用不到索引的,从而使得主从延迟,当发现从库延迟,要先去定位是否是从库上有sql写入的时间是否没有用到索引,如果是的话则加索引即可
这类情况在排查主从结构的时候特别多
而delete也是支持索引的,如果不进行索引,那么也会进行全表扫描
比如在某场景下我们要批量删除大量数据,通常建议使用工具或存储过程去分段(批量)删除数据,比如:
deletefrom tb where addtime&get;xxxx and addtime renametable yw to yw_1;
ERROR 2006 (HY000):MySQL server has gone away
No connection. Tryingto reconnect...
Connection> Current database:test1
Query OK, 0 rowsaffected (0.11 sec)
mysql> showtables;
+-----------------+
| Tables_in_test1 |
+-----------------+
| yw_1 |
+-----------------+
1 row in set (0.00sec)
新建表
create table yw (
> c1 int not null default '0',
c2 int not null default '0',
c3 int not null default '0',
c4 int not null default '0',
c5 timestamp not null,
c6 varchar(200) not null default '',
primary key(`id`),
KEY `idx_c2`(`c2`),
key `idx_c3`(`c3`)
);
Query OK, 0 rowsaffected (0.03 sec)
查看表结构
mysql> desc yw;
+-------+------------------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+-------------------+-----------------------------+
|> | c1 | int(11) | NO | | 0 | |
| c2 | int(11) | NO | MUL | 0 | |
| c3 | int(11) | NO | MUL | 0 | |
| c4 | int(11) | NO | | 0 | |
| c5 | timestamp | NO | | CURRENT_TIMESTAMP | onupdate CURRENT_TIMESTAMP |
| c6 | varchar(200) | NO | | | |
+-------+------------------+------+-----+-------------------+-----------------------------+
7 rows in set (0.07sec)
[root@mysql_node1test]# cat 2.sql
delimiter $$
drop procedure ifexists `update_yw` $$
create procedure`update_yw`(in row_num int )
begin
declare i int default 0;
while i < row_numdo
update yw set c3=floor(rand()*row_num) where> set i = i+1;
END while;
end$$
delimiter ;
导入
mysql> source/root/test/2.sql
Query OK, 0 rowsaffected (0.00 sec)
Query OK, 0 rowsaffected (0.00 sec)
再次插入300W条记录,查看用时时间
mysql> callinsert_yw(3000000);
Query OK, 1 rowaffected (8 min 11.57 sec)
将之前备份的表还原并再次执行,这里插入一百万条数据
mysql> renametable yw to yw_idx;
Query OK, 0 rowsaffected (0.06 sec)
mysql> renametable yw_1 to yw;
Query OK, 0 rowsaffected (0.01 sec)
mysql> showtables;
+-----------------+
| Tables_in_test1 |
+-----------------+
| yw |
| yw_idx |
+-----------------+
2 rows in set (0.00sec)
这样一个是带索引,一个是不带索引的
再次调用包含索引的结构
mysql> call update_yw(3000000);
Query OK, 1 rowaffected (4 min32.31 sec)
与之对比如下:
表名
是否所用索引
执行过程所耗时间
yw
否
11.57 sec
yw_idx
是
32.31 sec
两者间速度相差3倍左右
如果存在c3的索引的话,那么执行以下sql语句:
select c3 from yw where id=1;
发现同样是很慢的,因为在发生更新第三列的时候同时需要做索引的维护
索引同样是Btree结构,如果发生任何变更的时候,会将Btree更新,重新排序,这样就会重新开销所以会慢
MySQL支持什么样的索引?
一般来讲都5类
普通索引,唯一索引,主建,组合索引,全文索引(mysql5.6的特性)
全文搜索第三方工具:sphinx
创建索引:
create index> 更新索引:
alter table tb add indexidx_xxx(xxx);
删除索引:
DROP [ONLINE|OFFLINE]INDEX index_name ON tbl_name
普通索引一般包含前缀索引,如果前端部分很长可以建立前缀索引(前字符区分开来,减少一下索引的长度,让扫起来更省点IO),如下所示:
alter table yw add indxidx_c6_6(c6(6));
普通索引是的列是可以NULL的
唯一索引:
在设计中属于一种约束,在使用中,设置字段唯一的,或者是联合索引
例:
select * from table_nameidx_xxx=xxx;
如果在普通索引中,在记录中探测到下下条再判断是否是需要的记录,如果是则返回,所以普通索引是要往下多度几次,这是普通索引的开销
但唯一索引,只做等于匹配,不会再往下进行,其好处是比较节省IO,
唯一索引列可以允许有NULL,但只能有一个
主建
Innodb里聚集class index key以为所有的数据以主建排序存储
主建是不允许有null列的
组合索引(联合索引):
也被称为
select * from yw where c4=XXXX order by c3;
使用explain查看执行性能
mysql> explainselect * from yw_idx where c3=251609 order by c4;
+----+-------------+--------+------+---------------+--------+---------+-------+------+-----------------------------+
|> +----+-------------+--------+------+---------------+--------+---------+-------+------+-----------------------------+
| 1 | SIMPLE | yw_idx | ref |> +----+-------------+--------+------+---------------+--------+---------+-------+------+-----------------------------+
1 row in set (0.03sec)
实际执行sql还是很慢,key_len为4,但是还会很慢,这种sql是忽悠人的,但是实际上possible_keys里面是没有东西的,这属于一种欺骗性的所以需要注意
使用where条件判断
如果是前缀索引如果用到了c3 是否还可以继续调用c4字段
select * from yw where c3=xxx or c4=xxxx;
select * from yw where c3=xxx union all select * from yw where c4=xxxx;
可以看到是不能调用的,因为在这个条件里面,c3是可以用到的,而c4是不行的,因为c4是全表扫描的,如有一个地方需要全表扫描的话,那么不管如何都是需要全表扫描,这也是mysql的一个特性
如果是独立的字段,将c3和c4独立出来,则可以使用索引
以下是没有意义的索引
select count(*) from yw group by c3, c4;
使用两个字段独立索引都被进行调用
使用多索引合并
在5.5版本以上可以使用union 进行多索引合并
mysql> select *from yw where c3=xxx union all select * from yw where c4=xxxx;
这样c3 c4都有索引 ,这样的话sql是非常快的
如果使用以下sql语句:
mysql> selectcount(*) from yw group by c3, c4;
2999999 rows in set(57.26 sec)
虽然会用到索引,但是还是会全表扫描,因为扫描的IO过大,用到索引意义也不是很大
如果看到结果集超过一万行,都可以认为这个sql是可以杀掉了
只要结果集超过1万行(OLTP)环境,都可以认为这个SQL是有问题的
所以,最好控制结果集查询大小超过500,这样就可以避免过大全表扫描,避免IO过高
使用limit
如果我们为其后面加入limit 10 来查看效果
mysql> explainselect count(*) from yw_idx group byc3,c4 order by> +----+-------------+--------+------+---------------+------+---------+------+---------+---------------------------------+
|> +----+-------------+--------+------+---------------+------+---------+------+---------+---------------------------------+
| 1 | SIMPLE | yw_idx | ALL | NULL | NULL | NULL | NULL | 3016886 | Using temporary; Usingfilesort |
+----+-------------+--------+------+---------------+------+---------+------+---------+---------------------------------+
1 row in set (0.00sec)
速度并没有提升,这种sql在生产环境也是较多
覆盖索引是能够查到数据
在生产中,一个表的查询是能够数过来的,但是非常小的业务系统非常复杂
例:
Create fulltext indexidx_xxx on TbName(xxxx);
select * from tbwhere match(xxxx) against(‘wubx’);
使用索引中注意的事项
首先创建表结构:
mysql> createtable tb_1 (
-> id int unsigned not null auto_increment,
-> c1 varchar(200) default null ,
-> c2 int not null,
-> primary key (id)
-> );
Query OK, 0 rowsaffected (0.06 sec)
插入数据
mysql> insert intotb_1(c1, c2) values(NULL,1),(1,2),(NULL,3);
Query OK, 3 rowsaffected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
查看每列的数据量
mysql> selectcount(c1) ,count(*) ,count(1), count(c2),count(id) from tb_1;
+-----------+----------+----------+-----------+-----------+
| count(c1) |count(*) | count(1) | count(c2) | count(id) |
+-----------+----------+----------+-----------+-----------+
| 1 | 3 | 3 | 3 | 3 |
+-----------+----------+----------+-----------+-----------+
1 row in set (0.00sec)
mysql> select *from tb_1;
+----+------+----+
|> +----+------+----+
| 1 | NULL | 1 |
| 2 | 1 | 2 |
| 3 | NULL | 3 |
+----+------+----+
3 rows in set (0.00sec)
这里面观察出,null是不被统计的,而且null在设计字段里如果需要的,需要多一个字节去标示,所以需要多占用一个标示位
所以我们需要注意的是:
1.索引不会包含有NULL值的列
2.普通索引列可以有NULL
索引的选择区分度最大:
比如索引的字段,比如性别男&女 这个值如果在在几千万的数据那么很小,但是在表里面有列是最大的,则是用户的ID号:user_id,每个用户的ID是唯一的,那么这个列是可以作为索引的,因为是区分度也就是最高的,另外需要使用短索引,如果用户名里定义的是varchar(32)实际上我们可以用15个就可以标记出来那么我们可以:
create index idx_username ontable_name (username(15));
查询中使用like
例:
like "%aaa%" #这种是不能够用到索引的
idx_c6(c6) where c6 like "av%";
而 like av% 是能够用到索引
idx_c6(c6)
where c6 like "av%"
这样也是可以用到索引的,like语句如果前后百分号是不能用索引的,如果是以字符开头并以百分号结尾的是可以用到索引的
#如果区分度已经有user_id 这种特别大的列,那么就没有必要做其他操作,所以不建议将区分度大的索引与其他索引放在一起,如果放在一起是为了实现索引覆盖或查询这种特殊场景,是比较合适的,因为是无法回表
不在列上进行运算
排完序需要取最终的数据,比如oder by 或group by 或select * 之类的sql,索引中没有包含特殊的数据都是需要回表的
尤其是select * 的语句如果没有建立全表索引都是要回表的
如下所示:
select * from users where YEAR(adddate)< 2007;
adddate timestamp
这种sql太多是用不到索引的,如果改为基于事件查询则可以:
(因为2007也是通过参数传递进来的)
select * from useradddate '2000-01-01 00:00:00' < '2014-XX-XX XX:XX:XX.';
将其换为小一点的时间这样意义大一些
差劲的sql案例:
包含不等于,比如id = 1;
select * from ywwhere> 表示如果不等于1的id 其他全部打印出来。
mysql>explain select * from yw_idx whereid!=1;
+----+-------------+--------+-------+---------------+---------+---------+------+---------+-------------+
|> +----+-------------+--------+-------+---------------+---------+---------+------+---------+-------------+
| 1 | SIMPLE | yw_idx | range | PRIMARY | PRIMARY | 4 | NULL | 1508444| Using where |
+----+-------------+--------+-------+---------------+---------+---------+------+---------+-------------+
1 row in set (0.08sec)
这样的话相当于一次全表扫描
从扫描的行数来看,优化器explain中有rows字段
从row列表中,可看到接近全表的操作
这里还有一个情况,我们使用limit并查看效果
mysql> select *from yw where> +----+---------+--------+---------+--------+---------------------+--------------+
|> +----+---------+--------+---------+--------+---------------------+--------------+
| 2 | 2333997 | 269341 | 2459005 | 915557 |2014-09-24 15:38:29 | wubxwubxwubx |
+----+---------+--------+---------+--------+---------------------+--------------+
1 row in set (0.00sec)
select * from ywwhere>
select * from ywwhere> 以上两者可以对比
看起来没有变化还是这么多行,但是在limit执行的时候会有流式化的输出,每当读取到一行的时候会放入到buffer池中,存到一定数量后会对其进行一次排序,如当已满足条件了,则不会再进行匹配
但是如果limit之后,速度会快很多,虽然看到此情况,可能会用到索引了,这也是用索引的一种场景
使用not in
not in的主要作用是在执行sql查询语句的时候不在哪一个数据范围的的记录
mysql> select *from yw_idx where c2 not in(4262384,3605632);
mysql> explainselect * from yw_idx where c2 not in(4262384,3605632);
+----+-------------+--------+------+---------------+------+---------+------+---------+-------------+
|> +----+-------------+--------+------+---------------+------+---------+------+---------+-------------+
| 1 | SIMPLE | yw_idx | ALL |> +----+-------------+--------+------+---------------+------+---------+------+---------+-------------+
1 row in set (0.10sec)
对其优化:
一般来说不能直接使用not in之类的sql语句,这属于病态sql
优化的时候可以加一个LIMIT,以减少IO
另外limit结果较大的话或者对其结果不满意的话,可以改为使用left join,然后用主建去关联id为b 而b.id 为null,如下所示:
mysql> select *from yw a left join ( select> +----+---------+---------+---------+---------+---------------------+------------------------------------------------------+------+
|> +----+---------+---------+---------+---------+---------------------+------------------------------------------------------+------+
| 1 | 463681 | 1098981 | 1817518 | 2222359 | 2014-09-24 15:38:29 |wubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubx | NULL |
| 2 | 2333997 | 269341 | 2459005 | 915557 |2014-09-24 15:38:29 | wubxwubxwubx | NULL|
| 3 | 2971523 | 1226698 | 842469 | 414525 | 2014-09-24 15:38:29 | wubxwubxwubxwubxwubxwubxwubxwubxwubx | NULL |
| 4 | 2835700 | 930937 | 2835332 | 1945110 | 2014-09-24 15:38:29 | wubx | NULL |
| 5 | 1578655 | 1044887 | 2649255 | 2307696 |2014-09-24 15:38:29 | wubxwubxwubxwubxwubxwubxwubx | NULL |
| 6 | 1442242 | 992011 | 1740281 | 190626 |2014-09-24 15:38:29 | wubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubx |NULL |
| 7 | 693798 | 309586 | 753637 | 2403923 | 2014-09-24 15:38:29 |wubxwubxwubxwubxwubxwubxwubxwubxwubxwubx | NULL |
| 8 | 888272 | 2581335 | 1547343 | 1465295 | 2014-09-24 15:38:29 |wubxwubxwubxwubx | NULL |
| 9 | 1608599 | 240304 | 2475805 | 2157717 | 2014-09-24 15:38:29 | wubxwubxwubxwubx | NULL |
| 10 | 2833881 | 185188 | 1736996 | 565924 | 2014-09-24 15:38:29 |wubxwubxwubxwubxwubxwubxwubxwubxwubxwubx | NULL |
+----+---------+---------+---------+---------+---------------------+------------------------------------------------------+------+
10 rows in set (17.04sec)
改为顺序IO
另外一种情况就是将其改为顺序IO去取前几行
只oder by>
mysql> select *from yw where c2 not in(4262384,3605632) order by> +----+---------+---------+---------+---------+---------------------+------------------------------------------------------+
|> +----+---------+---------+---------+---------+---------------------+------------------------------------------------------+
| 1 | 463681 | 1098981 | 1817518 | 2222359 | 2014-09-24 15:38:29 |wubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubx |
| 2 | 2333997 | 269341 | 2459005 | 915557 |2014-09-24 15:38:29 | wubxwubxwubx |
| 3 | 2971523 | 1226698 | 842469 | 414525 | 2014-09-24 15:38:29 | wubxwubxwubxwubxwubxwubxwubxwubxwubx |
| 4 | 2835700 | 930937 | 2835332 | 1945110 | 2014-09-24 15:38:29 | wubx |
| 5 | 1578655 | 1044887 | 2649255 | 2307696 |2014-09-24 15:38:29 | wubxwubxwubxwubxwubxwubxwubx |
| 6 | 1442242 | 992011 | 1740281 | 190626 |2014-09-24 15:38:29 | wubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubxwubx |
| 7 | 693798 | 309586 | 753637 | 2403923 | 2014-09-24 15:38:29 |wubxwubxwubxwubxwubxwubxwubxwubxwubxwubx |
| 8 | 888272 | 2581335 | 1547343 | 1465295 | 2014-09-24 15:38:29 |wubxwubxwubxwubx |
| 9 | 1608599 | 240304 | 2475805 | 2157717 | 2014-09-24 15:38:29 | wubxwubxwubxwubx |
| 10 | 2833881 | 185188 | 1736996 | 565924 | 2014-09-24 15:38:29 |wubxwubxwubxwubxwubxwubxwubxwubxwubxwubx |
+----+---------+---------+---------+---------+---------------------+------------------------------------------------------+
10 rows in set (0.02sec)
只将结果集比较大的将其追加limit ,再去想办法对其优化
select * from yw a where a.id not in (select id from yw where id |
|