mysql 的這個痛點,用 elasticsearch 輕鬆解決

月白發表於2021-10-22

大家好,我是月白。

寫這篇文章不是對比 mysql 和 elasticsearch 的優劣(它們生而不同,沒啥好比的),而是想分享一下最近在工作上遇到的一個查詢問題和這個問題的解決過程。對於 elasticsearch,我也還是處在略懂階段,要不是因為這次工作需要,我可能不會去研究它?

好了,回到正題,由於內部工作調整,接收了一個公司的邊緣專案,體量並不大,幾十萬的使用者數量。然而,就是這區區的幾十萬使用者數量,導致了mysql in 查詢引數過多的問題,運營在管理後臺查詢客戶列表速度緩慢甚至一度陷入癱瘓。

你可能會想,是不是程式碼寫的太爛了或者前期設計考慮不周?

e18d20c94006dfe0-3cdd6892e1ea4e8e-5fd0bb2a3b67d491916c6556801d6eae.jpeg
其實這個也能理解,畢竟產品的需求是多變的,考慮不周是常有的事。這個列表查詢原本只有幾個簡單的欄位查詢,而且都是客戶表單表內的欄位查詢,隨著產品的變更,查詢條件多達十幾個,其中這個標籤查詢,聯表也解決不了問題,那具體是怎麼一個情況呢?

別急,讓我簡單介紹一下
開始你的表演.gif

問題簡述

為了集中於描述這個問題,表結構進行了慘無人道的簡化,能體會到這個意思就好?
前端的展示就是下面這種分頁表格(圖片截圖自 ant design 官方文件)
企業微信20211021-191939.png

表結構

客戶表 custmers

欄位型別
idint
namestring
gendertinyint
ageint
remarkstring
created_attimestamp
updated_attimestamp

標籤表 tags

欄位型別
idint
namestring
created_attimestamp
updated_attimestamp

關聯表 customer_tag

欄位型別
customer_idint
tag_idint

查詢需求

現在產品需要通過客戶名字以及客戶身上的標籤進行查詢,支援多個標籤同時查詢。原來的sql大概是這樣的

/*
 獲取符合要求的 customer_id 列表 
 查出來一大堆 customer_id
 */
select customer_id from custmer_tag where tag_id in (傳入的tag_id);

/*
 通過 customer_id 查詢
 每翻一頁都得經歷這一大堆id的in查詢,id過多還會導致程式碼直接崩潰
*/
select * from customers where id in (一大堆id) limit 10 offset 0; 

問題顯而易見了吧,in 查詢內引數過多,不僅效率低下,極端情況還會導致sql過長程式崩潰。

看了第一眼我覺是不是可以拯救一下(慢就慢一點,先讓程式不異常),於是換成下面的語句:

select * from customers where id in (select customer_id from custmer_tag where tag_id in (傳入的 tag_id) group by customer_id));

但是仔細看了一下業務邏輯我就放棄了,多個 tag_id 查詢 要支援 and 和 or 的查詢邏輯,select customer_id from custmer_tag where tag_id in (1,2,3) group by customer_id 這句子查詢 sql 就是 or 關係查詢,customer 只需要存在任何一個 tag_id 就滿足查詢條件。但是如果是 and 邏輯呢?要查出同時存在標籤 1,2,3 的客戶,那麼這條語句就不適用了。當然,如果一定要用 sql 去查,也許也能查出來,這裡我就沒有再試了,畢竟就運算元查詢行得通,效率也是十分低下的,不是長久之計。

解決方案

那該如何解決呢?我的第一反應就是先通過 es 查詢出符合條件的 customer 表的 id,然後再通過id查詢資料,這樣對整個業務邏輯改動最小,效率也完全沒有問題,畢竟原來的查詢及組裝資料邏輯十分複雜,涉及到五六張表聯查和後續資料處理,實在不想做過多改動(常規職場保命)
4e8872a8f8104b737b669bacc7903cc8.jpg

開始優化

搭建 elasticsearch 服務

這個是運維的活,我提需求就行了。

資料寫入 es

這個得我幹了?
由於需要查詢的欄位涉及了多張表,索性把所有相關欄位和customer的基本資訊寫入到 es,方便後續同樣需要查詢的業務(後面還真的用到了,而且還追加了欄位)

欄位確定了之後,如何實時更新呢?在每個業務修改和插入的點觸發寫入和更新的操作?
顯然不是,這樣寫既容易寫不全面,也需要很大的機械化工作,顯然不是生活在水深火熱之中的程式設計師該乾的事。那麼怎麼做呢?

這時候,如果我們能像mysql從庫那樣,把資料都同步過來,然後我們更新一下 es,豈不是完美。說幹就幹,在通過一番折騰之後(主要是監聽binlog),終於是解決了資料入庫的問題。

重構程式碼

現在的程式碼邏輯變成了這樣:

  1. 從 es 分頁查詢出 customer id 100, 200, 300
  2. 把原來 sql 的所有 where 條件刪除,新增 where id in (100,200,300),其他邏輯不用動

至此,業務重構完成,查詢速度輕輕鬆鬆提高百倍,那缺點是什麼呢?

!!!要花錢!!!!!
充錢能解決.gif

後話

雖然問題目前是解決了,但從這件事中,我體會到了 es 便利之處,決定進一步學習一番。這篇文章涉及到的 es 和 mysql 監聽等相關內容後續更新吧!

就醬,祝大家工作順利!我是月白,一枚在網際網路苟且偷生的猿。

相關文章