Mysql遷移到TiDB雙寫數(shù)據(jù)庫(kù)兜底方案詳解
正文
TiDB 作為開(kāi)源 NewSQL 數(shù)據(jù)庫(kù)的典型代表之一,同樣支持 SQL,支持事務(wù) ACID 特性。在通訊協(xié)議上,TiDB 選擇與 MySQL 完全兼容,并盡可能兼容 MySQL 的語(yǔ)法。因此,基于 MySQL 數(shù)據(jù)庫(kù)開(kāi)發(fā)的系統(tǒng),大多數(shù)可以平滑遷移至 TiDB,而幾乎不用修改代碼。對(duì)用戶來(lái)說(shuō),遷移成本極低,過(guò)渡自然。
然而,仍有一些 MySQL 的特性和行為,TiDB 目前暫時(shí)不支持或表現(xiàn)與 MySQL 有差異。除此之外,TiDB 提供了一些擴(kuò)展語(yǔ)法和功能,為用戶提供更多的便利。
TiDB 仍處在快速發(fā)展的道路上,對(duì) MySQL 功能和行為的支持方面,正按 路線圖 的規(guī)劃在前行。
兼容策略
先從總體上概括 TiDB 和 MySQL 兼容策略,如下表:
通訊協(xié)議 | SQL語(yǔ)法 | 功能和行為 |
---|---|---|
完全兼容 | 兼容絕大多數(shù) | 兼容大多數(shù) |
截至 4.0 版本,TiDB 與 MySQL 的區(qū)別總結(jié)如下表:
? | MySQL | TiDB |
---|---|---|
隔離級(jí)別 | 支持讀未提交、讀已提交、可重復(fù)讀、串行化,默認(rèn)為可重復(fù)讀 | 樂(lè)觀事務(wù)支持快照隔離,悲觀事務(wù)支持快照隔離和讀已提交 |
鎖機(jī)制 | 悲觀鎖 | 樂(lè)觀鎖、悲觀鎖 |
存儲(chǔ)過(guò)程 | 支持 | 不支持 |
觸發(fā)器 | 支持 | 不支持 |
事件 | 支持 | 不支持 |
自定義函數(shù) | 支持 | 不支持 |
窗口函數(shù) | 支持 | 部分支持 |
JSON | 支持 | 不支持部分 MySQL 8.0 新增的函數(shù) |
外鍵約束 | 支持 | 忽略外鍵約束 |
字符集 | ? | 只支持 ascii、latin1、binary、utf8、utf8mb4 |
增加/刪除主鍵 | 支持 | 通過(guò) alter-primary-key 配置開(kāi)關(guān)提供 |
CREATE TABLE tblName AS SELECT stmt | 支持 | 不支持 |
CREATE TEMPORARY TABLE | 支持 | TiDB 忽略 TEMPORARY 關(guān)鍵字,按照普通表創(chuàng)建 |
DML affected rows | 支持 | 不支持 |
AutoRandom 列屬性 | 不支持 | 支持 |
Sequence 序列生成器 | 不支持 | 支持 |
三種方案比較
雙寫方案:同時(shí)往mysql和tidb寫入數(shù)據(jù),兩個(gè)數(shù)據(jù)庫(kù)數(shù)據(jù)完全保持同步
•優(yōu)點(diǎn):此方案最安全,作為兜底方案不需擔(dān)心數(shù)據(jù)庫(kù)回滾問(wèn)題,因?yàn)閿?shù)據(jù)完全一致,可以無(wú)縫回滾到mysql
•缺點(diǎn):新方案,調(diào)研方案實(shí)現(xiàn),成本較高
讀寫分離:數(shù)據(jù)寫入mysql,從tidb讀,具體方案是切換到線上以后,保持讀寫分離一周時(shí)間左右,這一周時(shí)間用來(lái)確定tidb數(shù)據(jù)庫(kù)沒(méi)有問(wèn)題,再把寫操作也切換到tidb
•優(yōu)點(diǎn): 切換過(guò)程,mysql和tidb數(shù)據(jù)保持同步,滿足數(shù)據(jù)回滾到mysql方案
•缺點(diǎn):mysql和tidb數(shù)據(jù)庫(kù)同步存在延時(shí),對(duì)部分寫入數(shù)據(jù)要求實(shí)時(shí)查詢的會(huì)導(dǎo)致查詢失敗,同時(shí)一旦整體切換到tidb,無(wú)法回切到mysql
直接切換:直接一步切換到tidb
•優(yōu)點(diǎn):切換過(guò)程最簡(jiǎn)單,成本最低
•缺點(diǎn):此方案沒(méi)有兜底方案,切換到tidb,無(wú)法再回切到mysql或者同步數(shù)據(jù)回mysql風(fēng)險(xiǎn)較大,無(wú)法保證數(shù)據(jù)是否可用
Django雙寫mysql與tidb策略
settings.py中新增配置
# Dev Database settings DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'name', 'USER': 'root', 'PASSWORD': '123456', 'HOST': 'db', }, 'replica': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'name', 'USER': 'root', 'PASSWORD': '123456', 'HOST': 'db', }, 'bak': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'name', 'USER': 'root', 'PASSWORD': '123456', 'HOST': 'db', }, } # 多重寫入數(shù)據(jù)庫(kù)配置 MULTI_WRITE_DB = "bak"
雙寫中間件 basemodel.py
import copy import logging import traceback from django.db import models, transaction, router from django.db.models.deletion import Collector from django.db.models import sql from django.db.models.sql.constants import CURSOR from jcdp.settings import MULTI_WRITE_DB, DATABASES multi_write_db = MULTI_WRITE_DB # 重寫QuerySet class BaseQuerySet(models.QuerySet): def create(self, **kwargs): return super().create(**kwargs) def update(self, **kwargs): try: rows = super().update(**kwargs) if multi_write_db in DATABASES: self._for_write = True query = self.query.chain(sql.UpdateQuery) query.add_update_values(kwargs) with transaction.mark_for_rollback_on_error(using=multi_write_db): query.get_compiler(multi_write_db).execute_sql(CURSOR) except Exception: logging.error(traceback.format_exc()) raise return rows def delete(self): try: deleted, _rows_count = super().delete() if multi_write_db in DATABASES: del_query = self._chain() del_query._for_write = True del_query.query.select_for_update = False del_query.query.select_related = False collector = Collector(using=multi_write_db) collector.collect(del_query) collector.delete() except Exception: logging.error(traceback.format_exc()) raise return deleted, _rows_count def raw(self, raw_query, params=None, translations=None, using=None): try: qs = super().raw(raw_query, params=params, translations=translations, using=using) if multi_write_db in DATABASES: super().raw(raw_query, params=params, translations=translations, using=multi_write_db) except Exception: logging.error(traceback.format_exc()) raise return qs def bulk_create(self, objs, batch_size=None, ignore_conflicts=False): try: for obj in objs: obj.save() except Exception: logging.error(traceback.format_exc()) raise # objs = super().bulk_create(objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts) # if multi_write_db in DATABASES: # self._db = multi_write_db # super().bulk_create(objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts) return objs def bulk_update(self, objs, fields, batch_size=None): try: super().bulk_update(objs, fields, batch_size=batch_size) if multi_write_db in DATABASES: self._db = multi_write_db super().bulk_update(objs, fields, batch_size=batch_size) except Exception: logging.error(traceback.format_exc()) raise class BaseManager(models.Manager): _queryset_class = BaseQuerySet class BaseModel(models.Model): objects = BaseManager() class Meta: abstract = True def delete( self, using=None, *args, **kwargs ): try: instance = copy.deepcopy(self) super().delete(using=using, *args, **kwargs) if multi_write_db in DATABASES: super(BaseModel, instance).delete(using=multi_write_db, *args, **kwargs) except Exception: logging.error(traceback.format_exc()) raise def save_base(self, raw=False, force_insert=False, force_update=False, using=None, update_fields=None): try: using = using or router.db_for_write(self.__class__, instance=self) assert not (force_insert and (force_update or update_fields)) assert update_fields is None or update_fields cls = self.__class__ # Skip proxies, but keep the origin as the proxy model. if cls._meta.proxy: cls = cls._meta.concrete_model meta = cls._meta # A transaction isn't needed if one query is issued. if meta.parents: context_manager = transaction.atomic(using=using, savepoint=False) else: context_manager = transaction.mark_for_rollback_on_error(using=using) with context_manager: parent_inserted = False if not raw: parent_inserted = self._save_parents(cls, using, update_fields) self._save_table( raw, cls, force_insert or parent_inserted, force_update, using, update_fields, ) if multi_write_db in DATABASES: super().save_base(raw=raw, force_insert=raw, force_update=force_update, using=multi_write_db, update_fields=update_fields) # Store the database on which the object was saved self._state.db = using # Once saved, this is no longer a to-be-added instance. self._state.adding = False except Exception: logging.error(traceback.format_exc()) raise
上述配置完成以后,在每個(gè)應(yīng)用的models.py中引用新的BaseModel類作為模型基類即可實(shí)現(xiàn)雙寫目的
class DirectoryStructure(BaseModel): """ 目錄結(jié)構(gòu) """ view = models.CharField(max_length=128, db_index=True) # 視圖名稱 eg:部門視圖 項(xiàng)目視圖 sub_view = models.CharField(max_length=128, unique=True, db_index=True) # 子視圖名稱 sub_view_num = models.IntegerField() # 子視圖順序號(hào)
注:目前該方法尚不支持多對(duì)多模型的雙寫情景,如有業(yè)務(wù)需求,還需重寫ManyToManyField類,方法參考猴子補(bǔ)丁方式
遷移數(shù)據(jù)庫(kù)過(guò)程踩坑記錄
TIDB配置項(xiàng)差異:確認(rèn)數(shù)據(jù)庫(kù)配置:ONLY_FULL_GROUP_BY 禁用 (mysql默認(rèn)禁用)
TIDB不支持事務(wù)savepoint,代碼中需要顯式關(guān)閉savepoint=False
TIDB由于是分布式數(shù)據(jù)庫(kù),對(duì)于自增主鍵字段的自增策略與mysq有差異,若業(yè)務(wù)代碼會(huì)與主鍵id關(guān)聯(lián),需要注意
以上就是Mysql遷移到TiDB雙寫數(shù)據(jù)庫(kù)兜底方案詳解的詳細(xì)內(nèi)容,更多關(guān)于Mysql遷移TiDB雙寫數(shù)據(jù)庫(kù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Mysql數(shù)據(jù)庫(kù)值的添加、修改、刪除及清空操作實(shí)例
這篇文章主要給大家介紹了關(guān)于Mysql數(shù)據(jù)庫(kù)值的添加、修改、刪除及清空操作的相關(guān)資料,文中通過(guò)示例代碼以及圖文介紹的非常詳細(xì),需要的朋友可以參考下2021-06-06MySQL中CONCAT和GROUP_CONCAT方法的區(qū)別詳解
本文主要介紹了MySQL中CONCAT和GROUP_CONCAT方法的區(qū)別詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01Mysql啟動(dòng)報(bào)ERROR:2002的分析與解決
這篇文章主要給大家介紹了關(guān)于Mysql啟動(dòng)時(shí)報(bào)ERROR:2002問(wèn)題的分析與解決方法,文中通過(guò)示例代碼介紹將該問(wèn)題分析的非常詳細(xì),對(duì)同樣遇到這個(gè)問(wèn)題的朋友們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-08-08mysql 搜尋附近N公里內(nèi)數(shù)據(jù)的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇mysql 搜尋附近N公里內(nèi)數(shù)據(jù)的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04MYSQL與SQLserver之間存儲(chǔ)過(guò)程的轉(zhuǎn)換方式
這篇文章主要介紹了MYSQL與SQLserver之間存儲(chǔ)過(guò)程的轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11