在大中型的项目中, 随着业务的扩张, 用户量的增长, 对于我们项目的性能已提供越来越高的要求, 那么在提升项目性能的时候, SQL优化显得尤为重要.
这里可能会有疑问, 我们今天探讨的是SQL优化, 为什么要提到性能检测呢? 原因就是我们想要进行优化, 要知道优化哪条SQL, 优化哪个索引, 所以SQL性能检测必不可少.
所以我们首先要介绍检测工具.
通过以上命令show status
查询到了效率低的sql后, 可以通过```explain``命令查看整条select语句的执行过程.
慢查询日志记录了所有执行时间超过参数 long_query_time的sql语句的日志, long_query_time默认为10秒(可以通过配置文件设置), 慢查询日志默认是关闭的, 使用的时候需要修改配置文件打开.
vi /usr/my.conf
service mysql restart
/var/lib/mysql/
目录下, 有个slow_query.log文件,cd /var/lib/mysql
tail -f slow_query.log
通过慢查询日志, 就能抓取到哪些sql语句执行慢, 这样就能有针对性的进行优化了.
命令: show status like 'Com____'
上图红色框框代表的是当前数据库这一列增删改查的频次,执行一次, 对应的Value值+1.
通过这条命令, 我们可以知道当前数据库是以查询为主还是更新为主. 如果是查询为主, 就重点查询; 如果增删改多就重点优化写入操作.
show profile
可以查看所有sql语句的执行效率(所用时间). 前提是这个命令需要被打开, 严格的说也就是打开这个命令后执行的所有sql语句, 它都能记录下执行时间, 并展示出来.
可以通过这个命令分析哪些sql语句执行效率低. 耗时长, 就更有针对性的优化这条sql.
select @@have_profiling
select @@profiling
set profiling=1
show profiles
说的数据库优化, 其实最主要的优化查询, 也就是提高查询效率. 而优化查询, 最主要的是优化索引.
Mysql官方对索引的定义: 索引(Index)是帮助Mysql高效获取数据的数据结构(有序). 在数据之外, 数据库系统还维护了满足特定查找算法的数据结构, 这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法, 这种数据结构就是索引.
如下面的示意图所示:
上图的索引视图是一个简单的示意, 索引数据结构并非都是二叉树, 后面会详细介绍~
左边是数据表, 一共有两列7条记录, 最左边的数据记录的是物理地址. 为了加快Col2这一列的查找, 可以维护一个右边所示的二叉查找树, 每个节点分别包换索引键值和一个指向对应数据记录物理地址的指针, 这样就能通过二叉树快速查找到相应的数据.
比如: 我想找22,图1中是从上往下一个一个找, 要找5次才能找到; 而图2中找3次就可以了, 22比34小, 找到5, 22又比5大, 最后定位到了22.
一般来说索引本身也很大, 不可能全部存储在内存中, 因此索引往往以索引文件的形式存储在磁盘上. 索引是数据库用来提高性能最常用的工具.
通俗的说: 索引类似于字典的目录, 如果没有目录(索引), 那么我们要找到指定的字需要从第一个字开始查找到最后一个字才有结果, 可能要把字典中所有的字看一遍才能找到想要的结果; 而目录(索引)则能够让我们快速的定位到这个字的位置, 从而找到我们要的结果.
索引是在Mysql的存储引擎(InnoDB, MyIsam)层中实现的, 而不是在服务层实现的. 所以每种存储引擎的索引都不一定完全相同, 也不是所有的存储引擎都支持所有的索引类型的, Mysql目前提供了以下4种索引:
B+Tree是在BTree基础上进行演变的, 所以我们先来看看BTree, BTree又叫多路平衡搜索树, 一颗m叉BTree特性如下:
以5叉BTree为例, key的数量: 公式推导 [ceil(m/2) -1 ] <= n <= m-1. 所以 2 <= n <= 4, 中间节点分裂父节点,两边节点分裂.
B+Tree为BTree的变种, B+Tree与BTree的区别:
MySql索引数据结构对经典的B+Tree进行了优化, 在原B+Tree的基础上, 增加了一个指向相邻叶子节点的链表指针, 就形成了带有顺序指针的B+Tree, 提高区间访问的性能.
索引在创建表的时间, 就可以同事创建, 也可以创建表后随时增加新的索引.
show index from 表名
结果:
Key_name
: 索引名称
Column_name
: 在哪一列加的索引
index_type
: 索引类型, 这里B+Tree显示不出来, 显示的是BTree, 但类型是B+Tree.
create index idx_表名_哪一列 on 表名(哪一列)
drop index 索引名称 on 表名
索引的设计需要遵循一些已有的原则, 这样便于提升索引的使用效率, 更高效的使用索引.
索引是优化查询最主要的手段, 索引在建立的时候有对应的设计原则, 上面提到过的最左前缀原则我们这里详细介绍下. 在使用索引的时候我们也要注意一些原则避免索引失效.
复合索引命名规则: index_表名_列名1_列名2_列明3
比如: create index idx_seller_name_sta_addr on tb_seller(name, status, address)
复合索引创建好了, 剩下的就是查询的时候使用了, 这里重点介绍如何避免索引失效
如果在查询的时候, 使用了复合索引, 要遵循最左前缀法则, 也就是查询从索引的最左列开始, 并且不能跳过索引中的列.
如果不包含最左边的索引列, 则其他索引不生效;
如果包含了最左边的索引列, 但是跳过了一列直接索引了复合索引的第三列, 则第三列的索引不生效.
select * from tb_seller where name = "小米科技" and status = "1" and address = "北京市";
select * from tb_seller where name = "小米科技";
select * from tb_seller where name = "小米科技" and status = "1";
select * from tb_seller where status = "1" and address = "北京市";
select * from tb_seller where name = "小米科技" and address = "北京市";
如何查看索引是否生效, 可以通过上个章节的explain + sql语句
, 来查看key 和 key_len
判断索引是否生效.select * from tb_seller where name = "小米科技" and status = "1" and address = "北京市";
select * from tb_seller where name = "小米科技" and status >= "1" and address = "北京市";
select * from tb_seller where name = "小米科技" and status > "1" and address = "北京市";
address索引失效, 因为status是大于号, 范围查询.比如在索引上使用切割函数, 就会使索引失效.
select * from tb_seller where substring(name, 3, 2) = "科技";
如果索引列是字符串类型的整数, 条件查询的时候不加引号会造成索引失效. Mysql内置的优化会有隐式转换.
select * from tb_seller where name = "小米科技" and status = 1
如果索引列完全包含查询列, 那么查询的时候把要查的列写出来, 不使用select *
select sellerid, name, status from tb_seller where name = "小米科技" and staus = "1" and address = "西安市";
用or分割开的条件, 如果or前面的列有索引, or后面的列没有索引, 那么查询的时候索引会失效, 如果一定要用or查询, 可以考虑下or连接的条件列都加索引, 这样就不会失效了.
select * from tb_seller where name = "小米科技" or createTiem = "2018-01-01 00:00:00";
like
模糊查询时, 如果like%
也就是%加在后面索引不会失效, 如果%lik
或%like%
也就是%加在前面, 索引会失效.is null
和is not null
有时索引失效, 有时索引不失效.name
列, 表中的这一列都是有值的is null
这就是稀有数据, 就会走索引.is not null
, 表里的这列都是is not null都是有值的, 所以就不走索引, 全表扫描.order by
语句时,尽量使用覆盖索引, 也就是select 后面要查有索引的列, 不要使用select *
select *
使用的是using filesortselect id, age from emp order by age asc
; 这样就是用using indexselect age, count(*) from emp group by age order by null;
尽量避免子查询, 可以将子查询优化为join
多表连接查询.
类似select * from tb_item limit 49000,10;
约往后的页数, 查询效率越低. 因为分页查询相当于把49000+10条数据都查出来, 然后丢弃前49000条. 有两种思路进行优化
select * from tb_item t, (select id from tb_item order by id limit 49000,10) d where t.id = d.id;
select * from tb_item where id > 49000 limit 10;
垂直拆分按照字段进行拆分,其实就是把组成一行的多个列分开放到不同的表中,这些表具有不同的结构,拆分后的表具有更少的列。
举个例子:
缺点: 插入的时候使用事务,也可以保证两表的数据一致。缺点也很明显,由于拆分出来的两张表存在一对一的关系,需要使用冗余字段,而且需要join操作。
但是我们可以在使用的时候可以分别取两次,这样的来说既可以避免join操作,又可以提高效率。
水平拆分按照行进行拆分,常见的就是分库分表。以用户表为例,可以取用户ID,然后对ID取10的余数,将用户均匀的分配进这 0-9这10个表中。查找的时候也按照这种规则,又快又方便。
举个例子: 有些表业务关联比较强,那么可以使用按时间划分的。例如每天的数据量很大,需要每天新建一张表。这种业务类型就是需要高速插入,但是对于查询的效率不太关心。表越大,插入数据所需要索引维护的时间也就越长。
分区适用于例如日志记录,查询少。一般用于后台的数据报表分析。对于这些数据汇总需求,需要很多日志表去做数据聚合,我们能够容忍1s到2s的延迟,只要数据准确能够满足需求就可以。
MySQL主要支持4种模式的分区:range分区、list预定义列表分区,hash 分区,key键值分区。
录入使用key键值分区:
CREATE TABLE `test2` (
`id` int(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`name` varchar(100) DEFAULT NULL COMMENT '名称',
`state` int(1) DEFAULT NULL COMMENT '状态',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
PARTITION BY KEY (id)
PARTITIONS 10;
通过:docker exec -it ‘mysql容器id‘ /bin/bash
进入容器
进入到mysql数据目录: cd /var/lib/mysql/data/
查看列表:ls -l
大型网站会有大量的并发访问,如果还是传统的数据存储方案,只是靠一台服务器处理,如此多的数据库连接、读写操作,数据库必然会崩溃,数据丢失的话,后果更是不堪设想。这时候,我们需要考虑如何降低单台服务器的使用压力,提升整个数据库服务的承载能力。
我们发现一般情况对数据库而言都是“读多写少”,也就说对数据库读取数据的压力比较大,这样分析可以采用数据库集群的方案。其中一个是主库,负责写入数据,我们称为写库;其它都是从库,负责读取数据,我们称为读库。这样可以缓解一台服务器的访问压力。
MySql自带主从复制功能,我们可以使用主从复制的主库作为写库,从库和主库进行数据同步,那么可以使用多个从库作为读库,已完成读写分离的效果。
如果访问量非常大,虽然使用读写分离能够缓解压力,但是一旦写操作一台服务器都不能承受了,这个时候我们就需要考虑使用多台服务器实现写操作。
例如可以使用MyCat搭建MySql集群,对ID求3的余数,这样可以把数据分别存放到3台不同的服务器上,由MyCat负责维护集群节点的使用。