Mysql迁移到TiDB双写数据库兜底方案详解

目录
  • 正文
  • 兼容策略
  • 三种方案比较
  • Django双写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迁移TiDB双写数据库的资料请关注我们其它相关文章!

(0)

相关推荐

  • Mysql单库迁移的操作方法

    目录 为什么要迁移 一.导出数据库文件 二.上传至目标机器 三. 登录目标机器mysql,创建数据库 四.导入数据库文件 为什么要迁移 MySQL 迁移是 DBA 日常维护中的一个工作.迁移,究其本义,无非是把实际存在的物体挪走,保证该物体的完整性以及延续性.就像柔软的沙滩上,两个天真无邪的小孩,把一堆沙子挪向其他地方,铸就内心神往的城堡. 生产环境中,有以下情况需要做迁移工作,如下:1.磁盘空间不够.比如一些老项目,选用的机型并不一定适用于数据库.随着时间的推移,硬盘很有可能出现短缺:2.业务

  • MySQL迁移KingbaseESV8R2的实现步骤

    目录 1. 使用Oracle中转 2. 使用KingbaseESV8R3中转 3. 其他问题 1.自增 2.uuid 4. 已发现的 SQL 问题 1. 使用Oracle中转 KingbaseESV8R2只支持从Oracle迁移,所以先MySQL迁移到Oracle. 迁移到Oracle问题:1.oracle对表名有最大30位限制2.invalid hex number,无效16进制3.表数据空,报cannot insert NULL into4.索引名太长 MySQL迁移Oracle问题就很多了

  • 解决mysql数据库数据迁移达梦数据乱码问题

    受到领导的嘱托,接手了一个java项目,要进行重构,同时了项目的整体建设要满足信创的要求. 那么首先就要满足两点: 1,使用国产数据库达梦8替换mysql数据库 2,使用金蝶中间件替换tomcat进行容器部署 在不懈的努力下,我已在本地的搭建和安装完成达梦8(dm8)数据库,也完成了代码框架更改数据库源,替换达梦数据库的demo验证工作. driverClassName: dm.jdbc.driver.DmDriver url: jdbc:dm://10.0.3.132:5236/XC-SERV

  • MySQL数据库迁移OpenGauss数据库解析

    目录 环境 安装OpenGauss 创建并导入数据 修改后端项目 部署后端项目 部署前端项目 环境 华为云服务器 4核 8G CentOS 8.0 64位 JDK11 Data Grip xshell xftp mysql 8 安装OpenGauss 1.此次安装基于Docker进行安装 docker安装步骤自行解决 # 基于docker进行安装 $ docker run --name opengauss --privileged=true -p 5432:5432 -d -e GS_PASSW

  • mysql 实现迁移数据库到另一台服务器

    目录 mysql迁移数据库到另一台服务器 在两个服务器之间迁移mysql数据库 第一步 - 执行MySQL转储 第二步 - 复​​制数据库 第三步 - 导入数据库 mysql迁移数据库到另一台服务器 1.生成sql压缩文件(库名gkpt_main_v2 ,mysql用户:gmp ) mysqldump -u gmp -p -E -R gkpt_main_v2  2>$gkpt_main_v2_error.log  | gzip>./gkpt_main_v2.sql.gz  nohup mysq

  • MySQL数据备份、还原、数据库迁移以及表的导出和导入

    目录 前言 一.数据备份 1.使用mysqldump命令备份 2.直接复制整个数据库目录 3.使用mysqlhotcopy工具快速备份 二.数据还原 1.使用mysql命令还原 2.直接复制到数据库目录 三.数据库迁移 1.相同版本的MySQL数据库之间的迁移 2.不同版本的MySQL数据库之间的迁移 四.表的导出和导入 1.用select…into outfile导出文本文件 2.用mysqldump命令导出文本文件 3.用mysql命令导出文本文件 4.用load data infile方式

  • Mysql迁移到TiDB双写数据库兜底方案详解

    目录 正文 兼容策略 三种方案比较 Django双写mysql与tidb策略 正文 TiDB 作为开源 NewSQL 数据库的典型代表之一,同样支持 SQL,支持事务 ACID 特性.在通讯协议上,TiDB 选择与 MySQL 完全兼容,并尽可能兼容 MySQL 的语法.因此,基于 MySQL 数据库开发的系统,大多数可以平滑迁移至 TiDB,而几乎不用修改代码.对用户来说,迁移成本极低,过渡自然. 然而,仍有一些 MySQL 的特性和行为,TiDB 目前暂时不支持或表现与 MySQL 有差异.

  • MySQL安装后默认自带数据库的作用详解

    大家在学习MySQL时,安装后都会发现里边已经自带了几个默认的数据库,我装的MySQL5.5里边自带六个数据库 网上查了一些资料对这几个数据库的功能做一下学习. 1.information_schema 这个库中有很多的表 information_schema数据库表说明: SCHEMATA表:提供了当前mysql实例中所有数据库的信息.是show databases的结果取之此表. TABLES表:提供了关于数据库中的表的信息(包括视图).详细表述了某个表属于哪个schema,表类型,表引擎,

  • MySQL数据库JDBC编程详解流程

    目录 一.数据库编程的必备条件 二.Java的数据库编程:JDBC 三.JDBC工作原理 四.JDBC开发步骤 五.JDBC操作步骤 六.优化JDBC的部分代码 1.获取数据库连接对象 2.操作命令对象Statement 3.ResultSet对象 4.总结 一.数据库编程的必备条件 编程语言: 如Java.C++.C.Python等 数据库: 如Oracle.MySQL.SQL Server等 数据库驱动包: 不同的数据库,要使用编程语言来操作时,就需要使用该数据库厂商提供的数据库驱动包. 如

  • MySQL 数据库 source 命令详解及实例

    MySQL 数据库 source 命令详解及实例 MySQL 数据库 source 命令,该命令是数据库导入命令.source 命令的用法非常简单,首先你需要进入 MySQL 数据库的命令行管理界面,然后选择需要导入的数据库,执行 source 命令.如下图所示. MySql 数据库 source 命令 mysql> use test Database changed mysql> set names utf8; Query OK, 0 rows affected (0.00 sec) mys

  • 在windows上安装不同(两个)版本的Mysql数据库的教程详解

    1.起因: 需要导入一个sql文件,发现死活导不进去.当执行到这一句时,就有问题.经过一番搜索,原来是我的数据库版本(原先Mysql版本5.5)低了,而支持该语句的版本应该是至少要5.7.那我索性就去Mysql官网去下载了个最新版本的(8.0.15). `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP 2.过程: 那么问题来了:有两个解决方案.1.直接卸载掉5.5版本的Mysql,直接安装8.0.15版本的.2.在不卸

  • CentOS7安装调试Mysql数据库的步骤详解【实例】

    本实例要求下载并安装调试Mysql数据库. 第一步:下载Mysql数据库安装所需的全部软件. 我已提前下载好数据库软件,直接在服务器下载即可,如下: 通过scp直接下载: [root@agt20 ~]# scp root@192.168.122.10:/root/mysql* /root/ 软件如下图: [root@agt20 ~]# ls mysql-* mysql-5.7.17.tar mysql-community-client-5.7.17-1.el7.x86_64.rpm mysql-

  • 在IntelliJ IDEA中使用Java连接MySQL数据库的方法详解

    一.下载MySQL数据库并进行安装和配置 下载地址:https://dev.mysql.com/downloads/installer/ 二.下载JDBC连接器 下载地址:mysql-connector-java-8.0.22 下载好压缩包并解压后找到mysql-connector-java-8.0.22.jar文件放在自己指定的路径下. 三.在项目中导入jar包 用于测试数据库连接的测试类Test.java代码: import java.sql.Connection; import java.

  • Linux手动部署远程的mysql数据库的方法详解

    一.安装mysql 运行以下命令更新YUM源. rpm -Uvh  http://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm 运行以下命令安装MySQL. yum -y install mysql-community-server 运行以下命令查看MySQL版本号. mysql -V 返回结果如下,表示MySQL安装成功. mysql  Ver 14.14 Distrib 5.7.31, for Linux (x86_

  • nodejs环境快速操作mysql数据库的方法详解

    github地址https://github.com/dmhsq/dmhsq-mysql-db 可用于腾讯云SCF以及云开发环境 错误处理尚未完善 错误参考mysql错误 引入依赖包 npm install dmhsq-mysql-db 效果如下 简化了mysql的使用 安装依赖 npm install dmhsq-mysql-db 使用示例 快速操作mysql 错误处理尚未完善 部分错误参考mysql错误 引入资源 const database = require("dmhsq-mysql-d

  • python中的mysql数据库LIKE操作符详解

    LIKE 操作符用于在 WHERE 子句中搜索列中的指定模式. 语法: SELECT column_name(s) FROM table_name WHERE column_name LIKE pattern pattern这里就是放指定模板的地方,而这里就要用到" % ",也叫做通配符 %如果是放在条件前面,那就是查以...结尾的数据:例如:%李 %如果是放在条件后面,那就是查以...开头的数据:例如:李% %如果是在条件前后都存在,那就是查包含的数据:例如:%李% 小知识点: ER

随机推荐