Mysql到TiDB迁移,双写数据库兜底方案
作者:京东零售 石磊
TiDB 作为开源 NewSQL 数据库的典型代表之一,同样支持 SQL,支持事务 ACID 特性。在通讯协议上,TiDB 选择与 MySQL 完全兼容,并尽可能兼容 MySQL 的语法。因此,基于 MySQL 数据库开发的系统,大多数可以平滑迁移至 TiDB,而几乎不用修改代码。对用户来说,迁移成本极低,过渡自然。
然而,仍有一些 MySQL 的特性和行为,TiDB 目前暂时不支持或表现与 MySQL 有差异。除此之外,TiDB 提供了一些扩展语法和功能,为用户提供更多的便利。
TiDB 仍处在快速发展的道路上,对 MySQL 功能和行为的支持方面,正按 路线图 的规划在前行。
兼容策略
先从总体上概括 TiDB 和 MySQL 兼容策略,如下表:
通讯协议 | SQL语法 | 功能和行为 |
---|---|---|
完全兼容 | 兼容绝大多数 | 兼容大多数 |
截至 4.0 版本,TiDB 与 MySQL 的区别总结如下表:
MySQL | TiDB | |
---|---|---|
隔离级别 | 支持读未提交、读已提交、可重复读、串行化,默认为可重复读 | 乐观事务支持快照隔离,悲观事务支持快照隔离和读已提交 |
锁机制 | 悲观锁 | 乐观锁、悲观锁 |
存储过程 | 支持 | 不支持 |
触发器 | 支持 | 不支持 |
事件 | 支持 | 不支持 |
自定义函数 | 支持 | 不支持 |
窗口函数 | 支持 | 部分支持 |
JSON | 支持 | 不支持部分 MySQL 8.0 新增的函数 |
外键约束 | 支持 | 忽略外键约束 |
字符集 | 只支持 ascii、latin1、binary、utf8、utf8mb4 | |
增加/删除主键 | 支持 | 通过 alter-primary-key 配置开关提供 |
CREATE TABLE tblName AS SELECT stmt | 支持 | 不支持 |
CREATE TEMPORARY TABLE | 支持 | TiDB 忽略 TEMPORARY 关键字,按照普通表创建 |
DML affected rows | 支持 | 不支持 |
AutoRandom 列属性 | 不支持 | 支持 |
Sequence 序列生成器 | 不支持 | 支持 |
三种方案比较
双写方案:同时往mysql和tidb写入数据,两个数据库数据完全保持同步
•优点:此方案最安全,作为兜底方案不需担心数据库回滚问题,因为数据完全一致,可以无缝回滚到mysql
•缺点:新方案,调研方案实现,成本较高
读写分离:数据写入mysql,从tidb读,具体方案是切换到线上以后,保持读写分离一周时间左右,这一周时间用来确定tidb数据库没有问题,再把写操作也切换到tidb
•优点: 切换过程,mysql和tidb数据保持同步,满足数据回滚到mysql方案
•缺点:mysql和tidb数据库同步存在延时,对部分写入数据要求实时查询的会导致查询失败,同时一旦整体切换到tidb,无法回切到mysql
直接切换:直接一步切换到tidb
•优点:切换过程最简单,成本最低
•缺点:此方案没有兜底方案,切换到tidb,无法再回切到mysql或者同步数据回mysql风险较大,无法保证数据是否可用
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',
},
}
# 多重写入数据库配置
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
上述配置完成以后,在每个应用的models.py中引用新的BaseModel类作为模型基类即可实现双写目的
class DirectoryStructure(BaseModel):
"""
目录结构
"""
view = models.CharField(max_length=128, db_index=True) # 视图名称 eg:部门视图 项目视图
sub_view = models.CharField(max_length=128, unique=True, db_index=True) # 子视图名称
sub_view_num = models.IntegerField() # 子视图顺序号
注:目前该方法尚不支持多对多模型的双写情景,如有业务需求,还需重写ManyToManyField类,方法参考猴子补丁方式
迁移数据库过程踩坑记录
TIDB配置项差异:确认数据库配置:ONLY_FULL_GROUP_BY 禁用 (mysql默认禁用)
TIDB不支持事务savepoint,代码中需要显式关闭savepoint=False
TIDB由于是分布式数据库,对于自增主键字段的自增策略与mysq有差异,若业务代码会与主键id关联,需要注意
Mysql到TiDB迁移,双写数据库兜底方案的更多相关文章
- MYSQL的DOUBLE WRITE双写
期待未来超高速大容量的固态硬盘普及时,只需要CHECKPOINT,而不再需要各种各样的BUFFER,CACHE了 DOUBLE WRITE 在InnoDB将BP中的Dirty Page刷(flush) ...
- MySQL优化总结,百万级数据库优化方案
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...
- Redis面试题记录--缓存双写情况下导致数据不一致问题
转载自:https://blog.csdn.net/lzhcoder/article/details/79469123 https://blog.csdn.net/u013374645/article ...
- 【原创】分布式之数据库和缓存双写一致性方案解析(三) 前端面试送命题(二)-callback,promise,generator,async-await JS的进阶技巧 前端面试送命题(一)-JS三座大山 Nodejs的运行原理-科普篇 优化设计提高sql类数据库的性能 简单理解token机制
[原创]分布式之数据库和缓存双写一致性方案解析(三) 正文 博主本来觉得,<分布式之数据库和缓存双写一致性方案解析>,一文已经十分清晰.然而这一两天,有人在微信上私聊我,觉得应该要采用 ...
- mysql 客户无感知迁移_亿级账户数据迁移,不用数据库工具还能怎么搞?
原标题:亿级账户数据迁移,不用数据库工具还能怎么搞? 背景 在阿里巴巴内部"大中台,小前台"的组织和业务体制,使前线业务更加敏捷,赋能业务积极迎接未来挑战和机遇,在阿里大中台能力建 ...
- Redis与Mysql双写一致性方案解析
一 前言 首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用.在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作 但是在更新缓存方面,对于更新完数据库,是更新缓存呢,还是删除缓存 ...
- Redis 多级缓存架构和数据库与缓存双写不一致问题
采用三级缓存:nginx本地缓存+redis分布式缓存+tomcat堆缓存的多级缓存架构 时效性要求非常高的数据:库存 一般来说,显示的库存,都是时效性要求会相对高一些,因为随着商品的不断的交易,库存 ...
- Redis使用总结(二、缓存和数据库双写一致性问题)
首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用.在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作. 但是在更新缓存方面,对于更新完数据库,是更新缓存呢,还是删除缓存.又或者 ...
- 面试前必知Redis面试题—缓存雪崩+穿透+缓存与数据库双写一致问题
今天来分享一下Redis几道常见的面试题: 如何解决缓存雪崩? 如何解决缓存穿透? 如何保证缓存与数据库双写时一致的问题? 一.缓存雪崩 1.1什么是缓存雪崩? 回顾一下我们为什么要用缓存(Redis ...
- mysql数据库迁移到oracle数据库后 如何删除相同的数据
mysql数据库迁移到oracle数据库后 如何删除相同的数据 首先搞清楚有多少数据是重复的 select pid from product group by pid having count(pid ...
随机推荐
- 当线下门店遇上AI:华为云ModelBox携手佳华科技客流分析实践
摘要:在赋能传统门店客流经营数字化转型方面,华为云ModelBox与伙伴佳华科技合作推出的"华为云客流统计项目",算是一次成功的探索. 本文分享自华为云社区<当线下门店遇上A ...
- 详解openGauss多线程架构启动过程
摘要:本文介绍openGauss数据库的启动过程,包括主线程,辅助线程及业务处理线程的启动过程. 本文分享自华为云社区<openGauss内核分析(一):openGauss 多线程架构启动过程详 ...
- Solon2 开发之IoC,四、注入依赖与初始化
Solon 强调 有克制的注入 + 手动控制 结合的模式.好处是,代码用料少.启动快. Bean 的关键生命节点: 节点 说明 1. Constructor(构造方法) 不支持参数注入 2. @Inj ...
- Mac 复制文件名目录路径
Mac快速复制文件路径 在Mac电脑上,我们经常需要复制文件的路径,其实,Mac系统提供了快速复制文件路径的方法.下面我们来详细介绍. 方法一:使用菜单栏 首先,打开Finder,然后选择你要复制路径 ...
- 【原理篇】WebView 实现嵌套滑动,丝滑般实现吸顶效果,完美兼容 X5 webview
本文首发我的微信公众号徐公,收录于 Github·AndroidGuide,这里有 Android 进阶成长知识体系, 希望我们能够一起学习进步,关注公众号徐公,5 年中大厂程序员,一起建立核心竞争力 ...
- 用 three.js 绘制三维带箭头线 (线内箭头)
在LineMaterial.js基础上修改的ArrowLineMaterial.js代码: /** * @author WestLangley / http://github.com/WestLang ...
- 如何设置IDEA代码风格为Google风格,使用Google风格format
1.在Github仓库寻找:google style 为了节省大家时间直接放链接了:Here 2.进到项目 找到名为intellij-java-google-style.xml 文件 Ctrl + F ...
- vivo 云服务海量数据存储架构演进与实践
一.写在开头 vivo 云服务提供给用户备份手机上的联系人.短信.便签.书签等数据的能力,底层存储采用 MySQL 数据库进行数据存储. 随着 vivo 云服务业务发展,云服务用户量增长迅速,存储在云 ...
- 嵌入式Linux必读经典书籍(含下载方式)
最近,在知乎看到一个问题,"嵌入式Linux有哪些好书推荐".我读研期间也喜欢收藏一些书籍,每次看到京东有活动,总是忍不住想买一些书籍回来. 随着时间越来越久,我买的书越来越多,但 ...
- Go image registry
0. 前言 OpenShift image registry 概述 介绍了 OpenShift 平台上 registry 的基本结构.进一步地,本文将介绍在 Kubernetes 平台上,如何使用 G ...