1. 程式人生 > 其它 >巧用Sqlite3加速資料庫

巧用Sqlite3加速資料庫

當遇到大量高頻欄位需要更新時候,資料庫CPU分分鐘飈起來,嚇得定時任務都不敢運行了。連上資料庫,人工掐表執行下

MariaDB [AAAA]>  show  global  status like 'Question%'; 
+---------------+---------+
| Variable_name | Value   |
+---------------+---------+
| Questions     | 5025774 |
+---------------+---------+
1 row in set (0.00 sec)

兩數相減,原來還真是查詢過多導致。仔細分析了下,查詢來源:

  1. 通過聯合欄位鎖定記錄
  2. 更新記錄屬性,回寫資料庫

更新可以批量,不過資源鎖定是個問題。有人說直接莽,全拉倒記憶體,自己一條條匹配,反正記憶體也不會很慢。但是幾萬條記錄,又不是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,而不用手動生成,簡潔明瞭。