問題現象
測試同學反饋,商品查詢功能,輸入字母或漢字正常,輸入數字搜索時報錯。
問題排查
初次排查
查看了一下耗時點,在mybatis執行到DruidDataSource.getConnect方法之間耗時很久(兩次,每次都差不多100s左右)。
猜測:連接池問題,全部被佔用,在等待連接?
查看了下getConnection代碼, 裏面纔開始獲取連接,說明這個中間過程還沒有進行連接獲取
public DruidPooledConnection getConnection() throws SQLException {
return getConnection(maxWait);
}
這個功能,在其他環境驗證的時候正常,上線很久了,也沒有暴露過問題。查看了這個接口對應的請求,在那個點附近的幾十、百多秒,之前、之後的都在1s內。
當時查看部署服務器,那個時間段CPU耗時較高。環境之前出現過不穩定的情況,當時以爲環境問題,適當加大了接口超時時間。
問題再現
第二天,測試反饋又重現了。一次可以是湊巧,兩次可能性很低。
看了下請求數據,超時請求,輸入的搜索關鍵字是"2"。關鍵字會去模糊匹配商品名稱和條碼。匹配到很多商品?
查看了下數據庫裏面的商品,搜索條件下模糊匹配到幾W條。pinpoint裏面參數也印證了。
對比分析了下之前輸入中文或字母搜索的,匹配的商品數量很少。
由於是測試環境,之前批量造數據的時候,商品名稱不規範,數字"2"匹配到了很多。
至此找到了根本原因,如下:
我們的商品條碼和商品名稱是存放在兩張表的。查看代碼實現,是先去條碼錶模糊匹配,找到一批商品ukid,然後在作爲入參,去商品表裏面做分頁查詢,計算總量等。
商品數據量很大,mybatis做動態sql拼接時耗時很久。 從mybatis開始執行,到getConnection,中間是完成SQL組裝。
商品表查詢,xml配置
<select id="selectRsDefineds" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from rs_defined r
where
defined_ukid IS NOT NULL
<if test="buIds != null">
and definer_id in
<foreach close=")" collection="buIds" item="ownerUkid" open="(" separator=",">
#{ownerUkid}
</foreach>
</if>
<if test="definedName != null">
and
(
defined_name LIKE #{definedName}
<if test="inDefinedUkidList != null">
or defined_ukid in
<foreach close=")" collection="inDefinedUkidList" item="definedUkid" open="(" separator=",">
#{definedUkid}
</foreach>
</if>
)
</if>
<if test="statusList != null">
and status in
<foreach close=")" collection="statusList" item="status" open="(" separator=",">
#{status}
</foreach>
</if>
<if test="statusList == null">
and status = 1
</if>
<if test="typeList != null">
and defined_type in
<foreach close=")" collection="typeList" item="type" open="(" separator=",">
#{type}
</foreach>
</if>
<if test="debarUkids != null">
and defined_ukid NOT IN
<foreach close=")" collection="debarUkids" item="debarDefinedUkid" open="(" separator=",">
#{debarDefinedUkid}
</foreach>
</if>
<if test="sku != null">
and sku = #{sku,jdbcType=BIGINT}
</if>
<if test="sort != null">
ORDER BY ${sort}
</if>
<if test="limit != null">
<if test="offset != null">
limit ${offset}, ${limit}
</if>
<if test="offset == null">
limit ${limit}
</if>
</if>
</select>
解決方案
通過elasticSearch查詢
直接把所有商品數據同步到es,通過es做搜索。
自測耗時500ms左右。 (總量:28w+,匹配到的數據量:15w+,虛擬機)
公司已有es環境,es本身用於搜索,對大數據量篩選過濾、匹配度等支持更好。而且調整成本不高。
其他方案
- 自身在程序中做SQL拼接,不要用mybatis的。 針對這種特定場景,沒必要所有的均如此
- 表結構設計調整,條碼、名稱 聚合到一張表中進行搜索匹配
補充說明
mybatis做動態sql拼接,很久以前碰到過一次棧溢出的。當時查詢到SQL拼接使用的visitor模式,構造了比較複雜的語法樹。
本次這塊沒有去往下分析。有大神歡迎賜教,或者有資料分享下,謝
之前棧溢出的記錄
https://blog.csdn.net/LG772EF/article/details/58636910