巧用Sqlite3加速資料庫
阿新 • • 發佈:2022-05-13
當遇到大量高頻欄位需要更新時候,資料庫CPU分分鐘飈起來,嚇得定時任務都不敢運行了。連上資料庫,人工掐表執行下
MariaDB [AAAA]> show global status like 'Question%';
+---------------+---------+
| Variable_name | Value |
+---------------+---------+
| Questions | 5025774 |
+---------------+---------+
1 row in set (0.00 sec)
兩數相減,原來還真是查詢過多導致。仔細分析了下,查詢來源:
- 通過聯合欄位鎖定記錄
- 更新記錄屬性,回寫資料庫
更新可以批量,不過資源鎖定是個問題。有人說直接莽,全拉倒記憶體,自己一條條匹配,反正記憶體也不會很慢。但是幾萬條記錄,又不是kv,多欄位匹配,每次找到一條資料也是難度很大。帶著這個問題,sqlite3 in memory 閃亮登場!
以Django為例,多資料庫配置如下:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'name', 'USER': 'user', 'PASSWORD': 'password', 'HOST': '127.0.0.1', 'PORT': '3306', }, 'memory': { 'ENGINE': 'django.db.backends.sqlite3', # :memory: 屬於獨佔,單程序內多執行緒無法共享 'NAME': 'file:memDB1?mode=memory&cache=shared', 'uri': True, } }
記憶體表無法儲存表結構,每次都要初始下,mysql 與 sqlite3 結構並不一樣,因此我採用的是先用檔案,然後匯出sql
sqlite3 db.sqlite3
sqlite> .output tmpl.sql
sqlite> .dump
sqlite> .exit
然後定時任務執行前,初始化表結構
from django.db import connections @classmethod def init_cache(cls): path = '../sql/tmpl.sql' cache_con = connections['memory'] with cache_con.cursor() as cursor: with open(path) as fp: for sql in fp.readlines(): cursor.execute(sql) @classmethod def init_table(cls, *args): for model in args: objs = model.objects.all() logger.info("同步表結構: %s 總共: %s 進入cache" % (model, len(objs))) model.objects.using("memory").bulk_create(objs, batch_size=1000)
上述邏輯,只要在定時任務啟動時初始化一次,就可以快照一波指定資料到記憶體。由於API例項是直接查詢資料庫,cache只有自身使用,那麼讀寫分離前提就夠了。簡單邏輯我們可以直接這麼寫也不卡:
objs = []
for item in data['data']['result']:
flag = item['metric']['disk']
instance = item['metric']['instance']
ip = instance.split(":")[0]
disk = Disk.objects.using("memory").filter(host__ip=ip, flag=flag).first()
if not disk:
logger.warning("硬碟<%s-%s>不在資料庫中" % (ip, flag))
continue
value = item['value'][-1]
disk.io_usage = value
objs.append(disk)
Disk.objects.using("default").bulk_update(objs, ("io_usage",), batch_size=500)
從Prometheus中讀取硬碟IO使用率,先去cache中鎖定model,更新欄位,然後合併更新到資料庫中。記住,這裡一定要指定更新欄位,這樣cache的作用就只是查主鍵拼SQL,而不用手動生成,簡潔明瞭。