存储过程不走索引的第二次记录
Posted 一头猪的奇妙旅行
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了存储过程不走索引的第二次记录相关的知识,希望对你有一定的参考价值。
1、故障现象
业务连接返回超时,数据库大量线程卡在了updating状态,锁等待十分严重,主机性能反而正常
2、初步的处理
查看错误日志,发现一个存储过程的执行时间很长,差不多1000s左右,打开存储过程,果然发现里面的某条语句就是卡在updating的语句;
初步定位到单条sql导致的数据库卡顿,分析存储过程里面的sql,对比表索引,利用explain获取sql执行计划,竟然是走了索引的。
3、环境介绍
由于是一个大量的并发业务,存储过程如下
delimiter $$ CREATE PROCEDURE `p_y_cn201701a1_init_chance`( i_usermobile VARCHAR (11), i_type varchar(16), OUT o_return INT) label_pro : BEGIN DECLARE p_curym INT ; declare p_curymd int; DECLARE p_count INT; DECLARE CONTINUE HANDLER FOR 1062 SET o_return = -2 ; SET p_curym = DATE_FORMAT(NOW(), ‘%Y%m‘) ; SET p_curymd = DATE_FORMAT(NOW(), ‘%Y%m%d‘) ; SET p_count = 0; SET o_return = 0; SELECT COUNT(1) INTO p_count FROM T_Y_CN201701A1_DRAWCE WHERE mobile = i_usermobile and curmonth = p_curym; if p_count = 0 then insert into T_Y_CN201701A1_DRAWCE(mobile,total,used,curmonth) values(i_usermobile,0,0,p_curym); END IF; if i_type = ‘query‘ then select count(1) into p_count from t_y_cn201701a1_query_config WHERE mobile = i_usermobile AND curday = p_curymd; if p_count = 0 then insert into t_y_cn201701a1_query_config(mobile,chance,curday,createtime) values(i_usermobile,2,p_curymd,now()); update T_Y_CN201701A1_DRAWCE set total = total + 2 where mobile = i_usermobile AND curmonth = p_curym; SET o_return = row_count(); end if; end if; if i_type = ‘share‘ then #分享 SELECT COUNT(1) INTO p_count FROM t_y_cn201701a1_share_config WHERE mobile = i_usermobile AND curday = p_curymd; IF p_count = 0 THEN INSERT INTO t_y_cn201701a1_share_config(mobile,chance,curday,createtime) VALUES(i_usermobile,1,p_curymd,NOW()); UPDATE T_Y_CN201701A1_DRAWCE SET total = total + 1 WHERE mobile = i_usermobile AND curmonth = p_curym; SET o_return = ROW_COUNT(); END IF; end if; COMMIT ; END */$$ DELIMITER ;
4、更深一层的处理
为了探究sql在执行的时候遇到了什么阻碍,决定新建一个类似的存储过程,用以判断存储过程内部是否有按照执行计划执行
CREATE DEFINER=`root`@`localhost` PROCEDURE `ptest1`( i_usermobile VARCHAR (11), OUT o_return INT) label_pro : BEGIN DECLARE p_curym INT ; declare p_curymd int; DECLARE p_count INT; DECLARE CONTINUE HANDLER FOR 1062 SET o_return = -2 ; SET p_curym = DATE_FORMAT(NOW(), ‘%Y%m‘) ; SET p_curymd = DATE_FORMAT(NOW(), ‘%Y%m%d‘) ; SET p_count = 0; SET o_return = 0; explain select * from test1 where mobile = i_usermobile AND curmonth = p_curym; commit; end
执行结果果然出现猫腻
查看到返回的结果
mysql> CALL ptest2(‘13654390105‘,@com_mysql_jdbc_outparam_o_return); +----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+ | 1 | SIMPLE | test2 | NULL | ALL | NULL | NULL | NULL | NULL | 99847 | 10.00 | Using where | +----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+ 1 row in set (0.02 sec) Query OK, 0 rows affected (0.02 sec)
继续分析存储过程里面的sql,由于UPDATE T_Y_CN201701A1_DRAWCE SET total = total + 1 WHERE mobile = i_usermobile AND curmonth = p_curym;
该句sql是全表扫描并且会进行表锁的,加上事务不能即时完成,导致大量的后续事务的锁等待,对该句sql进行优化,令其走主键索引即
SELECT id INTO p_index FROM T_Y_CN201701A1_DRAWCE WHERE mobile = i_usermobile AND curday = p_curymd;
UPDATE T_Y_CN201701A1_DRAWCE SET total = total + 1 WHERE id=p_id;
5、现象观察
锁等待消失,仍旧存在慢日志,时间将为30s左右,mysql线程大量卡在sending data状态,主机cpu使用率飙到90+,io紧张
6、继续处理
什么原因导致的存储过程里面的sql不走索引呢,在老大的提醒下,考虑由于字符集引起的原因
由于存储过程的字符集是utf8
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_bin
但是sql对应的表的字符集是
mysql> show create table T_Y_CN201701A1_DRAWCE\G
*************************** 1. row ***************************
Table: T_Y_CN201701A1_DRAWCE
Create Table: CREATE TABLE `T_Y_CN201701A1_DRAWCE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`mobile` varchar(13) CHARACTER SET utf8 DEFAULT NULL,
`total` int(1) NOT NULL DEFAULT ‘0‘ COMMENT ‘抽奖机会总数‘,
`used` int(1) NOT NULL DEFAULT ‘0‘ COMMENT ‘使用的抽奖机会‘,
`curmonth` int(11) NOT NULL,
`curday` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `t_y_cn201701a1_drance_index1` (`mobile`,`curday`)
) ENGINE=InnoDB AUTO_INCREMENT=3257351 DEFAULT CHARSET=gbk COMMENT=‘抽奖机会记录表‘
1 row in set (0.00 sec)
是gbk,不是统一的,尝试更改mobile列的字符集,再次查看存储过程的执行计划
ALTER TABLE test1 mobile VARCHAR(13) CHARACTER SET utf8;
再次进行flag存储过程的调用
mysql> CALL ptest1(‘13654390105‘,@com_mysql_jdbc_outparam_o_return); +----+-------------+-------+------------+-------+------------------------+------------------------+---------+-------------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+------------------------+------------------------+---------+-------------+------+----------+-------+ | 1 | SIMPLE | test1 | NULL | const | PK_Y_CN201612A1_DRAWCE | PK_Y_CN201612A1_DRAWCE | 46 | const,const | 1 | 100.00 | NULL | +----+-------------+-------+------------+-------+------------------------+------------------------+---------+-------------+------+----------+-------+ 1 row in set (0.00 sec) Query OK, 0 rows affected (0.00 sec)
如上图所示,开始使用索引了
6、现象观察
慢日志消失,业务正常,所以真是字符集造成的原因了
以上是关于存储过程不走索引的第二次记录的主要内容,如果未能解决你的问题,请参考以下文章
MySQL 存储过程在同一连接上的第二次调用失败,“SELECT command denied to user 'user'@'localhost' for table 'view_name'