Mysql SQL优化查询效率

近期在项目中遇到一个功能模块数据量稍大的地方,本地开发和测试功能都没啥问题,上了正式环境,问题就体现出来了,因为正式环境需要从第三方导入很多数据,然后正式环境的列表加载基本要几十秒甚至根本加载超时(微服务连接超过一定时间自动断开),其实这里数据开始并不是很大,t_reg_hypertension,t_patient_health ,t_visit_hypertension数据量分别是2w,15w,53w,随着数据量增大,后续的优化工作还会继续

1、第一次优化

这个项目很多地方都需要和基础健康档案进行关联,开始的sql是这样的,这里的优化是忽略了中间动态sql

select distinct a.id,a.patient_id,a.hospital_id,a.personal_file_number,a.patient_name,a.live_type,a.age,a.card_number,a.telephone,a.crowd_label,a.address,a.diagnosis_date,a.data_from,
        a.duty_doctor_name,a.management_unit,a.status,
        (select count(id) from t_visit_hypertension where patient_id=a.patient_id and del_flag=0 and YEAR(create_time)=YEAR(NOW())) visitTimes,
        (select visit_date from t_visit_hypertension where patient_id=a.patient_id and del_flag=0 order by visit_date DESC LIMIT 0,1) lastServiceTime
        from t_reg_hypertension a INNER JOIN t_patient_health ph on a.patient_id=ph.patient_id and a.hospital_id=ph.hospital_id
        where
            a.del_flag=0 and a.status=0 and ph.status=0
        order by a.create_time desc,id limit 0,10

开始的时候打开数据库都很卡,

//查看当前MySQL在进行的线程,包括线程的状态、是否锁表等,可以实时地查看 SQL 的执行情况
show processlist;

 当时看到有很多连接,而且都很耗时,执行下面命令,结束无关的连接,然后数据库就不会卡顿了

//结束连接
kill id;

执行explain执行情况

可以看到没有用到索引,都是扫描全表 

开始想到的就是建索引,然后先把两张表关联的字段建立联合索引

CREATE INDEX index_t_patient_health_1 ON t_patient_health (hospital_id, patient_id);
CREATE INDEX index_t_reg_hypertension_1 ON t_reg_hypertension (hospital_id, patient_id);

查看索引

show index from t_reg_hypertension

这样稍微比之前好一些,但还是慢,需要10多秒的时间,下面可以看出已经用到索引了

2、第二次优化

细看了一下sql语句,发现有两个字段随访次数 visitTimes 和最后服务时间 lastServiceTime 是统计另外一张表的数据,这张表的数据量更大,所以很慢。然后重新优化sql,思路是把子查询语句删除了,先把分页10条数据查出来然后再去统计

 java代码逻辑

    @Override
    public PageVO<RegDiabetesDTO> page(RegDiabetesQueryReq req) {
        PageVO<RegDiabetesDTO> page = regDiabetesService.page(req);
        if (page.getList() == null || page.getList().size() <= 0) {
            return page;
        }
        List<Map<String, String>> mapList = new ArrayList<>();
        page.getList().forEach(item -> {
            Map<String, String> map = new HashMap<>();
            map.put("hospitalId", item.getHospitalId());
            map.put("patientId", item.getPatientId());
            mapList.add(map);
        });
        //查询出随访次数 visitTimes
        List<Map<String, Object>> visitTimesList = visitDiabetesService.getVisitTimesByPatientId(mapList);
        Map<String, Integer> visitTimesMap = new HashMap<>();
        visitTimesList.forEach(item -> {
            if (item.get("visitTimes") != null) {
                visitTimesMap.put(item.get("hospitalId") + "" + item.get("patientId"), Integer.valueOf(item.get("visitTimes").toString()));
            }
        });
        //查询出最后一次服务时间 lastServiceTime
        List<Map<String, Object>> lastServiceTimeList = visitDiabetesService.getLastServiceTimeByPatientId(mapList);
        Map<String, Date> lastServiceTimeMap = new HashMap<>();
        lastServiceTimeList.forEach(item -> {
            if (item.get("lastServiceTime") != null) {
                lastServiceTimeMap.put(item.get("hospitalId") + "" + item.get("patientId"), DateUtils.parseDate(item.get("lastServiceTime").toString()));
            }
        });
        //把获取到的随访次数和最后一次访问时间设置到list中
        page.getList().forEach(item -> {
            item.setVisitTimes(visitTimesMap.get(item.getHospitalId() + "" + item.getPatientId()));
            item.setLastServiceTime(lastServiceTimeMap.get(item.getHospitalId() + "" + item.getPatientId()));
        });
        return page;
    }

 下面是两个统计的sql,这里也需要把hospital_id,patient_id建立联合索引

<resultMap id="ResultMapNew" type="map">
        <result column="hospital_id" property="hospitalId" jdbcType="VARCHAR"/>
        <result column="patient_id" property="patientId" jdbcType="VARCHAR"/>
        <result column="visitTimes" property="visitTimes" jdbcType="INTEGER"/>
        <result column="lastServiceTime" property="lastServiceTime" jdbcType="TIMESTAMP"/>
    </resultMap>
    <select id="getVisitTimesByPatientId" resultMap="ResultMapNew">
        select hospital_id,patient_id,count(1) visitTimes from t_visit_hypertension
        <where>
            (
            <foreach collection="mapList" item="item" open="(" separator="or" close=")">
                hospital_id=#{item.hospitalId} and patient_id=#{item.patientId}
            </foreach>
            )
            and del_flag=0 and YEAR(create_time)=YEAR(NOW())
        </where>
        GROUP BY hospital_id,patient_id
    </select>

    <select id="getLastServiceTimeByPatientId" resultMap="ResultMapNew">
        select hospital_id,patient_id,Max(visit_date) lastServiceTime from t_visit_hypertension
        <where>
            (
            <foreach collection="mapList" item="item" open="(" separator="or" close=")">
                hospital_id=#{item.hospitalId} and patient_id=#{item.patientId}
            </foreach>
            )
            and del_flag=0
        </where>
        GROUP BY hospital_id,patient_id
    </select>

主表sql

select distinct a.id,a.patient_id,a.hospital_id,a.personal_file_number,a.patient_name,a.live_type,a.age,a.card_number,a.telephone,a.crowd_label,a.address,a.diagnosis_date,a.data_from,
        a.duty_doctor_name,a.management_unit,a.status 
        from t_reg_hypertension a INNER JOIN t_patient_health ph on a.patient_id=ph.patient_id and a.hospital_id=ph.hospital_id
        where
            a.del_flag=0 and a.status=0 and ph.status=0
        order by a.create_time desc,id limit 0,10

现在就能看出来,索引用到了,而且没有子查询,这样查询就比较快了,差不多需要0.5秒,但是还是能看出来有一张表扫描了全表为什么呢?

3、第三次优化

网上查了资料发现,两张表关联, 会去全表扫描数量小的表

  • Mysql内部的优化,使用小表驱动大表;

  • 它在估算到必须有一个表要全表扫描的话,一定会选择那个数据量更小的表去全表扫描;

  • 在这个查询中,因为on以后的where条件列并没有使用到索引;

  • 所以mysql的优化只用到了第二张表的索引,没有用到第一张表的索引。

所以这里要从where入手,根据查询条件来优化第一张表,这里建立一个联合索引,随带把order by也一起优化了,考虑到查询语句基本都需要del_flag,status这两个字段,排序也是非常耗时的,所以建立联合所有

CREATE index index_t_reg_hypertension_2 on t_reg_hypertension(del_flag,status,create_time);

这里要调整查询语句的顺序,联合索引遵循:最左前缀法则

select distinct a.id,a.patient_id,a.hospital_id,a.personal_file_number,a.patient_name,a.live_type,a.age,a.card_number,a.telephone,a.crowd_label,a.address,a.diagnosis_date,a.data_from,
        a.duty_doctor_name,a.management_unit,a.status 
        from t_reg_hypertension a INNER JOIN t_patient_health ph on a.patient_id=ph.patient_id and a.hospital_id=ph.hospital_id
        where
             ph.status=0 and a.del_flag=0 and a.status=0
        order by a.create_time desc,id limit 0,10

 但是还发现,排序的索引未生效,因为create_time是desc,而id是asc,索引生效需要同时升序和同时降序

4、第四次优化

select distinct a.id,a.patient_id,a.hospital_id,a.personal_file_number,a.patient_name,a.live_type,a.age,a.card_number,a.telephone,a.crowd_label,a.address,a.diagnosis_date,a.data_from,
        a.duty_doctor_name,a.management_unit,a.status 
        from t_reg_hypertension a INNER JOIN t_patient_health ph on a.patient_id=ph.patient_id and a.hospital_id=ph.hospital_id
        where
             ph.status=0 and a.del_flag=0 and a.status=0
        order by a.create_time desc,id desc limit 0,10

5、第五次优化

还是有Using temporary,这里会建立临时表,查了很多资料,终于发现问题出现在去重 distinct 关键字,把这个去掉就ok,而且非常快,只需要0.01秒左右,最后优化效果。这里会发现一个很奇妙的情况,有distinct不排序效率很高,distinct 和order by 同时存在就会很慢,因为distinct会创建一张临时表然后再来排序

select a.id,a.patient_id,a.hospital_id,a.personal_file_number,a.patient_name,a.live_type,a.age,a.card_number,a.telephone,a.crowd_label,a.address,a.diagnosis_date,a.data_from,
        a.duty_doctor_name,a.management_unit,a.status 
        from t_reg_hypertension a INNER JOIN t_patient_health ph on a.patient_id=ph.patient_id and a.hospital_id=ph.hospital_id
        where
             ph.status=0 and a.del_flag=0 and a.status=0
        order by a.create_time desc,id desc limit 0,10

总结

(1)字符串不加单引号,索引会失效

(2)不以%开头的Like模糊查询,虽然是范围查询,但是后面跟着的索引一样会生效,order by 后面排序的索引会失效

(3)where 后面只会使用最优的一个索引,当多条件时会建立复合索引

(4)distinct 和order by 同时存在效率会很慢,因为distinct会创建一张临时表然后再来排序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值