MySQL中建立及优化索引组织结构的思路

导读mysql

经过一个实际生产环境中的数据存取需求,分析如何设计此存储结构,如何操纵存储的数据,以及如何使操做的成本或代价更低,系统开销最小。同时,让更多初学者明白数据存储的表上索引是如何一个思路组织起来的,但愿起到一个参考模板的价值做用。sql

 

测试用例描述数据库

测试用例为B2C领域,一张用于存储用户选购物品而生成的产品订单信息表,不过去掉一些其余字段,以便用于测试,其表中的数据项也不特别描述,字段意思见表服务器

USE `test`;架构

DROP TABLE IF EXISTS `test`.`goods_order`;ide

CREATE TABLE `goods_order`(性能

`order_id`        INT UNSIGNED      NOT NULL             COMMENT ‘订单单号’,测试

`goods_id`        INT UNSIGNED      NOT NULL DEFAULT ’0′ COMMENT ‘商品款号’,优化

`order_type`      TINYINT UNSIGNED  NOT NULL DEFAULT ’0′ COMMENT ‘订单类型’,网站

`order_status`    TINYINT UNSIGNED  NOT NULL DEFAULT ’0′ COMMENT ‘订单状态’,

`color_id`        SMALLINT  UNSIGNED NOT NULL DEFAULT ’0′ COMMENT ‘颜色id’,

`size_id`         SMALLINT  UNSIGNED NOT NULL DEFAULT ’0′ COMMENT ‘尺寸id’,

`goods_number`    MEDIUMINT  UNSIGNED NOT NULL DEFAULT ’0′ COMMENT ‘数量’,

`depot_id`        INT UNSIGNED  NOT NULL DEFAULT ’0′ COMMENT ‘仓库id’,

`packet_id`       INT UNSIGNED  NOT NULL DEFAULT ’0′ COMMENT ‘储位code’,

`gmt_create`      TIMESTAMP     NOT NULL DEFAULT ’0000-00-00 00:00:00′ COMMENT ‘添加时间’,

`gmt_modify`      TIMESTAMP     NOT NULL DEFAULT ’0000-00-00 00:00:00′ COMMENT ‘更新时间’,

PRIMARY KEY(order_id,`goods_id`)

)ENGINE=InnoDB AUTO_INCREMENT=1 CHARACTER SET ‘utf8′ COLLATE ‘utf8_general_ci’;

其中,主键信息:PRIMARY KEY(order_id,`goods_id`),为什么主键索引索引字段的顺序为:order_id,`goods_id`,而不是: `goods_id`, order_id呢?缘由很简单,goods_id在订单信息表中的重复率会比order_id高,也即order_id的筛选率更高,能够减小扫描索引 记录个数,从而达到更高的效率,同时,下面即将会列出的SQL也告诉咱们,有部分SQL语句的WHERE字句中只出现order_id字段,为此更加坚决 咱们必须把字段:order_id做为联合主键索引的头部,`goods_id`为联合主键索引的尾部。

数据存储表设计的小结:

设计用于存储数据的表结构,首先要知道有哪些数据项,也即行内常说的数据流,以及各个数据项的属性,好比存储的数据类型、值域范围及长度、数据完整性等要求,从而肯定数据项的属性定义。存储的数据项信息肯定以后,至少进行以下三步分析:

l  首先,肯定哪些数据项或组合,能够做为记录的惟一性标志;

l  其次,要肯定对数据记录有哪些操做,每一个操做的频率如何,对网站等类型应用,还须要区分前台操做和后台操做,也即分外部用户的操做,仍是内部用户的操做;

l  最后,对做为数据记录操做的条件部分的数据项,分析其数据项的筛选率如何,也即数据项不一样值占总数据记录数的比例关心,比例越接近1则是筛选率越好,以及各个值得分布率;

综上所述,再让数据修改性操做优先级别高于只读性操做,就能够建立一个知足要求且性能较好的索引组织结构。

数据的存取设计,就涉及一块很是重要的知识: 关系数据库的基础知识和关系数据理论的范式。对于范式的知识点,特别解释下,建议学到BCNF范式为止,1NF、2NF、3NF和BCNF之间的差异,各 自规避的问题、存在的缺陷都要一清二楚,可是在真实的工做环境中,不要任何存取设计都想向范式靠,用一句佛语准确点表达:空便是色,色便是空。

n  用于生成测试数据的存储过程代码

建立索引,就离不开表存储的真实数据,为此编写一个存储过程近可能模拟真实生产环境中的数据,同时也方便你们使用此存储过程,在本身的测试环境中,真实感觉验证,

存储过程代码:

DELIMITER $$

DROP PROCEDURE IF EXISTS `usp_make_data` $$

CREATE PROCEDURE `usp_make_data`()

BEGIN

DECLARE iv_goods_id INT UNSIGNED DEFAULT 0;

DECLARE iv_depot_id INT UNSIGNED DEFAULT 0;

DECLARE iv_packet_id INT UNSIGNED DEFAULT 0;

 

SET iv_goods_id=5000;

SET iv_depot_id=10;

SET iv_packet_id=20;

 

WHILE iv_goods_id>0

DO

START  TRANSACTION;

WHILE iv_depot_id>0

DO

WHILE iv_packet_id>0

DO

INSERT INTO goods_order(order_id,goods_id,order_type,order_status,color_id,size_id,goods_number,depot_id,packet_id,gmt_create,gmt_modify)

VALUES(SUBSTRING(RAND(),3,8),iv_goods_id,SUBSTRING(RAND(),3,1),SUBSTRING(RAND(),5,1)%2,SUBSTRING(RAND(),3,3),SUBSTRING(RAND(),4,3),SUBSTRING(RAND(),5,2),

iv_depot_id,SUBSTRING(RAND(),4,2)*iv_packet_id,DATE_ADD(NOW(),INTERVAL -SUBSTRING(RAND(),2,3) DAY),DATE_ADD(NOW(),INTERVAL -SUBSTRING(RAND(),3,2) DAY)

);

SET iv_packet_id=iv_packet_id-1;

END WHILE;

SET iv_packet_id=20;

SET iv_depot_id=iv_depot_id-1;

END WHILE ;

 

COMMIT;

SET iv_depot_id=10;

SET iv_goods_id=iv_goods_id-1;

END WHILE ;

END $$

DELIMITER ;

 

n  业务逻辑描述

l  非注册用户,或网站的注册用户不登录,都能可选购买物品,生成订单号对应的用户UID为系统默认的;

l  订单与用户UID关联、描述等信息,存储其它的表中,经过订单号的模式关联;

l  用户的订单信息,在未付款以前均可以再修改,付款以后则没法修改;

l  已经付费的订单信息,自动发送到物流部门,进行后续工序的操做。处理完毕以后,会更新订单中涉及物品的存储位置信息;

l  按期读取部分数据到数据仓库分析系统,用于统计分析;

l  我的订单查询,先后台都有;

l  购物记录查询显示;

 

n   根据业务规则描述须要使用操纵数据的SQL语句

(1).      EXPLAIN SELECT * FROM goods_order WHERE `order_id`=40918986;

(2).      SELECT * FROM goods_order WHERE `order_id` IN (40918986,40717328,30923040…) ORDER BY gmt_modify DESC;

(3).      UPDATE goods_order SET gmt_modify=NOW(),…. WHERE  `order_id`=40717328 AND goods_id=4248;

(4).      SELECT COUNT(*) FROM goods_order WHERE depot_id=0 ORDER BY gmt_modify DESC LIMIT 0,50;

(5).      SELECT * FROM goods_order WHERE depot_id=6 AND packet_id=0 ORDER BY gmt_modify DESC LIMIT 0,50;

(6).      SELECT COUNT(*) FROM goods_order WHERE goods_id=4248 AND order_status=0 AND order_type=1

(7).      SELECT * FROM goods_order WHERE goods_id=4248 AND order_status=0 AND order_type=1 ORDER BY gmt_modify DESC LIMIT 0,50;

(8).      SELECT * FROM goods_order WHERE gmt_modify>=’ 2011-04-06’;

8SQL语句按触发其执行的用户分类:

l   前台用户点击触发的操做而会执行的SQL语句为:(1)、(2)、(3);

l   后台内部用户点击触发的操做而会执行的SQL语句为:(1)、(2)、(3)、(4)、(5)、(6)、(7);

l   后台系统自动按期执行:(4)、(5)、(6)、(7),工做时间正常状况每隔15分钟执行一次,以检查是否有已付款而没有准备货物的订单、是否有收款而未发货的订单等;

l  统计分析系统按期导出数据而执行的SQL语句为:(8),频率为每24小时一次;

咱们再分析上述列出来的SQL,分为2类,一类是读操做的SQL(备注:SELECT操做),另一类为修改性操做(备注:UPDATE、DELETE操做),分别以下:

SELECT 的WHERE子句、GROUP BY子、ORDER BY 子句和HAVING 子句中,出现的字段:

(1).      order_id

(2).      order_id+gmt_modify

(3).      depot_id+gmt_modify

(4).      depot_id+packet_id+gmt_modify

(5).      goods_id+order_status+order_type

(6).      goods_id+order_status+order_type+gmt_modify

(7).      gmt_modify

修改性操做的WHERE子句中出现的条件字段:

(8).     order_id+ goods_id

 

咱们已经存在主键索引:PRIMARY KEY(order_id,`goods_id`),另外考虑到此表数据的操做以SELECT和INSERT为主,UPDATE的SQL量其次,再根据上述SQL语句,为此咱们能够初步肯定须要建立的索引:

ALTER TABLE goods_order

ADD INDEX idx_goodsID_orderType_orderStatus_gmtmodify(goods_id,order_type,order_status,gmt_modify),

ADD INDEX idx_depotID_packetID_gmtmodify(depot_id,packet_id,gmt_modify);

 

总结:

文章中也分析了为什么联合主键索引的顺序为:order_id,`goods_id`,再补充下做为主键的联合索引的字段属性的其余特性:字段值写入以后不变化、字段值长度短且最好为数值类型;

对于编号SQL:(8),天天按更新日期读取一次数据的操做,以采用全表扫描的方式实现,牺牲其数据读取的性能,以减小更新字段修改日期的值而带来的索引维护开销;

对于编号SQL:(4)、(5),考虑到每次都是读取最新的50条记录,以及读取的数据基本上可确定为热数据,为此不得不牺牲其中一条SQL的数据读取性能,而少建立一个联合索引,从而减小维护索引字段的IO量;

对于编号SQL:(6)、(7),建立的联合索引,须要特别注意联合索 引:idx_goodsID_orderType_orderStatus_gmtmodify(goods_id,order_type,order_status,gmt_modify) 中的字段顺序,其中:

l   goods_id字段的筛选率高于order_type,order_status,另外gmt_modify字段只出如今ORDER BY子句中,为此只有让goods_id字段做为联合索引的头部,以提升索引的筛选率,从而提升索引的效率,减小逻辑或物理的读。

l   order_status字段只有0或1两种值,而order_type有多种,以及根据SQL语句,必须order_type出如今联合中的位置要比order_status靠近头部;

l   gmt_modify字段出如今ORDER BY子句中,为此必须放到联合索引字段的最后;

 

最后,再梳理一下从需求到设计存储结构,再到编写SQL和建立索引结构,咱们应该作的步骤:

l   整理业务产生的数据流,读取数据的方式;

l   整理清楚数据流中的每一个数据项属性信息;

l   分析业务指标,推测须要存储数据的规模(备注:必定要以多少GB做为容量单位);

l   选择可能用于支持业务的硬件设备和数据库架构;

l   把全部可能操纵数据的条件和操做类型,都整理清楚;

l   分析操纵数据条件字段各自的数据筛选率;

l   权衡各个SQL的性能和IO量,也即相似于哪一个操做权重高一些,那些操做权重适当低一些;

l   建立索引组织结构;

l   收集测试和生产环境的反馈信息,优化索引组织结构;

 

备注:

本想再用测试环境结合业务的方式,跑一套模拟测试脚本程序,让你们更加直观地看到不一样索引组织状况下,相同的SQL操做及频率,数据库服务器的处理 能力和负载变化及对比信息,惋惜惟一的服务器没法使用了,只好放弃。对于分析相同的SQL,走不通索引,其须要的逻辑IO和物理IO量也是一个办法,这次 就不分析了,有须要的朋友能够去玩玩,另外建议初学者必定要好好阅读下mysql 手册上的相关章节内容:7.2.6. Index Merge Optimization。

相关文章
相关标签/搜索