这周就要从泰笛离职了,在公司内部的wiki上,根据公司实际的项目,写了一些mysql的优化方法,供小组里的小伙伴参考下,没想到大家的热情很高,还专门搞了个ppt讲解了一下。

举了三个大家很容易犯错的地方,之前项目中也存在,后期修正了。

MySQL优化实例(一)

以我们项目中一个sql语句为例,展示sql语句优化的步骤。

webService项目中有个getWashXiaogeId的封装方法,用于分配配送小哥。

其中,有一段逻辑是查询某个小哥当前的配送订单量(绿植+洗涤)。查询慢查询日志时发现,这个sql的执行效率很差。

-- 定义变量
SET @todayZero = DATE_FORMAT(now(), '%Y-%m-%d 00:00:00');
SET @tomorrowZero = DATE_FORMAT(
FROM_UNIXTIME(
UNIX_TIMESTAMP(now()) + 86400
),
'%Y-%m-%d 00:00:00'
);
SET @takeUserId = 255;
-- 查询某个小哥当前的配送数
SELECT
count(0) AS count
FROM
`tidy_plant_order`
WHERE
`current_takeuserid` = @takeUserId
AND `create_datetime` > @todayZero
AND `create_datetime` < @tomorrowZero
UNION ALL
SELECT DISTINCT
count(*) AS count
FROM
`tidy_session_order`
WHERE
(
`takeuserid` = @takeUserId
OR `yunuserid` = @takeUserId
)
AND `create_datetime` > @todayZero
AND `create_datetime` < @tomorrowZero

我们在explain这个select语句发现:

在查询洗涤订单表的时候,MySQL进行了全表扫描,这意味着MySQL必须扫描整张表,从头到尾,去找到需要的行。

这条sql优化的地方很多,例如:

0)可以将or关键字改为in或者union;

1)可以将union拆分成N条简单的sql查询,避免MySQL使用到临时表,在逻辑层求和;

2)可以根据实际的业务模型,添加索引或修改当前错误的索引;

3)若2)代价较高,则可以基于现有的索引,优化sql语句,尽可能使用到目前已经存在的索引;

我们将tidy_session_order的现有索引贴出:

发现,有两个多列索引index_st和index_sy有我们需要的字段,即takeuserid和yunuserid。(这两个索引本身是有问题的,status的选择性比takeuserid/yunuserid要差,如果要建多列索引,也是应该takeuserid/yunuserid为第一列,status为第二列)。

所以我们将现有的sql优化成以下的样子:

-- 定义变量
SET @todayZero = DATE_FORMAT(now(), '%Y-%m-%d 00:00:00');
SET @tomorrowZero = DATE_FORMAT(
FROM_UNIXTIME(
UNIX_TIMESTAMP(now()) + 86400
),
'%Y-%m-%d 00:00:00'
);
SET @takeUserId = 255;
-- 查询某个小哥当前的配送数
SELECT
COUNT(*) AS count
FROM
tidy.`tidy_plant_order`
WHERE
`current_takeuserid` = @takeUserId
AND `create_datetime` BETWEEN @todayZero
AND @tomorrowZero
UNION ALL
SELECT DISTINCT
COUNT(*) AS count
FROM
tidy.`tidy_session_order`
WHERE
`status` IN (0, 1, 2, 3, 4, 5, 7, 9, 10, 11, 12)
AND `yunuserid` = @takeUserId
AND `takeuserid` != @takeUserId
AND `create_datetime` BETWEEN @todayZero
AND @tomorrowZero
UNION ALL
SELECT DISTINCT
COUNT(*) AS count
FROM
tidy.`tidy_session_order`
WHERE
`status` IN (0, 1, 2, 3, 4, 5, 7, 9, 10, 11, 12)
AND `takeuserid` = @takeUserId
AND `create_datetime` BETWEEN @todayZero
AND @tomorrowZero

explain这个sql发现:

查询时间从原来的0.5-1s 降至了 0.01-0.02s。

MySQL优化实例(二)

本实例以短信验证码为例,展示创建覆盖索引的优势。

覆盖索引的定义:

如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。

实例:

我们的app在登陆时,需要获取验证码,验证通过才能使用我们的服务。在发送短信的同时,我们会在记录表中插入一个条记录,用户在输入验证码后,以此来判断用户输入值是否正确。

SET @phone = 13651999158;
SET @code = 123456;
SELECT
count(0)
FROM
tidy_verification_code_log
WHERE
phone = @phone
AND `code` = @code
AND `timestamp` > UNIX_TIMESTAMP(NOW()) - 120000;

我们explain发现:

MySQL通过全表扫描的方式,查找需要的那条记录,效率极差。

我们可以通过创建一个覆盖索引的方式来进行优化。

alter table tidy_verification_code_log add index`index_pct`(`phone`, `code`, `timestamp`);

将此前的SQL优化成:

SET @phone = 13651999158;
SET @code = 123456;
SELECT
`timestamp`
FROM
tidy_verification_code_log
WHERE
phone = @phone
AND `code` = @code
-- 然后在逻辑层判断timestamp是否超时

explain显示:

在Extra列中,如果出现Using index,则表示MySQL将使用覆盖索引,以避免访问表。

覆盖索引的优势:

当索引中的列包含所有查询中需要使用的列的时候,MySQL就可以使用索引返回需要的数据,而无须查询对应的数据行。由于InnoDB的聚簇索引特性,覆盖索引对InnoDB表特别有用。InnoDB的二级索引在叶子节点中保存了行的主键值,所以如果二级索引能够覆盖查询,则可以避免对主键索引的二次查询。

MySQL优化实例(三)

本实例展示如何正确地创建一个多列索引(或者称为联合索引)。

为每个列创建独立的索引或者按照错误的顺序创建多列索引,是常见的错误索引策略。

索引的选择性:

索引的选择性是指,不重复的索引值(也称为基数,cardinality)和数据表的记录的总数(#T)的比值,范围从1/#T到1之间。索引的选择性越高则查询效率越高,因为选择性高的所以可以让MySQL在查找时过滤掉更多的行。唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

错误示例1,不应该建多列索引:

tidy_barcode表中有一个多列索引index_si:

CREATE TABLE `tidy_barcode` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`orderid` int(11) NOT NULL COMMENT '订单号',
`session_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '预订单编号',
`gpid` int(11) NOT NULL DEFAULT '0' COMMENT '洗涤衣物的总分类',
`gcid` int(11) NOT NULL DEFAULT '0' COMMENT '洗涤衣物的子分类',
`goodid` int(11) NOT NULL COMMENT '商品id',
`goods` varchar(100) NOT NULL DEFAULT '' COMMENT '洗涤衣物的称呼',
`goodstype` varchar(50) NOT NULL DEFAULT '' COMMENT '洗涤衣物的类型',
`num` decimal(4,2) unsigned NOT NULL,
`price` decimal(11,2) NOT NULL DEFAULT '0.00' COMMENT '洗涤衣物价格',
`disprice` decimal(11,2) NOT NULL DEFAULT '0.00' COMMENT '洗涤衣物折扣价',
`realprice` decimal(11,2) NOT NULL DEFAULT '0.00' COMMENT '洗涤衣物实际价',
`goods_item_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '订单明细表对应的ID',
`barcode` varchar(30) NOT NULL DEFAULT '' COMMENT '条码',
`originbarcode` varchar(30) NOT NULL DEFAULT '' COMMENT '初始条码',
`goodimg` varchar(512) NOT NULL DEFAULT '' COMMENT '商品图片',
`goodinfo` varchar(100) NOT NULL DEFAULT '' COMMENT '洗涤衣物的备注信息',
`status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '每件衣物的状态',
`ifscan` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否已扫描',
`ifnormal` tinyint(4) DEFAULT NULL COMMENT '0无异常,1异常,2异常修复,3取消,4改码,5加件',
`changemark` varchar(100) NOT NULL DEFAULT '' COMMENT '修改备注',
`mark` varchar(100) NOT NULL COMMENT '异常备注',
`userid` int(11) NOT NULL DEFAULT '0' COMMENT '异常提交者',
`createdatetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '创建时间',
`goods_mark` int(10) NOT NULL DEFAULT '1' COMMENT '1、正常价格衣物。2、童装类型衣物',
PRIMARY KEY (`id`),
KEY `index_se` (`session_id`),
KEY `index_si` (`status`,`ifnormal`),
KEY `index_b` (`barcode`)
) ENGINE=InnoDB AUTO_INCREMENT=591457 DEFAULT CHARSET=utf8;

status,ifnormal的字段本身选择性就很差,不适合作为索引或者多列索引的第一列,就像我们一般不会在性别这一列添加索引(当然也有例外)。

错误示例2,多列索引顺序错误:

tidy_session_order表中有一个多列索引index_st:
CREATE TABLE `tidy_session_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`show_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '订单编号',
`phone` bigint(15) NOT NULL DEFAULT '0' COMMENT '用户手机号',
`cname` varchar(30) NOT NULL DEFAULT '' COMMENT '客户名',
`totalprice` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '洗涤总价',
`disprice` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '折扣价格',
`realprice` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '实际价格',
`ispay` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否支付:0为未支付;1为已支付;',
`integral` int(11) NOT NULL DEFAULT '0' COMMENT '积分',
`shou_serverid` int(11) NOT NULL DEFAULT '0' COMMENT '收去网点编号',
`song_serverid` int(11) NOT NULL DEFAULT '0' COMMENT '派送网点编号',
`lat` varchar(20) NOT NULL DEFAULT '0' COMMENT '经度',
`lng` varchar(20) NOT NULL DEFAULT '0' COMMENT '纬度',
`city_id` int(6) NOT NULL DEFAULT '0' COMMENT '城市编号',
`shou_address` varchar(100) NOT NULL DEFAULT '' COMMENT '收件地址',
`song_address` varchar(100) NOT NULL DEFAULT '' COMMENT '送件地址',
`coe_state` int(11) NOT NULL DEFAULT '0' COMMENT '多状态标示 ',
`is_appoint` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否为预约订单 0为非预约 1为预约订单',
`appoint_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '预约的时间点',
`equ_type` char(10) NOT NULL DEFAULT '' COMMENT '设备类型 ios 、android',
`equ_id` varchar(60) NOT NULL DEFAULT '' COMMENT '设备唯一标识',
`equ_info` varchar(255) NOT NULL DEFAULT '' COMMENT '设备信息 json格式类型',
`create_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '创建日期',
`take_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '取件时间',
`order_id` int(11) NOT NULL DEFAULT '0' COMMENT '订单ID',
`status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '订单状态对应tidy_base_info表里的pid=18信息',
`previsit_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '预计上门时间',
`lry_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '送洗时间',
`sor_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '分拣时间',
`yun_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '派送时间',
`done_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '订单的完成时间;即为status为4时的时间点',
`done_type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '签收类型 1为图片签收 2为音频签收',
`done_verify` varchar(255) NOT NULL DEFAULT '' COMMENT '签收文件的保存路径',
`operator_id` int(11) NOT NULL DEFAULT '0' COMMENT '操作人 0为用户自己取消 其他为操作人的编号',
`mark` varchar(100) NOT NULL DEFAULT '' COMMENT '备注信息',
`evaluation` tinyint(1) NOT NULL DEFAULT '0' COMMENT '评价星级;1为非常差;2为差;3为一般;4为满意;5为非常满意;',
`evaluation_mark` varchar(60) NOT NULL DEFAULT '' COMMENT '评价内容:50个字以内;',
`evaluation_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '评价的时间',
`takeuserid` int(11) NOT NULL DEFAULT '0' COMMENT '取件人id',
`okuserid` int(11) NOT NULL DEFAULT '0' COMMENT '加工人id',
`yunuserid` int(11) NOT NULL DEFAULT '0' COMMENT '送件人id',
`ifnormal` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否异常',
`version` varchar(50) NOT NULL DEFAULT '' COMMENT '版本标识:1.5',
`is_cash` tinyint(1) NOT NULL DEFAULT '0' COMMENT '标记是否现金支付',
`is_del` tinyint(1) NOT NULL DEFAULT '0' COMMENT '标注订单是否删除 1为是 0为否',
`order_source` varchar(20) NOT NULL DEFAULT '泰笛' COMMENT '订单来源',
`third_discount` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '三方优惠金额,现金支付时扣除该金额',
`settle_accounts` tinyint(1) NOT NULL DEFAULT '0' COMMENT '第三方订单是否已结算',
`modify_datetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '数据发生修改的时间',
`abnormal` int(2) DEFAULT '0' COMMENT '异常标记,0为正常状态,1为部分返洗,2为全部返洗',
`ispart` int(1) NOT NULL DEFAULT '0' COMMENT '是否部分签收',
`is_sign` tinyint(1) NOT NULL DEFAULT '0' COMMENT '客户是否签收',
`isperiod` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否周期洗 1为true 0为false',
PRIMARY KEY (`id`),
KEY `index_st` (`status`,`takeuserid`),
KEY `index_sy` (`status`,`yunuserid`),
KEY `index_so` (`status`,`okuserid`),
KEY `index_show` (`show_id`),
KEY `index_pc` (`phone`,`create_datetime`)
) ENGINE=InnoDB AUTO_INCREMENT=4774982 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='洗涤预订单表';
takeuserid的选择性比status要大,如果一定要创建这两个字段的多列索引的话,takeuserid在第一列,status在第二列。

通过下面这个SQL即可比较多个字段的选择性差异:

SELECT
COUNT(DISTINCT id) / COUNT(0) AS id,
COUNT(DISTINCT phone) / COUNT(0) AS phone,
COUNT(DISTINCT takeuserid) / COUNT(0) AS takeuserid,
COUNT(DISTINCT `status`) / COUNT(0) AS `status`,
COUNT(0)
FROM
tidy_session_order

查询结果显示:

错误示例3,滥用多列索引:

tidy_api_log表主要作用是,通过订单号或者手机号来查询相关的接口调用日志。表中index_op索引的创建时错误的。

CREATE TABLE `tidy_api_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`phone` bigint(20) NOT NULL DEFAULT '0' COMMENT '手机号',
`order_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '订单号',
`api_controller` varchar(50) NOT NULL DEFAULT '' COMMENT '控制器',
`api_action` varchar(50) NOT NULL DEFAULT '' COMMENT '方法名 ',
`input_params` varchar(2000) NOT NULL DEFAULT '' COMMENT '输入参数',
`output_message` varchar(255) NOT NULL DEFAULT '' COMMENT '输出信息',
`output_params` varchar(2000) NOT NULL DEFAULT '输出参数',
`output_status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '调用状态,1成功or0失败',
`create_datetime` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '记录插入时间',
PRIMARY KEY (`id`),
KEY `index_op` (`order_id`,`phone`)
) ENGINE=InnoDB AUTO_INCREMENT=2542672 DEFAULT CHARSET=utf8 COMMENT='v2.0接口调用日志';

根据上述的业务表述,应该分别对order_id和phone创建独立的索引或去其他字段联合建立多列索引。

MySQL优化实例的更多相关文章

  1. mysql 优化实例之索引创建

    mysql 优化实例之索引创建 优化前: pt-query-degist分析结果: # Query 23: 0.00 QPS, 0.00x concurrency, ID 0x78761E301CC7 ...

  2. (转)MySQL优化实例

    在Apache, PHP,MySQL的体系架构中,MySQL对于性能的影响最大,也是关键的核心部分.对于Discuz!论坛程序也是如此,MySQL的设置是否合理优化,直接影响到论坛的速度和承载量!同时 ...

  3. mysql sql优化实例

    mysql sql优化实例 优化前: pt-query-degist分析结果: # Query 3: 0.00 QPS, 0.00x concurrency, ID 0xDC6E62FA021C85B ...

  4. MySQL多实例部署与优化

    MySQL安装 ##上传MySQL安装包## mkdir /home/oldboy/tools -p cd /home/oldboy/tools/ ###wget -q http://mirrors. ...

  5. MySQL索引优化实例说明

    下面分别创建三张表,并分别插入1W条简单的数据用来测试,详情如下: [1] test_a 有主键但无索引   CREATE TABLE `test_a` (   `id` int(10) unsign ...

  6. MySQL优化聊两句

    原文地址:http://www.cnblogs.com/verrion/p/mysql_optimised.html MySQL优化聊两句 MySQL不多介绍,今天聊两句该如何优化以及从哪些方面入手, ...

  7. Mysql优化系列(2)--通用化操作梳理

    前面有两篇文章详细介绍了mysql优化举措:Mysql优化系列(0)--总结性梳理Mysql优化系列(1)--Innodb引擎下mysql自身配置优化 下面分类罗列下Mysql性能优化的一些技巧,熟练 ...

  8. [转] MySql 优化 大数据优化

    一.我们可以且应该优化什么? 硬件 操作系统/软件库 SQL服务器(设置和查询) 应用编程接口(API) 应用程序 ------------------------------------------ ...

  9. 单表60亿记录等大数据场景的MySQL优化和运维之道

    此文是根据杨尚刚在[QCON高可用架构群]中,针对MySQL在单表海量记录等场景下,业界广泛关注的MySQL问题的经验分享整理而成,转发请注明出处. 杨尚刚,美图公司数据库高级DBA,负责美图后端数据 ...

随机推荐

  1. Get和Post区别

    1. get是从服务器上获取数据,post是向服务器传送数据.2. get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到.post是通过H ...

  2. 数据库排序sql,order by

    一开始我认为 SELECT * FROM dbo.T_User ORDER BY CreateTime ,IsDel DESC 的执行顺序是先按创建时间倒序排序,再按isdel倒序排序,所以我就没再S ...

  3. 【原创】SQL分页查询存储过程

    ------------------------------------- -----作者:张欣宇 -----时间:2013-06-28 -----简介:根据参数和条件分页查询 ----------- ...

  4. WCF入门教程(三)定义服务协定--属性标签

    WCF入门教程(三)定义服务协定--属性标签 属性标签,成为定义协议的主要方式.先将最简单的标签进行简单介绍,以了解他们的功能以及使用规则. 服务协定标识,标识哪些接口是服务协定,哪些操作时服务协定的 ...

  5. [WCF编程]12.事务:事务协议与管理器

    一.事务协议 总体来说,WCF开发人员不需要涉及事务协议与管理器.我们应该依赖WCF来选择相应的事务协议和管理器,重点关注业务逻辑的实现. WCF是根据事务范围里的参与个体来选择事务管理协议的.事务管 ...

  6. [WCF编程]2.SOA概述

    一.SOA简介 1. SOA(面向服务架构)既是一种编程方式,也是软件开发的一种架构方法.根据这种架构方法,应用程序是由具有一定行为(称为服务)的功能单元组成的. 2. SOA的基本思想是构建一个粗粒 ...

  7. 【shell 大系】Linux Shell常用技巧

    在最近的日常工作中由于经常会和Linux服务器打交道,如Oracle性能优化.我们数据采集服务器的资源利用率监控,以及Debug服务器代码并解决其效率和稳定性等问题.因此这段时间总结的有关Linux ...

  8. PHP7 redis扩展安装

    1.安装redis (1)下载:https://github.com/phpredis/phpredis/tree/php7 或下载http://pan.baidu.com/s/1i5DFrjn用sa ...

  9. Lind.DDD.Repositories.EF层介绍

    回到目录 Lind.DDD.Repositories.EF以下简称Repositories.EF,之所以把它从Lind.DDD中拿出来,完全出于可插拔的考虑,让大家都能休会到IoC的魅力,用到哪种方法 ...

  10. 22、ASP.NET MVC入门到精通——搭建项目框架

    本系列目录:ASP.NET MVC4入门到精通系列目录汇总 前面的章节,说了ASP.NET MVC项目中常用的一些技术和知识点,更多的是理论上面的东西,接下来,我将通过一个简单的OA项目来应用我们之前 ...