存储过程不走索引的第二次记录

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'

MYSQL存储引擎InnoDB(二十三):排序索引构建

SQL语句优化mysql不走索引的原因数据库索引的设计原则

SQL语句优化mysql不走索引的原因数据库索引的设计原则

第二次调用存储过程抛出ORA异常

我的第二次博客