最近听了公司里的同事作的技术分享,而后以为对本身仍是挺有帮助的。都是一些平常须要注意的地方,咱们目前在开发过程当中,其实用不到MySQL太深的内容的。只是能适用咱们平常开发的知识就能够了。因此我将本身的理解和学习总结也写出来,供你们一块儿分享。算法
大致分四部分:sql
作数据库优化通常是由如下几种方式:数据库
成本和效果成反比。缓存
服务器硬件安全
加强服务器的硬件方式不一样的方式:例如增长磁盘配置(SSD,PCRE),增大内存,增长CPU配置等。加强服务器硬件在必定的阶段内确实能够达到不错的效果,可是并非长久之计,若是不注重下面的三种策略,一味的增长硬件配置,会拔苗助长。服务器
系统及数据库配置架构
随着系统硬件的不断更新迭代,数据库的配置也是不断变化的。例如之前的机械硬盘性能并不很好,因此数据库的配置并无设置过高。当服务器广泛的都是SSD后数据库的系统配配置也是能够随之变化的。另外随着业务的变化以及数据量的增加,数据库的配置也是随着变化的。可是这部分的配置带来的效果并不太明显,和增长服务器硬件相似。并发
数据库表设计及规划分布式
数据库的表在设计之初就应该考虑好了之后的规划。否则当发现数据库产生瓶颈了再去优化,成本会很高。因此也须要开发人员能经过对业务的深入理解来对数据库作好长远的规划。函数
SQL及索引优化
对SQL语句以及索引的优化能够说是成本最低的了,效果也是很是显著的。
这四部份内容,总有人以为SQL及索引优化是最重要的,可是本人以为最重要的是数据库表设计以及规划,若是能根据业务将表设计好了,根本是不须要进行索引优化的。若是数据库没有规划好,再好的DBA给你作SQL优化,效果也是杯水车薪的。
上面这幅图是MySQL的基本逻辑架构图,主要分为四层。
链接层
经过MySQL的链接地址去访问MySQL的数据库,以及对访问信息的校验。
服务层
对SQL语句的校验,以及对SQL的优化和优化策略选择,最后发送到执行器去执行SQL。还包括MySQL的查询缓存也在这一层。
引擎层
MySQL是插件式存储引擎,最终将数据存到硬盘时不一样的引擎有不一样的组织方式。上面列出了一些引擎,常见InnoDB,MyISAM等,只要符合MySQL的接口规范,MySQL是支持自定义的引擎。
存储系统层
这部分主要是数据存储,将数据存到磁盘,磁盘的IO读写等过程。
请使用InnoDB存储引擎,慎用MyISAM引擎。
上图是InnoDB引擎和MyISAM引擎的一些区别对比。
ACID事务支持:因为咱们介绍此次介绍MySQL的时候是以OLTP(on-line transaction processing:联机事务处理)为主的,而非OLAP(On-Line Analytical Processing:联机分析处理),因此事务处理是很重要的,这也就是为何强烈要求使用InnoDB引擎的一个缘由。
锁粒度:MyISAM支持的锁粒度是表级锁,表级锁的意思是指当一张数据表被锁住后,其余的对这张表的操做(DML)都要等着前一个锁释放了才能够执行。因此当并发量高时用户体验是很很差的。而InnoDB引擎的行级锁,只是对表的一部分数据进行加锁,因此能很好的支持并发,下降了对同一张表的操做冲突。
外检约束:虽然InnoDB支持外键,MyISAM不支持外键。可是也不建议在平常的使用过程当中用外键,由于每次操做外键时都要去检查一下外键关联的数据。
全文索引:InnoDB引擎不支持全文索引,可是MyISAM支持。可是在数据库中创建全文索引其实并非什么好的策略,仍是建议若是须要创建全文索引的时候考虑使用搜索引擎工具如:ElasticSearch,Solr等。
崩溃安全:InnoDB支持崩溃安全,MyISAM是不支持的崩溃安全的。
什么是崩溃安全呢?
举个例子🌰:当一台服务器的上的数据库忽然挂了,或是服务器崩溃了,甚至是忽然断电了。这个时候若是MySQL使用的是InnoDB引擎,那么在数据库恢复后或是从新通电后,会执行崩溃恢复,就是未执行完的事务会继续执行,该回滚的回滚,该执行完的执行完,能确保数据的一致性。可是若是MySQL是使用的MyISAM引擎,那么首先MyISAM不支持事务,因此会形成数据的不一致性,并且若是在对表进行操做时断电,致使没有正确的关闭表,还会致使存储文件的损坏,在恢复通电后对这张表的任何读写操做都不能执行了。并且就算手动恢复数据也是比较麻烦的。
在设计表时要遵循几个基本原则:
第一条基本原则,是为了防止随着业务的发展之后若是数据量大到必定程度了须要分表时,拆分带有这些特性的表时成本是很是大的。
第二条、第三条 、第四条都是说大字段或大文件是不建议存储到MySQL当中的,由于对这些数据的操做MySQL是有特殊的存储方式的,性能不好。若是存储了这些数据后,再有一些排序或者是聚合操做的话会直接在磁盘中创建临时文件表,普通的字段类型例如varchar类型的,在有聚合操做时是会在内存中进行临时存储的。
第五条原则是要求对业务有长远的规划,不一样的业务首先要分表,其次要分库。虽然MySQL的很强大,可是单节点的能力是有限的。因此企业级的数据库都是分布式的,要为之后业务的增加数据的访问量增加作好充分的规划。
例如:使用VARCHAR(5)和VARCHAR(200)存储'hello'的空间开销是同样的,使用更短的列有什么优点吗?
虽然存储开销是同样的,可是若是对这个字段进行聚合操做(order by、group by等),这个时候是须要先将临时数据存储到内存中的,可是申请内存空间时是按照字段的定义大小来申请的,也就是说VARCHAR(200)申请的内存空间是VARCHAR(5)的40倍。还有一种状况是,当一个表的数据量很大时,要作数据迁移或是大数据分析时,是须要抽取全表数据的,这个时候读全表数据是没法靠申请内存空间来实现的,MySQL是会在磁盘中创建临时文件表。而且是按照字段定义的大小来占用磁盘空间的,若是一个200G的硬盘,可是表中的数据是50G,在抽取全表数据时会有可能将磁盘占满的。
因此,更大的定义列会消耗更多的内存,在使用内存临时表进行排序或操做时会根据定义的长度进行内存分配。
在给字段选择类型时,尽可能遵循【小而简单】的原则,可是能够根据能够读性等因素适当调整。
例如:在存储时间字段时,有的人使用int类型(4个字节),有的人使用datetime(8个字节),虽说占用的空间小了,可是可读性也变差了。并且就即便是类型选择的稍微不太合理,这部分也是能够经过对SQL的优化等操做来减少影响的。
还有就是例如存储性别的时候,我们使用tinyint,而不使用枚举类型,由于若是之后又多了一种类型(😏),这种操做是须要进行改表的,成本比用tinyint类型大不少。
单个表的字段数到了必定程度是建议拆表的,可是具体的峰值是根据实际的业务来看的,还有就是一个表的记录行数也是不建议不少,当到达必定量时再进行聚合操做是性能不好的。当表的数据量很大时增长字段也是须要消耗成本的,须要copy表中数据而后从新建表,这样才能保证线上的数据在加字段时是热处理。表物理文件的大小也是根据实际需求来考虑是否拆分的,若是表中只是追加操做,并且查询操做很不频繁,呢么拆表就能够慢慢考虑。这部份内容不作过多的讨论。
上面的图介绍的是InnoDB的索引结构,分为两部分聚簇索引和辅助索引。
聚簇索引也是主键索引,InnoDB表都是有主键的,就算是没有给表建立主键,MySQL也会默认的建立一个主键,聚簇索引每个叶子节点表明的是主键的键值,最末端指向的是主键所在的那行数据记录。
辅助索引也是非聚簇索引,辅助索引就是平常表中除了主键之外的其余索引,每一个叶子节点都表明的是索引的字段值,最末端指向的是索引值的主键。
在建立索引时须要注意,经常使用的有int类型,bigInt类型。首先这些类型是占用字节数少,而且是有序的。在创建辅助索引时能节省空间,由于每一个辅助索引记录后面都带着一个主键索引,若是主键是uuid或是MD5值一类的,那么在创建辅助索引后会占用很大的磁盘空间,而且在按照主键去查询的时候主键值是要加载到内存中的,因此综合考虑仍是int、bigInt更好一些。
例以下图的例子。
主键以外将name字段设置为索引。索引类型是varchar而且每一个索引记录后面都跟着一个主键值,这个索引实际上是很耗性能的。
相对于InnoDB来讲,MyISAM引擎的主键也是指向主键所在的记录的,可是辅助索引就不同了,辅助索引最终也是指向数据记录的。MyISAM引擎的在数据存储的物理位置上有一个物理位置的编号。而后不管是主键仍是辅助索引都是指向这个编号的。
以下图的例子所示:
表必须有主键。
不使用更新频繁的列。
忌用字符串列作主键。
不使用UUID/MD5等生成的随机数作主键。
推荐用独立于业务的AUTO_INCREMENT列或全局ID生成器作代理主键。
表必须有主键,即便没有主键InnoDB也会自动生成一个,若是使用频繁更新的列作主键,那主键的B+树不是一个稳定的结构,很耗磁盘开销,以及主键性能大大下降,上面已经说了字符串类型作主键会占用大量磁盘空间。不适用随机数作主键,是为了防止有磁盘空洞,产生不连续的空间。
目前的MySQL确实是有最左前缀的规则,即a_b_c索引,查询b和c时不走联合索引,可是随着MySQL的不断发展,如今又出现了一种叫作“索引下推”的概念,虽然不是表明着b和c使用时就能走索引了,可是看趋势可能之后会出现这种优化。最左前缀内容就不作过多的介绍了。
首先介绍一下,回表的概念,InnoDB引擎的表是必须有主键的,可是当存在辅助索引时,辅助索引在索引记录中存储的是主键值。当经过二级索引去查询非辅助索引包含的字段时,是先根据辅助索引查询到相应的主键值,而后再根据主键值去查询到相应的记录。这个查询两次的过程就是回表。若是一个联合索引由a、b、c三个字段组成,那么“select b,c from test where a = 100”这个SQL就不须要产生回表的,由于只查询联合索引就能获得想要的结果了。
改善查询效率
避免排序
数据率重
减慢插入和更新的效率。
索引添加的目的就是为了改善查询效率,添加索引时要避免出现using filesort,出现using filesort是指,当查询操做中包含order by,没法利用索引完成排序操做时,MySQL优化器不得不选择相应的排序算法来实现,数据较少时从内存排序,不然从磁盘排序。
举个例子:
仍是以上面的tb_user_test表为例,"select b,c from tb_user_test where a=100 and b=200 order by c desc;"这个SQL语句在执行的时候若是tb_user_test没有idx_a_b_c这个联合索引那么执行计划是这样的
注意Extra列的值,Using filesort 出现了,这说明MySQL将数据从新排序了。
若是将字段a和b建立了联合索引后的执行计划是这样的
仍是会有Using filesort。
将字段a和b还有c建立了联合索引后的执行计划是这样的
此次没有Using filesort了,建立索引时注意避免出现重排序问题。
数据虑重是指在使用distinct或者group by的时候也是可使用索引进行优化查询的。distinct或group by的列建立索引能提示查询效率。
索引虽然能改善查询效率,可是代价是牺牲了插入和更新的效率。
单张表索引数量建议不超过5个。
单个索引中的字段建议不超过5个。
字符串适度使用前缀索引。
索引不是越多越好,能不添加的索引尽可能不要添加。
索引的控制只是一些建议,并非强制要求。
不在低区分度的列上创建索引,例如:“性别”。
尽可能避免%前导查询,如like "%ab"。
尽可能避免负向查询,如not in /like。
避免全表扫描以及频繁的回表操做
区分度低的列建立了索引后查询速度确实提高了,可是当数据量变大后会产生大量的随机IO和回表查询。like前缀是不走索引的,索引对负向查询的支持也很差。
其余几点须要注意的是,索引的创建要优先保证高频查询需求的效率,低频需求尽量使用到最左前缀索引。索引也要随着业务的演进更变化,不是建完索引就完事了。
SQL尽量简单,线上尽量少使用大SQL,使用简单小SQL。
尽量少使用存储过程/触发器/函数,减小MySQL端的数学运算和逻辑判断。(不易于扩展)
使用预编译语句,下降SQL注入几率。
尽可能少用select * ,只取须要的数据列。(可下降磁盘I/O,有机会只走复合索引,缓存使用下降。)
基本原则:where条件比较,字段类型和传入值必须保证:数字对数字,字符对字符。
经过下面的例子就能够看出来。
字段:`remark` varchar(50) NOT NULL COMMENT '备注,默认为空',
MySQL>SELECT id, gift_ code FROM gift Where deal_ id = 640 AND remark=115127; 1 row in set (0.14 sec) MySQL>SELECT id, gift_ code FROM pool gift Where deal_ id = 640 AND remark='115127' ;1 row in set (0.005 sec)
当remark传入int类型的值后,查询时间0.14秒,传入字符类型后只须要0.005秒。
基本原则:不在索引列进行数学运算和函数运算。
索引字段进行数学运算时,不走索引。能够放到后面对值进行运算。
例如:
经过运行时间就能够看出效果。
索引字段慎用函数运算,MySQL的优化器对函数运算识别不出来时会直接走全表扫描。
例子以下:
select * from table limt 10000,10;
limit 10000,10; 偏移量越大则越慢。查询的时候要一步一步遍历到第10010条记录,而后取后10条记录,前面的所有抛弃掉。
select * from table where id>=23424 limit 11;
#10+1(每页10条)
select * from table where id>23434 limit 11;
分页方式二
select * from table where id>=(select id from table limit 10000,1) limit 10;
分页方式三
select * from table where Inner join (select id from table limit 10000,10) using (id);
分页方式四
先取id:select id from table limit 10000,10; select * from table where id in (123,456,...);
具体示例:
MySQL> select sql_no_ cache * from post limit 10,10;10 row in set (0.01 sec) MySQL> select sql_ no_cache * from post limit 2000,10;10 row in set (0.13 sec) MySQL> select sql_no_cache * from post limit 80000,10;10 rows in set (0.58 sec) MySQL> select sql_no_ cache id from post limit 8000,10;10 rows in set (0.02 sec) MySQL> select sql_no_ cache * from post WHERE id> = 323423 limit 10;10 rows in set (0.01 sec) MySQL> select * from post WHERE id >= ( select sql_ no_ cache id from post limit8000,1 ) limit 10;10 rows in set (0.02 sec)
不一样的业务使用不一样的数据库实例
不一样的业务表拆分到不一样的数据库中,能够根据不一样的模块,不一样的功能将表拆分到不一样个数据库中。逻辑比较清晰,可是也要考虑到具体的状况,若是有关联查询时,两个表放在里不一样的库中,这样就拆分的不合理了,因此拆分的时候要对业务作深刻的了解。
一个表中的数据拆分到不一样表中或不一样的库中。可是拆分的时候要慎重的考虑好了,要以哪一个键做为惟一标识进行拆分。一但肯定下来最好不要随意更改。
水平拆分+垂直拆分
(若是对分布式事务要求不过高的可使用WTable,底层也是作了拆分。聚合操做也比较麻烦,要对每一个库进行请求,而后再进行聚合操做。)
此次的知识总结的比较粗糙,之后会对每一块作深刻研究。
文章会同步到个人公众号上面,欢迎关注。