作者:京东零售 石磊

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迁移,双写数据库兜底方案的更多相关文章

  1. MYSQL的DOUBLE WRITE双写

    期待未来超高速大容量的固态硬盘普及时,只需要CHECKPOINT,而不再需要各种各样的BUFFER,CACHE了 DOUBLE WRITE 在InnoDB将BP中的Dirty Page刷(flush) ...

  2. MySQL优化总结,百万级数据库优化方案

    1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...

  3. Redis面试题记录--缓存双写情况下导致数据不一致问题

    转载自:https://blog.csdn.net/lzhcoder/article/details/79469123 https://blog.csdn.net/u013374645/article ...

  4. 【原创】分布式之数据库和缓存双写一致性方案解析(三) 前端面试送命题(二)-callback,promise,generator,async-await JS的进阶技巧 前端面试送命题(一)-JS三座大山 Nodejs的运行原理-科普篇 优化设计提高sql类数据库的性能 简单理解token机制

    [原创]分布式之数据库和缓存双写一致性方案解析(三)   正文 博主本来觉得,<分布式之数据库和缓存双写一致性方案解析>,一文已经十分清晰.然而这一两天,有人在微信上私聊我,觉得应该要采用 ...

  5. mysql 客户无感知迁移_亿级账户数据迁移,不用数据库工具还能怎么搞?

    原标题:亿级账户数据迁移,不用数据库工具还能怎么搞? 背景 在阿里巴巴内部"大中台,小前台"的组织和业务体制,使前线业务更加敏捷,赋能业务积极迎接未来挑战和机遇,在阿里大中台能力建 ...

  6. Redis与Mysql双写一致性方案解析

    一 前言 首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用.在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作 但是在更新缓存方面,对于更新完数据库,是更新缓存呢,还是删除缓存 ...

  7. Redis 多级缓存架构和数据库与缓存双写不一致问题

    采用三级缓存:nginx本地缓存+redis分布式缓存+tomcat堆缓存的多级缓存架构 时效性要求非常高的数据:库存 一般来说,显示的库存,都是时效性要求会相对高一些,因为随着商品的不断的交易,库存 ...

  8. Redis使用总结(二、缓存和数据库双写一致性问题)

    首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用.在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作. 但是在更新缓存方面,对于更新完数据库,是更新缓存呢,还是删除缓存.又或者 ...

  9. 面试前必知Redis面试题—缓存雪崩+穿透+缓存与数据库双写一致问题

    今天来分享一下Redis几道常见的面试题: 如何解决缓存雪崩? 如何解决缓存穿透? 如何保证缓存与数据库双写时一致的问题? 一.缓存雪崩 1.1什么是缓存雪崩? 回顾一下我们为什么要用缓存(Redis ...

  10. mysql数据库迁移到oracle数据库后 如何删除相同的数据

    mysql数据库迁移到oracle数据库后 如何删除相同的数据 首先搞清楚有多少数据是重复的 select pid from product group by pid having count(pid ...

随机推荐

  1. 动手实践丨使用华为云IoT边缘体验“边云协同”

    摘要:"边云协同",即云端与边缘的协同.通过边云通道,部署在边缘节点上,并实现在云端远程管理应用,保障部署在边缘的应用能够正常运行,并通过与云端的连线,将业务执行结果在云端呈现. ...

  2. 6种方法帮你搞定SimpleDateFormat类不是线程安全的问题

    摘要:本文主要讲述在高并发下SimpleDateFormat类为何会出现安全问题,以及如何解决SimpleDateFormat类的安全问题. 本文分享自华为云社区<[高并发]SimpleDate ...

  3. Windows 2012 上网慢如何解决

    解决步骤:1.执行netsh  int  tcp  show  global 查看默认TCP全局参数等相关设置 Windows 2012 默认ECN 功能是开启的,将其关闭即可 以管理员的身份运行下列 ...

  4. 浅谈sql执行流程、innodb架构设计、buffer pool缓冲池

    一.从服务端到数据库sql执行流程: 1.SQL接口:负责处理接收到sql的语句 2.查询解析器:负责将sql变成数据库可以看懂的语言 3.查询优化器:选择最优的查询路径(针对你编写的复杂sql语句生 ...

  5. CodeForces - 469A I Wanna Be the Guy

    There is a game called "I Wanna Be the Guy", consisting of n levels. Little X and his frie ...

  6. 6.0 《数据库系统概论》之关系数据库的规范化理论(数据依赖对表的影响[插入-删除-修改-冗余]、1NF-2NF-3NF-BCNF-4NF、函数依赖与多值依赖)

    前言 本篇文章学习书籍:<数据库系统概论>第5版 王珊 萨师煊编著 视频资源来自:数据库系统概论完整版(基础篇+高级篇+新技术篇) 由于 BitHachi 学长已经系统的整理过本书了,我在 ...

  7. 特色国风数字孪生智慧大坝 3D 可视化

    前言 水利兴,五谷丰.水利作为国民经济稳定和谐的重要部分,不仅有防洪减灾.农业灌溉.城市供水调水.渔业外贸.旅游航运.生态环境等综合应用,水电资源也是至关重要的可持续能源之一.大坝与水库.水电站等水利 ...

  8. <vue 路由 4、嵌套路由>

    一.效果 点击about后,新闻和体育属于about的子路由调用的页面 知识点说明 路由里使用children属性可以实现路由的嵌套 三.代码结构 注:主要是标红的几个文件 四.代码 重新编写这几个文 ...

  9. <vue初体验> 基础知识 2、vue的列表展示

    系列导航 <vue初体验> 一. vue的引入和使用体验 <vue初体验> 二. vue的列表展示 <vue初体验> 三. vue的计数器 <vue初体验&g ...

  10. vue路由模块化

    https://www.bilibili.com/video/BV1Tg411u7oy?from=search&seid=5098139115981575542&spm_id_from ...