本文源码:GitHub·点这里 || GitEE·点这里mysql
在MySQL使用的过程当中,所谓的性能问题,在大部分的场景下都是指查询的性能,致使查询缓慢的根本缘由是数据量的不断变大,解决查询性能的最多见手段是:针对查询的业务场景,设计合理的索引结构。git
索引的使用并非越多越好,而是针对业务下的查询场景,不断的改进和优化,例如电商系统中用户订单的场景,假设存在以下表结构:github
CREATE TABLE `ds_user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id', `user_name` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表'; CREATE TABLE `ds_order` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id', `user_id` int(11) NOT NULL COMMENT '用户ID', `order_no` varchar(60) NOT NULL COMMENT '订单号', `product_name` varchar(50) DEFAULT NULL COMMENT '产品名称', `number` int(11) DEFAULT '1' COMMENT '个数', `unit_price` decimal(10,2) DEFAULT '0.00' COMMENT '单价', `total_price` decimal(10,2) DEFAULT '0.00' COMMENT '总价', `order_state` int(2) DEFAULT '1' COMMENT '1待支付,2已支付,3已发货,4已签收', `order_remark` varchar(50) DEFAULT NULL COMMENT '订单备注', `create_time` datetime DEFAULT NULL COMMENT '建立时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单表';
用户和订单管理表,在电商的业务中很常见,能够经过对该业务分析,看看经常使用的索引结构:sql
用户方:数据库
运营方:服务器
这样一个流程分析走下来,便可以在开发初期,肯定哪些结构是查询必须用到的,预先作好索引结构,避免数据量庞大到影响性能时再去考虑使用索引。架构
有些时候会考虑放弃一些查询条件,例如基于产品名称的数据统计,走定时任务的方式,用来缓解表的查询压力,处理的方式是多样的。并发
优秀的索引设计,都是创建在对业务数据的理解上,考虑业务数据的查询方式,提升查询效率。函数
单列索引,即索引创建在表的一个字段上,一个表能够有多个单列索引,使用起来相对比较简单:性能
CREATE INDEX user_id_index ON ds_order(user_id) USING BTREE;
主键索引,或者上述的user_id_index都是单列索引。
业务场景:基于用户本身对订单查询,和管理系统,订单和用户的关联查询,因此订单表的user_id须要一个索引。
组合索引包含两个或两个以上的列,组合索引相比单列索引复杂不少,如何创建组合索引,和业务关联度很是高,在使用组合索引时,还须要考虑查询条件的顺序。
CREATE INDEX state_create_time_index ON `ds_order`(`create_time`,`order_state`);
如上就是组合索引,实际包含的是2个索引 (create_time) (create_time,order_state),这样查询就涉及到最左前缀的原则,必须按照顺序来查询,这里下面详说。
业务场景:首先单说这里组合索引,在业务开发中,常见订单状态的统计,基于统计结果作运营分析,另外就是在运营系统中,基于建立时间段的筛选条件是默认存在的,避免所有数据实时扫描;一些其余的常见查询也都是条件加时间段的查询模式。
若是须要加索引的列是很长的字符串,那么索引会变的庞大臃肿,起到的效果可能并非很明显。这时候能够截取列的前面一部分,建立索引,节省空间,这样可能会出现索引的选择性降低,即基于前缀索引查询出的类似数据可能不少:
ALTER TABLE ds_order ADD KEY (order_no(30)) ;
这里因为订单号太长,因此选择前面30位做为前缀索引,用做订单号的查询,固然这里涉及到一个很是经典的业务场景,订单号机制。
业务场景:前缀索引一个典型的应用场景就是处理订单号,一个看似很长的订单号,其实包含的信息很是多:
如此一段分析下来,实际订单号是很是长的,因此须要引入前缀索引机制,前缀索引指望使用的索引长度能够筛选整个列的基数,例如上面的订单号:
注意:若是业务容许的状况下,通常要求前缀索引的长度有惟一性,例如上面的时间和标示位。
例如全文索引等,这些用到的场景很少,若是数据庞大,又须要检索等,一般会选择强大的搜索中间件来处理。显式惟一索引,这种也会在程序上作规避,避免不友好的异常被抛出。
如何建立最优的索引,是一件不容易的事情,一样在查询的时候,是否使用索引也是一件难度极大的事情,经验之谈:多数是性能问题暴露的时候,才会回头审视查询的SQL语句,针对性能问题,作相应的查询优化。
这里直接查询主键索引,MySQL的主键通常选择自增,因此速度很是快。
EXPLAIN SELECT * FROM ds_order WHERE id=2; EXPLAIN SELECT * FROM ds_order WHERE id=1+1; EXPLAIN SELECT * FROM ds_order WHERE id+1=1;
这里,id=2,id=1+1,MySQL均可以自动解析,可是id+1是在索引列上执行运算,直接致使主键索引失效。这里有一个基本策略,若是非要在单列索引上作操做,能够将该逻辑放在程序中,到MySQL层面,SQL语句越干净利落越好。
前缀索引的查询,能够基于Like对特定长度筛选,或者全订单号查询。
EXPLAIN SELECT * FROM ds_order WHERE order_no LIKE '202008011314158723628732871625%'; EXPLAIN SELECT * FROM ds_order WHERE order_no='20200801131415872362873287162572367';
查询最麻烦的就是组合索引,或者说查询条件组合起来,都使用了索引:
EXPLAIN SELECT * FROM ds_order WHERE create_time>'2020-08-01 00:00:00' AND order_state='1';
上述基于组合索引中列的顺序,使用了组合索引:state_create_time_index。
EXPLAIN SELECT * FROM ds_order WHERE create_time>'2020-08-01 00:00:00';
上述只使用create_time列,也一样使用了索引结构。
EXPLAIN SELECT * FROM ds_order WHERE order_state='1';
上述若是只使用order_state条件,则结果显示全表扫描。
EXPLAIN SELECT * FROM ds_order WHERE create_time>'2020-08-01 00:00:00' AND order_no LIKE '20200801%';
上述则基于组合索引的create_time列和单列索引order_no保证查询条件都使用了索引。
经过上面几个查询案例,索引组合索引使用的注意事项以下:
索引机制在MySQL中真的很是复杂,非专业的DBA(就是指开发人员),基本要熟练常见的索引结构,待过两年所谓的大厂,每一个版本开发涉及的核心表SQL都是有专业DBA验收,复杂的查询都是提交需求,DBA直接输出查询SQL,固然在通常公司是没有DBA,须要开发在开发的过程当中不断的思考,逐步优化,这须要对业务数据有必定的敏感度,对核心接口有执行监控,当发现稍微出现耗时状况,就能够不断优化,这个积累是个枯燥和进步的过程。
GitHub·地址 https://github.com/cicadasmile/mysql-data-base GitEE·地址 https://gitee.com/cicadasmile/mysql-data-base
推荐阅读:MySQL数据库系列