记一次-接口调优过程
1、问题背景
运营反馈线上人工审核功能中的患者画像审核接口操作响应时间过长,一般都在20-50s左右徘徊,严重影响运营人员的审核效率。
引发接口画像分段结果保存接口:/v1/pb/portrait/saveSegmentPortraitInfo
2、优化过程
2.1、问题分析
通过测试人员生产环境演示数据进行审核操作,发现接口响应耗时为24687ms,详细日志如下图。
2.1.1、定位问题
- 请求时间
时间为:2024-03-19 18:00:39.773
- 请求响应时间
- 代码分析
经查看代码发现此接口为【业务管理服务】调用【画像服务】接口,最终处理逻辑在画像服务中。代码定位结果如下图
- 画像服务请求时间
- 画像服务响应时间
2.1.2、分析问题
从上面的分析可以看出,整体的耗时操作是在画像服务中,下面我们看看画像服务中的方法实现,方法如下图
如上图所示,所有红框内的操作均可能是耗时操作,下面我们一个个看。
- 方法1
下面的方法是个简单的查询操作,查询操作我们主要分析数据量和查询是否负责,查询条件是否具备多样性,是否有索引和创建索引。
patPortraitMapper.getPortraitInfoEntity(request.getHosCode(), request.getPatId(), request.getHosId(), null)
方法sql
<select
resultType="com.iflytek.pcm.profile.domain.model.PatPortraitInfoEntity">
select id as id,
hos_code as hosCode,
pat_id as patId,
hos_id as hosId,
version as version,
create_time as createTime,
update_time as updateTime,
audit_flag as auditFlag,
source_hos_id as sourceHosId,
concat('', portrait_data) as portraitData,
sign_date as signDate,
emr_type as emrType
from pat_portrait_info pi
where pi.hos_code = #{hosCode}
and pi.pat_id = #{patId}
and pi.hos_id = #{hosId}
<if test="emrType != null and emrType.trim().length() > 0">
and pi.emr_type = #{emrType}
</if>
order by version desc limit 1
</select>
从上面查询可以看出有hosCode,patId,hosId三个条件,然后我们看一下表的数据量有多少,以及建表语句是否存在索引。
经查询表中数据两位28000多条数据,但是主要查询条件hosCode,patId,hosId字段没有创建索引。依据创建索引的规则,其中pat_id,hosId属性具备创建索引的条件,可以提升查询效率。下面我们看看是否可以提升。
- 增加索引之前
- 增加索引后
从上面看来,增加索引查询性能提升还是比较显著的。
- 方法2
checkExistSegmentCode(String portraitId, String segmentKey)
同样的套路看数据量,看查询条件是否具备创建索引条件
<select resultType="java.lang.Integer">
selecT count(1)
from pat_portrait_segment_info
where segment_key = #{segmentKey}
and portrait_id = #{portraitId}
</select>
建表语句
经查询当前表数据量为50w+,但是此表中id属性为非主键属性且数据有重复,portrait_id也没有创建索引,同时这个表中id无索引,在没有主键和索引的情况下,MySQL 在执行查询时可能需要进行全表扫描来找到匹配的数据。这会导致查询效率降低,特别是当表中数据量较大时。同时更新效率也很低。
解决方案:为id和portrait_id创建普通索引以提升查询及更新效率。修改后表语句。提升效果验证结果略。后面看优化后整体效果。
ALTER TABLE `all_zhgl`.`pat_portrait_segment_info`
ADD INDEX `idx_id`(`id`) USING BTREE,
ADD INDEX `idx_portrait_id`(`portrait_id`) USING BTREE;
- 方法3
this.savePatSegmentInfo(portraitInfoEntity.getId(), segmentInfo, segmentKey, getPatSegmentPortraitInfoResponse.getSegmentCategory());
此方法为保存方法,在没有主键和索引的情况下,数据插入操作可能会变慢。因为 MySQL 可能需要进行全表扫描来确保新插入的数据不与现有数据产生冲突,你可能会有疑问,没有主键,没有索引还会检查数据冲突吗,答案是肯定的会,因为即使表没有主键或索引,MySQL 在插入数据时仍然会检查数据冲突。数据冲突检查确保了数据库中的数据完整性和唯一性,即使没有显式定义的主键或唯一索引。当表没有主键或唯一索引时,MySQL 在执行插入操作时会进行全表扫描,以确保新插入的数据不会与现有数据产生冲突。这种全表扫描可能会导致插入性能下降,尤其是在数据量较大的情况下。所以尽管 MySQL 会进行数据冲突检查,但在实际情况下,为表添加合适的主键或唯一索引仍然是一个良好的做法。通过定义主键或唯一索引,可以提高数据插入的性能,并确保数据的完整性和唯一性,从而更有效地管理数据。
解决方案:如方法2,创建索引。
- 方法4
patPortraitMapper.updatePatSegmentInfo(portraitInfoEntity.getId(), segmentKey, segmentInfo.toString());
sql
<update id="updatePatSegmentInfo">
update pat_portrait_segment_info
set segment_info =#{segmentInfo}
where segment_key = #{segmentKey}
and portrait_id = #{portraitId}
</update>
从上面的更新可以看出,更新条件为portrait_id,由于segment_key区分度不大不具备创建索引条件,portrait_id在上面查询中已增加索引。没有索引的情况下,更新操作也会受到影响,特别是涉及到大量数据更新时,更新操作可能需要遍历整个表来找到需要更新的行,从而导致性能下降。
为创建索引前更新效率如下图【此图为DBA从日志中获取的】
从上图可以看出这个更新操作花费了19s多,总锁定时间17.86s,检查数据量487429。
- Query time: 查询时间,表示执行这个查询所花费的总时间,单位通常是秒。在这里是 19.023395 秒。
- Lock time: 锁定时间,表示在执行这个更新时所花费的总锁定时间,单位通常也是秒。在这里是 17.861310 秒。
- Rows sent: 返回的行数,表示从数据库中检索并发送回客户端的行数。在这里是 0 行,可能意味着查询结果为空或者该操作不返回实际数据。
- Rows examined: 检查的行数,表示在执行查询时实际检查的行数。在这里是 487429 行,表示查询过程中涉及了大量的数据行。
- Rows affected: 影响的行数,表示执行查询后受影响的行数。在这里是 1 行,表示执行的操作只影响了一行数据。
- Bytes sent: 发送的字节数,表示从数据库发送到客户端的字节数。在这里是 52字节。
- 方法5
this.resetPatPortraitInfo(jsonObjectOfPortrait, segmentKey, segmentInfo);
此方法仅涉及json对象属性调整,不涉及数据库,故忽略
- 方法6
patPortraitMapper.deletePatSegmentInfo(portraitInfoEntity.getId(), segmentKey);
分析同更新操作,无索引全表扫描,效率较低。
2.2、优化后效果
此处使用验证数据同样为pat_id=20240104患者,画像重置后重新操作,效果如下:
- 请求时间
- 响应时间
由上图可以看出优化后的执行时间缩短为32ms,从24.68s提升到32ms,优化效果还是非常显著的,目前运营反馈人工审核功能操作一在3s内可以完成,大大提高了运营的审件效率。
3、优化总结
整体来看,研发人员建表不满足建表规范,同时创建表的时候没有考虑后期表的查询及更新场景的效率,相关数据库规范请参考医疗BU数据库研发规范。总结信息如下:
在 MySQL 中,表无主键和索引会对查询和更新效率产生一定的影响。以下是一些可能的影响:
查询效率:在没有主键和索引的情况下,MySQL 在执行查询时可能需要进行全表扫描来找到匹配的数据。这会导致查询效率降低,特别是当表中数据量较大时。 更新效率:更新操作也会受到影响,特别是涉及到大量数据更新时。没有索引的情况下,更新操作可能需要遍历整个表来找到需要更新的行,从而导致性能下降。 数据完整性:缺乏主键可能导致数据完整性方面的问题,例如重复数据、无法唯一标识每行数据等。这可能会对数据操作和管理带来困难。 针对这些问题,可以考虑以下解决方案:
添加主键:为表添加一个主键可以提高查询效率,并确保每行数据的唯一性。主键还可以帮助优化更新操作。 添加索引:根据查询需求,在经常被用于查询条件的列上添加索引可以显著提升查询效率。索引可以加快数据的检索速度。 优化查询语句:尽量避免全表扫描,尽量使用索引列作为查询条件,避免在没有索引的列上进行复杂的查询操作。 定期维护表:定期对表进行优化和维护,包括删除无用数据、重新建立索引、优化查询语句等,以保持表的高效性。 总的来说,为表添加主键和索引是提高查询和更新效率的关键步骤。同时,合理设计表结构和优化查询语句也是提高数据库性能的重要手段。