1、先查看了一下慢日志中的内容,发现慢日志中没有具体的记录。这个问题比较好解决,其实他的本质是设定的慢日志的阈值是1s,只有超过1s的SQL语句才会被记录,这里我把参数long_query_time的值设置成为0.4,这样,就可以把查询超过0.4s的SQL都记录到慢日志里面了。
2、查看慢日志中的SQL语句,如下:
SELECT * FROM `account` WHERE `accountid` = 20000000528 and `accounttype` = 1 and `appid`=10005;
语句也比较简单,是根据某3列的值来查询一条记录,我先查看了一下结果集,结果集中只有一条记录,说明这个查询的结果是比较少的。
3、查看对应的表结构:
mysql--dba_admin 12:59:09>>show create table accountG
*************************** 1. row ***************************
Table: account
Create Table: CREATE TABLE `account` (
`appid` int(4) unsigned NOT NULL,
`uid` bigint(8) unsigned NOT NULL,
`accountid` char(32) NOT NULL DEFAULT '',
`accounttype` int(4) unsigned NOT NULL,
`mtime` bigint(8) unsigned NOT NULL DEFAULT '0',
`id` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
KEY `appid` (`appid`,`accountid`,`accounttype`,`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
可以看到,这个表有一个主键,一个普通索引,这个普通索引包含appid、accountid、accounttype以及uid这4个字段,是联合索引,查看了一眼表中的数据量,大概有5w多条,并不算很多。
4、初步修改意见
有了对表结构的认知和SQL语句,我当时的第一反应是这个SQL写的是否满足需求,问了一下业务方,他们说是为了查询uid写出来的SQL,那么其实这个SQL中的select *是不必要的,如果只要uid的值,则可以只查新uid即可,那么SQL语句其实可以被改写为:
SELECT uid FROM `account` WHERE `accountid` = 20000000528 and `accounttype` = 1 and `appid`=10005;
这样写,有一个好处,就是需要查询的uid和三个查询条件appid、accountid以及accounttype字段都在同一棵索引的B+树上,这样可以利用覆盖索引的原理,避免回表扫描。
5、这样测试了一把之后,业务方反馈说是并没有明显的降低查询时间。时间还是在0.3s左右,还是不能满足需求。这个时候,我已经开始怀疑这个表的索引创建的是否有问题了,于是首先查看了一眼表中的数据,看看appid的值都包含哪些吧。
mysql--dba_admin 12:02:25>>select distinct accounttype from account limit 10; +-------------+ | accounttype | +-------------+ | 1 | +-------------+ 1 row in set (0.02 sec) mysql--dba_admin 12:02:25>>select distinct appid from account limit 10; +-------------+ | appid | +-------------+ | 10005 | +-------------+ 1 row in set (0.02 sec)
经过查看,发现appid的值目前只包含一个10005,而accounttype的值也只包含数字1,而accountid和uid的值包含5w左右,这样问题已经有点明确了。
6、修改索引字段
我们再来看这个联合索引:
key appid(appid,accountid,accounttype,uid)
这样明显是不合理的,因为appid和accounttype的值基数太小,只有1,索引这个索引应该修改,具体的改法有很多,我是通过修改联合索引的先后顺序,将基数大的字段放在前面,这样扫描的时候能够过滤的更加准确一些。将索引改为:
key 'idx_accid' (accountid,uid,appid,accounttype)
这样,再来看一眼执行计划:
mysql--dba_admin 13:05:51>>explain SELECT uid FROM `account` WHERE `accountid` = 20000000528; +----+-------------+---------+-------+---------------+-----------+---------+------+-------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+-------+---------------+-----------+---------+------+-------+--------------------------+ | 1 | SIMPLE | account | index | idx_accid | idx_accid | 112 | NULL | 57237 | Using where; Using index | +----+-------------+---------+-------+---------------+-----------+---------+------+-------+--------------------------+ 1 row in set (0.00 sec)
这个问题比较奇怪,为什么看着走了索引,但是却扫描了这么多记录???改成单个字段的试试,这里我又把索引改成了:
KEY `idx_accid` (`accountid`)
只保留一个字段,看看执行结果:
mysql--dba_admin 13:12:17>>explain SELECT uid FROM `account` WHERE `accountid` = 20000000528 AND `accounttype` = 1 and `appid`=10005; +----+-------------+---------+------+---------------+------+---------+------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+------+---------------+------+---------+------+-------+-------------+ | 1 | SIMPLE | account | ALL | idx_accid | NULL | NULL | NULL | 57237 | Using where | +----+-------------+---------+------+---------------+------+---------+------+-------+-------------+ 1 row in set (0.00 sec)
我去,竟然不走索引,这就奇了怪了。
7、终于,我想了又想,猜了又猜,看到了一处细节。
建表语句如下:
mysql--dba_admin 13:16:24>>show create table accountG
*************************** 1. row ***************************
Table: account
Create Table: CREATE TABLE `account` (
`appid` int(4) unsigned NOT NULL,
`uid` bigint(8) unsigned NOT NULL,
`accountid` char(32) NOT NULL DEFAULT '',
`accounttype` int(4) unsigned NOT NULL,
`mtime` bigint(8) unsigned NOT NULL DEFAULT '0',
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
KEY `idx_accid` (`accountid`)
) ENGINE=InnoDB AUTO_INCREMENT=57055 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
SQL语句如下:
SELECT uid FROM `account`
WHERE
`accountid` = 20000000528
AND
`accounttype` = 1
and
`appid`=10005;
到这里,问题应该已经能够看出来了,这个字段accountid在表中应该是一个char类型的,但是在SQL语句中写成了整数类型,发生了隐式类型转换,导致索引不可用。
弄清楚了这个,再来看这两个执行计划:
mysql--dba_admin 13:17:54>>explain SELECT id FROM `account` WHERE `accountid` = 20000000528; +----+-------------+---------+-------+---------------+-----------+---------+------+-------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+-------+---------------+-----------+---------+------+-------+--------------------------+ | 1 | SIMPLE | account | index | idx_accid | idx_accid | 96 | NULL | 57237 | Using where; Using index | +----+-------------+---------+-------+---------------+-----------+---------+------+-------+--------------------------+ 1 row in set (0.00 sec) mysql--dba_admin 13:18:25>>explain SELECT id FROM `account` WHERE `accountid` = '20000000528'; +----+-------------+---------+------+---------------+-----------+---------+-------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+------+---------------+-----------+---------+-------+------+--------------------------+ | 1 | SIMPLE | account | ref | idx_accid | idx_accid | 96 | const | 1 | Using where; Using index | +----+-------------+---------+------+---------------+-----------+---------+-------+------+--------------------------+
其中扫面行数的区别最明显,一个是5w多行,一个是1行。所以,只需要将原来的SQL语句改为:
SELECT uid FROM `account`
WHERE
`accountid` = '20000000528'
AND
`accounttype` = 1
and
`appid`=10005;
就可以解决问题了。
8、到这里,问题基本上算是解决了,使用accountid作为索引的字段,这样在进行where条件过滤的时候,就可以过滤出很少的记录,然后再回表去通过id值查到想要的uid值,这样就比较快了。
最终的优化前后对比结果如图:
优化之前:
优化之后:
可以看到,效果还是很明显的。
今天的文章就到这里了。
继续阅读与本文标签相同的文章
MySQL中explain的结果字段介绍(1)
InnoDB表空间介绍(2)
-
【推荐】一文辨清SD-WAN的“多快好省”
2026-05-26栏目: 教程
-
Spring+quartz cron表达式(cron手册官方)完美理解
2026-05-26栏目: 教程
-
20160120使用myeclipse一年开始转IntelliJ IDEA 15做以下总结
2026-05-26栏目: 教程
-
三张照片解决--win10系统的edge浏览器设置为浏览器IE8,IE7,IE9---完美解决 费元星
2026-05-26栏目: 教程
-
java获取当前路径的几种方法
2026-05-26栏目: 教程
