精华内容
下载资源
问答
  • mysql分库分表实战
    2021-01-25 22:53:12

    为什么要分库分表

    在大型网站中,当用户量以及用户产生的业务数据量达到单库单表性能极限时,为了支撑业务可持续发展,对于重要的核心业务必然要进行分库分表来存储业务数据。

    对于非核心业务产生的大量数据,例如爬虫爬取的信息,论坛产生的数据等,可以考虑把数据保存在像MongoDB这样的NoSQL存储里面,这些存储会自动分片存储业务数据,不需要业务逻辑进行干预。

    分库分表的缺点

    分库分表之后实际会产生一系列问题,对于每种问题我们需要针对性进行解决,保障系统稳定运行。

    下面就分库分表会产生的问题进行列举:

    数据的获取需要通过改写路由去多个分片获取数据,然后进行聚合。对于某些SQL需要全库表路由来获取数据,有的SQL甚至需要把SQL语句改写成笛卡尔积去获取分片数据,这中间会产生大量数据库连接造成数据库资源浪费。

    分库分表之后需要分布式事务来保证数据一致性。对于不同业务可以采用不同的分布式事务实现方式来对数据一致性提供不同的保障级别,这其实是一种业务的权衡。

    对于分页数据的获取,最好是采用异构数据到ElasticSearch或者其他分布式存储中,既能提供高性能的检索,也能避免由于分库分表导致分页数据获取困难的问题。当然是用汇总表对业务数据汇总,提供一个兜底方案也是很有必要的。

    什么是分库分表

    分库分表就是把用户数据进行拆分,存储到不同的数据库的不同数据表中。

    分库分表主要是用来承载用户的写负载,用户数据在单库单表存储量太大,会导致用户读取阻塞,进而导致用户无法写入。

    对于读负载可以通过读写分离,异构到ElasticSearch等方案进行很好的解决。

    对于写负载只能通过分库分表的方案进行解决。

    怎么进行分库分表

    分库分库的第一个要点是如何拆分数据。

    大体有以下几种拆分方案,每种方案都有优点也有缺点:

    按时间段进行拆分

    取模法

    查表法

    一般业务中使用的都是取模法,因为通过取模法,业务数据可以均匀分布在数据库中,用户请求也可以均匀的请求不同的数据库,可以避免热点表,但是对于不同用户的数据的交互或者联合查询就需要进行多次查询。

    当然在某些业务场景下使用其他拆分方案可能会更好,例如历史日志,可以采用日志数据按照时间拆分,进行归档的方式来进行分库分表。

    分库分表的第二个要点就是拆分之后SQL如何路由,如何执行。

    对于这一点一般采用现有的框架来进行实现,如MyCAT,Sharding-Jdbc等,他们基于不同的模式为我们封装好了拆分之后SQL路由和执行的细节。

    其中MyCAT是使用代理服务器的方式实现,Sharding-Jdbc是使用嵌入应用系统中直接调用数据库的方式实现的。

    实践中可以根据不同项目组的情况使用不同的方案。

    分库分表实践

    下面介绍通过Sharding-Jdbc来实现分库分表。通过Sharding-Jdbc可以很方便地实现数据库的拆分,SQL自动改写,路由,请求结果归并等具体的数据处理细节。

    下面介绍一个简单的分库分表的实现流程,更多实现细节和使用方案可以参考官方文档。

    引入Jar包

    org.apache.shardingsphere

    sharding-jdbc-core

    4.1.1

    配置数据源

    Map dataSourceMap = new HashMap<>();

    *// 配置第 1 个数据源*

    DruidDataSource dataSource1 = new DruidDataSource();

    dataSource1.setDriverClassName("com.mysql.cj.jdbc.Driver");

    dataSource1.setUrl("jdbc:mysql://localhost:3306/continuous?useUnicode=true&characterEncoding=utf-8&useSSL=false");

    dataSource1.setUsername("root");

    dataSource1.setPassword("12345678");

    dataSourceMap.put("ds0", dataSource1);

    *// 配置第 2 个数据源*

    DruidDataSource dataSource2 = new DruidDataSource();

    dataSource2.setDriverClassName("com.mysql.cj.jdbc.Driver");

    dataSource2.setUrl("jdbc:mysql://localhost:3306/continuous2?useUnicode=true&characterEncoding=utf-8&useSSL=false");

    dataSource2.setUsername("root");

    dataSource2.setPassword("12345678");

    dataSourceMap.put("ds1", dataSource2);

    配置分库分表的方案

    *// 配置 t_order 表规则*

    TableRuleConfiguration orderTableRuleConfig =

    new TableRuleConfiguration("t_order", "ds0.order0, ds1.order1");

    *// 配置分库策略*

    orderTableRuleConfig.setDatabaseShardingStrategyConfig(

    new StandardShardingStrategyConfiguration("user_id", databaseShardingAlgorithm));

    *// 配置分表策略*

    orderTableRuleConfig.setTableShardingStrategyConfig(

    new StandardShardingStrategyConfiguration("user_id", tableShardingAlgorithm));

    *// 配置业务主键ID生成方案*

    orderTableRuleConfig.setKeyGeneratorConfig(

    new KeyGeneratorConfiguration("SNOWFLAKE", "user_id"));

    *// 配置分片规则*

    ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();

    shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);

    *// 设置属性*

    Properties properties = new Properties();

    properties.put("sql.show", true);

    *// 创建 ShardingSphereDataSource*

    return ShardingDataSourceFactory.*createDataSource*(dataSourceMap, shardingRuleConfig, properties);

    基于业务进行不同的定制

    对于不同业务的查询,有时需要使用Hint的方式进行分库分表。

    最佳实践和好的资料

    文章中说明什么情况下分库,什么情况下分表。

    数据量大,就分表;并发高,就分库。

    还详细比较了三种数据库拆分方案的细节。

    Hit分片方式的使用,有几篇文章写很不错

    在这篇文章有使用查表法对于非分片key查询数据的支持,很值得借鉴。

    更多相关内容
  • mysql 分库分表实战

    千次阅读 2022-03-31 15:14:22
    解决方案一: 如果是sql语句,添加上: UNIQUE KEY `name_index` (`name`) USING BTREE COMMENT '名字索引' 解决方案二: import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" //执行init(),注册...

    1、生成1000w条数据。

    如果用代码,会很久。1w条用了1分钟,1000w条数据估计1000分钟。

    下面直接用存储过程。

     发现插入10000条数据用了0.9s, 插入10w条数据用了4.7s, 插入100w条数据用了58s左右,1000w条数,参考文章https://www.jb51.net/article/207999.htm

     事先先创建一个表

    CREATE TABLE `users` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) DEFAULT NULL,
      `home` varchar(255) DEFAULT NULL,
      `balance` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8mb4;
    登录mysql客户端 依次输入如下。
    use test
    delimiter "$"
    
     create procedure bathInsert(in args int)
        -> begin
        -> declare i int default 1;
        -> start transaction;
        -> while i <= args do
        -> insert into users(id,name,home,balance) value(i,"dgx","shengzheng","level-go");
        -> set i = i+1;
        -> end while;
        -> commit;
        -> end
        -> $
    
    call bathInsert(10000000);$
    
    
    

    即快速生成了1000w条数据。耗时1 min 14.77 sec)。

    Query OK, 0 rows affected (1 min 14.77 sec)

    如果name不一样,用concat函数,也可以在navicat那里编辑输入

    CREATE DEFINER=`root`@`localhost` PROCEDURE `bathInsert2`(in args int)
    begin
    declare i int default 1;
    start transaction;
    while i <= args do
    insert into users(id,name,home,balance) value(i,concat("dgx",i),"shengzheng","level-go");
     set i = i+1;
    end while;
    commit;
    end

    SELECT * FROM users WHERE `name` ='dgx1214000'  没有用索引 耗时 2s.035多一点点。

    解决方案一:

    如果是sql语句,添加上:

    UNIQUE KEY `name_index` (`name`) USING BTREE COMMENT '名字索引'

    解决方案二:

    import (
    	"database/sql"
    	"fmt"
    	_ "github.com/go-sql-driver/mysql" //执行init(),注册mysql驱动
    	"hash/crc32"
    	"strconv"
    	"time"
    )
    func main() {
    	//方案二:用go语言官方自带驱动
    	dsn := "root:root@tcp(127.0.0.1:3306)/goinsum"
    	db, err := sql.Open("mysql", dsn)
    	if err != nil {
    		panic(error.Error(err))
    	}
    	fmt.Println("parameter verification succeeded!")
    	//MakeHashTrans(db)
    	ShardingSingle(db)
    
    }
    
    func MakeHashTrans(db *sql.DB) {
    	rows, err := db.Query("select * from users")
    	cloumns, err := rows.Columns()
    	if err != nil {
    		fmt.Println(err)
    	}
    	// 定义10个变量,分别存储不同的拼接内容,分别计数,如果到了10000,就批量插入数据库。
    	var resultkey = [10]string{}
    	var resultkeylast = [10]string{}
    	var count = [10]int{}
    
    	fmt.Println(resultkey)
    	for rows.Next() {
    		err := rows.Scan(&cloumns[0], &cloumns[1], &cloumns[2], &cloumns[3])
    		if err != nil {
    			fmt.Println(err)
    		}
    		result := "(" + cloumns[0] + ",'" + cloumns[1] + "','" + cloumns[2] + "'," + "'" + cloumns[3] + "'" + ")" + ","
    
    		fmt.Println(cloumns[0])
    		hashValue := crc32.ChecksumIEEE([]byte(cloumns[1]))
    		tableId := hashValue % 10
    		fmt.Println(tableId)
    		a := int(tableId)
    		count[a] = count[a] + 1
    		fmt.Println(count[a])
    		resultkey[a] = resultkey[a] + result
    		// 积累到了10000,就批量插入数据库
    		if count[a] == 100000 {
    			resultkeylast[a] = resultkey[a][0 : len(resultkey[a])-1]
    			qry := "insert into user_" + strconv.Itoa(a) + " (`uid`, `name`, `home`, `balance`) values" + resultkeylast[a]
    			//fmt.Println(qry)
    			_, err = db.Exec(qry)
    			if err != nil {
    				fmt.Println(err)
    			}
    			// 清零处理
    			count[a] = 0
    			resultkey[a] = ""
    		}
    		fmt.Printf("a ==%d,count[a]==%d", a, count[a])
    
    	}
    
    }
    
    // 性能大PK,
    func ShardingSingle(db *sql.DB) {
    	//未分表搜索
    	//SELECT * from users WHERE name = 'dgx51322'
    	name := "dgx51322"
    
    	st1 := time.Now()
    	row, _ := db.Query("select * from users where name = '" + name + "'")
    	fmt.Println("single query unsharding spend time:", time.Since(st1))
    	cloumn1, err := row.Columns()
    	if err != nil {
    		fmt.Println(err)
    	}
    	for row.Next() {
    		row.Scan(&cloumn1[0], &cloumn1[1], &cloumn1[2], &cloumn1[3])
    		fmt.Println(cloumn1)
    	}
    
    	//分表搜索
    	a := crc32.ChecksumIEEE([]byte(name)) % 10
    	qryString := "select * from user_" + strconv.Itoa(int(a)) + " where name = '" + name + "'"
    	st2 := time.Now()
    	row2, _ := db.Query(qryString)
    	fmt.Println("single query sharding spend time:", time.Since(st2))
    	cloumn2, _ := row2.Columns()
    	for row2.Next() {
    		row2.Scan(&cloumn2[0], &cloumn2[1], &cloumn2[2], &cloumn2[3], &cloumn2[4])
    		fmt.Println(cloumn2)
    	}
    	return
    }
    

    性能大pk,比较:

    parameter verification succeeded!
    single query unsharding spend time: 2.1368947s
    [51322 dgx51322 shengzheng level-go]
    single query sharding spend time: 1.5661ms
     

    展开全文
  • MySQL 分库分表实战

    2021-01-04 23:42:17
    1.1垂直拆分--分库 1.2、垂直分表--分表 2、水平拆分 2.1、水平分表 2.2、水平分库 3、拆分规则 3.1 水平分库规则 3.2 水平分表规则 一、背景描述 和遇到的问题 二、如何解决? 1、垂直拆分 1.1垂直...

    目录

    一、背景描述 和遇到的问题

    二、如何解决?

    1、垂直拆分

    1.1垂直拆分--分库

    1.2垂直分表--分表

    2、水平拆分

    2.1、水平分表

    2.2、水平分库

    3、拆分规则

    3.1 水平分库规则

     3.2 水平分表规则

    三、 分库分表需要注意的点



    一、背景描述 和遇到的问题

    二、如何解决?

    1、垂直拆分

    1.1垂直拆分--分库

    1.2、垂直分表--分表

    2、水平拆分

    2.1、水平分表

    2.2、水平分库

    有点像分布式... 例如GP,分库硬件上投入需要多,性能比较好。

    3、拆分规则

    3.1 水平分库规则

     3.2 水平分表规则

    用手机号登录做hash,手机号登录速度比较快。

    用用户ID做 hash, 用户登录验证比较快,

     用手机号登录和用账户登录本身就是矛盾的,如何权衡?如何做到都不扫描所有库呢?

    增加一个路由表来解决。

    如法炮制,例如这道题:答案是A、C

    三、 分库分表需要注意的点

    展开全文
  • 由于业务需要,需要对Mysql数据库进行分库分表,故而最近一直在整理分库分表的相关知识,现手上的工作也告一段落了,抽空将自己最近的学习结果转化为博文,分享给大家,本博文打算做成一个系列的,首先是分库分表的...

    由于业务需要,需要对Mysql数据库进行分库分表,故而最近一直在整理分库分表的相关知识,现手上的工作也告一段落了,抽空将自己最近的学习结果转化为博文,分享给大家,本博文打算做成一个系列的,首先是分库分表的理论知识的了解,其次是基于Java编程语言的分库分表的框架的开发,最后是分库分表的编制。让大家不仅仅从理论上了解mysql的分库分表,通过代码来更深层次的了解,理论是如何落地到实践的。最后非常感谢《可伸缩服务架构 框架与中间件》这本书的作者么,本博文的代码实现是参考此书,然后结合当前系统平台框架开发而成。

    坚持我写作的一贯风格,我们先需要带着问题来了解mysql的分库分表

    • 什么是分库分表,为什么我们需要分库分表
    • 如何进行分库分表,有什么优缺点
    • 对于分库分表有哪些架构设计,对于后期的扩容扩展怎么样
    • 目前行业内流行的解决方案有哪些?各自有什么特点
    • 自己设计一个数据库分库分表的框架,如何设计,需要考虑哪些因素

    为什么需要分库分表

    随着我们的系统运行,存储在关系型数据库的数据量会越来越大,系统的访问的压力也会随之增大,如果一个库中的表数据超过了一定的数量,比如说mysql中的表数据达到千万级别,就需要考虑进行分库分表;

    其次随着表数据的不断增大,会发现,查询也随着变得缓慢,如果添加索引的话,会发现影响到了新增和删除的性能,如果我们将数据库分散到不同的表上,单表的索引大小就得到了控制,对索引以及表结构的变更会变得很方便和高效;

    当数据库实例的吞吐量达到性能的瓶颈时,我们需要扩展数据库实例,让每个数据库实例承担其中一部分数据库的请求,分解总体的大请求量的压力;

    在数据库进行扩容的时候对应用层的配置改变最少, 就需要在每个数据库实例中预留足够的数据库数量

    以上的情况我们都可以使用分库分表,那么什么是分库分表呢?

    简而言之就是数据拆分:将一个表结构分为多个表,或者将一个表数据分片后放入多个表,这些表可以放在同一个数据库里,也可以放到不同的数据库中,甚至可以放到不同的数据库实例中

    数据拆分的方式

    数据拆分有两种方式:

    • 垂直拆分: 根据业务的维度,将原本一个库中的表拆分多个表,每个库中表与原有的结构不同
    • 水平拆分: 根据分片算法,将一个库拆分成多个库,每个库依旧保留原有的结构

    在实际的开发过程中,通常是先进行维度拆分形成微服务结构,然后再进行水平拆分

    分库分表

    比如我们有一张表,随着业务的不断进行,mysql中表中数据量达到了10亿,若是将数据存放在一张表中,则性能一定不会太好,根据我们使用的经验,mysql数据库一张表的数据记录极限一般在5000万左右,所以我们需要对进行分片存储(水平拆分),按照5000万一个单位来拆分的话,需要切片数量20个,也就是20个数据库表

    如果将20个相同业务表存放在同一个数据库中,那么单个数据库实例的网卡I/O、内存、CPU和磁盘性能是有限的,随着数据库访问频率的增加,会导致单个数据库实例和数据库达到性能瓶颈,因此我们需要将20个表分到多个数据库和多个数据库实例中,具体的评估如下:
    【TODO 对数据库实例和数据库表的数量的评估】

    image

    如何进行分库分表

    分库分表是对数据库拆分的一种解决方案,根据实施切片逻辑的层次不同,我们将分库分表方案大致分为三大类:客户端分片、代理分片和支持事务的分布式数据库

    • 客户端分片

    所谓的客户端分片即在使用数据库的应用层直接操作分片逻辑,分片规则需要在同一个应用的多个节点间进行同步,每个应用层嵌入一个操作切片的逻辑实现。

    image

    在客户端分片,目前主要有以下三种方式:

    1. 在应用层直接实现

    这是一种非常通用的解决方案,直接在应用层读取分片规则,解析分片规则,根据分片规则实现切分的路由逻辑,从应用层直接决定每次操作应该使用哪个数据库实例中的对应的数据库

    这种解决方案虽然有一定的代码侵入,但是实现起来比较简单,但是切片的逻辑是自己开发的, 如果生产上遇到了问题,能快速定位解决;

    当然这种方式也存在缺点:代码的耦合度比较高,其次这种实现方式会让数据库保持的链接比较多,这要看应用服务的节点数量,需要提前进行容量上的评估

    1. 通过定制JDBC协议实现

    这种解决方案主要是为了解决1中解决方案中的代码耦合,通过定制JDBC协议来实现(主要是针对业务逻辑层提供与JDBC一致的接口),让分库分表在JDBC的内部实现

    目前当当网开源的框架:Sharding JDBC 就是使用这种解决方案来实现的

    1. 通过定制ORM框架实现

    目前ORM框架非常流行,流行的JPA、Mybatis和Hibernate都是优秀的ORM框架,通过定制ORM框架来实现分库分表方案,常见的有基于Mybatis的分库分表方案的解决;

        <select id="selectUser" parameterType="java.util.Map" resultType="User">
            select user_id as userId,user_name as userName
            from user_#{index}
            where user_id = #{userId}
        </select>
    
    • 代理分片

    代理分片就是在应用层和数据库层之间添加一个代理层,把分片的路由规则配置在代理层,代理层对外提供与JDBC兼容的接口给应用层,在业务实现之后,在代理层配置路由规则即可;

    image

    这种方案的优点:让应用层的开发人员专注于业务逻辑的实现,把分库分表的配置留给代理层处理
    同样的业务存在缺点:增加了代理层,这样的话对每个数据库操作都增加了一层网络传输,这对性能是有影响的,同时需要维护增加的代理层,也有了硬件成本,线上生产环境出现了问题,不能迅速定位,需要有一定的技术专家来维护

    我们常见的 Mycat就是基于此种解决方案来实现的

    • 支持事务的分布式数据库

    支持分布式事务的框架,目前有OceanBase、TiDB框架,这些框架将可伸缩特定和分布式事务的实现包装到了分布式数据库内部实现,对使用者透明,使用者不需要直接控制这些特性,但是对事务的支持不如关系型数据,适合大数据日志系统、统计系统、查询系统、社交网站等

    分库分表的架构设计

    上面我们介绍过数据拆分的两种方式:垂直拆分和水平拆分;

    拆分方式优点缺点
    垂直拆分1. 拆分后业务清晰,拆分规则明确
    2. 系统之间进行整合或扩展容易
    3. 按照成本、应用等级、应用的类型等将表放到不同的机器上,便于管理
    4.便于实现动静分离、冷热分离的数据库表的设计模式
    5. 数据维护简单
    1. 部分业务表无法进行关联、只能通过接口的方式来解决,提高了系统的复杂度
    2. 受每种业务不同的限制,存在单库性能瓶颈,对数据扩展和性能提升不友好
    3. 事务处理复杂
    水平拆分1. 单裤单表的数据保持一定的量级,有助于性能的提高
    2. 切分的表的结构相同,应用层改造较少,只需要增加路由规则即可
    3. 提高了系统的稳定性和负载能力
    1. 切分后数据是分散的,很难利用数据库的关联查询,跨库查询性能较差
    2. 拆分规则难以抽象
    3. 分片数据的一致性难以解决
    4. 数据扩容的难度和维护量极大

    综上所述,我们发现垂直拆分和水平拆分具有共同点:

    1. 存在分布式事务问题
    2. 存在跨节点join的问题
    3. 存在跨节点合并排序、分页的问题
    4. 存在多数据源管理的问题

    垂直拆分更偏向于业务拆分的过程,在技术上我们更倾向于水平切分的方案;

    常见的分片策略:

    • 按照哈希切片

    对数据库的某个字段进行来求哈希,再除以分片总数后取模,取模后相同的数据为一个分片,这样将数据分成多个分片的方法叫做哈希分片

    我们大多数在数据没有时效性的情况下使用哈希分片,就是数据不管是什么时候产生的,系统都需要处理或者查询;

    优点缺点
    数据切片比较均匀,数据压力分散的效果好数据分散后,对于查询需求需要进行聚合处理
    • 按照时间切片

    按照时间的范围将数据分布到不同的分片上,比如我们可以将交易数据按照与进行切片,或者按照季度进行切片,由交易数据的多少来决定按照什么样的时间周期来进行切片

    这种切片方式适合明显时间特点的数据,常见的就是订单历史查询

    分布式事务

    本博文不进行分布式事务的分析和实践,后期我会更新一系列的分布式事务的博文,一起探讨分布式事务的原理、解决方案和代码实践等,本博文简单介绍了分布式事务的解决方案;

    上面说到的,不管是垂直拆分还是水平拆分,都有一个共同的问题:分布式事务

    我们将单表的数据切片后存储在多个数据库甚至是多个数据库实例中,所以依靠数据库本身的事务机制不能满足需要,这时就需要用到分布式事务来解决了

    三种解决方案

    • 两阶段提交协议

    两阶段提交协议中的两阶段是:准备阶段和提交阶段,两个阶段都是由事务管理器(协调者)发起,事务管理器能最大限度的保证跨数据库操作的事务的原子性。

    具体的交互逻辑如下:

    image

    优点缺点
    是分布式系统环境下最严格的事务实现防范,
    保证了数据一致性和操作原子性
    1. 难以进行水平伸缩,因为在提交事务过程中,事务管理器需要和每个参与者进行准备和提交的操作协调
    2.每个参与者之间的协调需要时间,参与者一多的话,则锁定资源和消费资源之间的时间差就边长
    3. 两阶段提交协议是阻塞协议,在极端情况下不能快速响应的话,会造成阻塞问题
    • 最大努力保证模式

    这是一种非常通用的保证分布式一致性的模式,适合对一致性要求不是十分严格的但是对性能要求比较高的场景

    最大努力保证模式:在更新多个资源时,将多个资源的提交尽量延后到最后一刻进行处理,这样的话,如果业务流程出现问题,则所有的资源更新都可以回滚,事务仍然保持一致。

    最大努力保证模式在发生系统问题,比如网络问题等会出现问题,造成数据一致性的问题 ,这是就需要进行实时补偿,将已提交的事务进行回滚

    一般情况下,使用消息中间件来完成消费者之间的事务协调,客户端从消息中间件的队列中消费消息,更新数据库,此时会涉及到两个操作,一是从消息中间件消费消息,二是更新数据库,具体的操作步骤如下:

    1. 开启消息事务
    2. 接收消息
    3. 开启数据库事务
    4. 更新数据库
    5. 提交数据库事务
    6. 提交消息事务

    上述步骤最关键的地方在5和6,如果5成功了,但是6出现了问题,导致消息中间件认为消息没有被成功消费,既有的机制会重新再消费消息,就会出现消息重复消费,这是需要幂等处理来避免消息的重新消费

    其次我们还需要注意消息消费的顺序性问题,以及消费过程中是否调用远程接口等耗时操作

    优点缺点
    性能较高1. 数据一致性不能完美保证,只能是最大保证
    2. 可能出现消息重复消费(幂等处理)
    3. 数据库事务可能存在远程操作嵌套,互相影响
    • 事务补偿机制

    以上提到的两种解决方案:两阶段提交协议对系统的性能影响较大,最大努力保证模式会是多个分布式操作互相嵌套,有可能互相影响,那么我们采用事务补偿机制:

    事务补偿即在事务链中的任何一个正向事务操作,都必须存在一个完全符合回滚规则的可逆事务。如果是一个完整的事务链,则必须事务链中的每一个业务服务或操作都有对应的可逆服务。对于Service服务本身无状态,也不容易实现前面讨论过的通过DTC或XA机制实现的跨应用和资源的事务管理,建立跨资源的事务上下文.

    我们通过跨银行转账来说明:

    首先调用取款服务,完全调用成功并返回,数据已经持久化。然后调用异地的存款服务,如果也调用成功,则本身无任何问题。如果调用失败,则需要调用本地注册的逆向服务(本地存款服务),如果本地存款服务调用失败,则必须考虑重试,如果约定重试次数仍然不成功,则必须log到完整的不一致信息。也可以是将本地存款服务作为消息发送到消息中间件,由消息中间件接管后续操作。

    最后添加的重试机制是最大程度的确保补偿服务执行,保持数据的一致性,如果重试之后还是失败,则将操作保存在消息中间件中,等待后续处理,这样就更多了一重保障

    image

    分库分表引起的问题

    由于将完整的数据分成若干份,在以下的场景中会产生多种问题

    • 扩容与迁移

    在分库分表中,如果涉及的分片已经达到了承载数据的最大值,就需要对集群进行扩容,通常包括以下的步骤

    1. 按照新旧分片规则,对新旧数据库进行双写
    2. 将双写前按照旧分片规则写入的历史数据,根据新分片规则迁移写入新的数据库
    3. 将按照旧的分片规则查询改为按照新的分片规则查询
    4. 将双写数据库逻辑从代码中下线,只按照新的分片规则写入数据
    5. 删除按照旧分片规则写入的历史数据

    2步骤中迁移数据时,数据量非常大,通常会导致不一致,因此需要先迁移旧的数据,洗完后再迁移到新规则的新数据库下,再做全量对比,对比评估在迁移过程中是否有数据的更新,如果有的话就再清洗、迁移,最后以对比没有差距为准

    • 分库分表维度导致的查询问题

    进行了分库分表以后,如果查询的标准是分片的主键,则可以通过分片规则再次路由并查询,但是对于其他主键的查询、范围查询、关联查询、查询结果排序等,并不是按照分库分表维度查询的;

    这样的话,解决方案有以下三种:

    1. 在多个分片表中查询后合并数据集,这种方式的效率最低
    2. 冗余记录多份数据,方便查询, 缺点是需要额外维护一份数据,浪费资源
    3. 通过搜索引擎解决,但如果实时性要求很高,就需要实现实时搜索,可以利用大数据相关特性来解决
    • 跨库事务难以实现

    同时操作多个库,则会出现数据不一致的情况,此时可以引用分布式事务来解决

    • 同组数据跨库问题

    要尽量把同一组数据放到同一数据库服务器上,不但在某些场景下可以利用本地事务的强一致性,还可以是这组数据自治

    主流的解决方案

    目前针对mysql的分库分表,行业内主流的解决方案有:ShardingJDBC、Mycat

    Mycat代理分片框架

    Mycat是一款面向企业级应用的开源数据库中间件产品,他目前支持数据库集群,分布式事务与ACID,被普遍视为基于Mysql技术的集群分布式数据库解决方案

    Mycat支持多种分片规则:

    • 枚举法
    • 固定分片的hash算法
    • 范围约定
    • 求模法
    • 日期列分区法
    • 通配取模
    • ASCII码求模通配
    • 编程指定
    • 截取数据哈希解析
    • 一致性Hash

    具体的Mycat使用方法,以后应该会有一博文来整理,敬请期待啊~~~

    展开全文
  • **当单表达到几千万时,查询一次要很久,如果有联合查询,有可能会死在那分库分表主要就是解决这个问题,减小数据库的负担,缩短查询时间**分库:1)按功能分用户类库、商品类库、订单类库、日志类、统计类库…2)按...
  • MySQL 分库分表实践

    千次阅读 2022-04-07 09:46:55
    一、为什么要分库分表 二、库表太大产生的问题 三、垂直拆分 1. 垂直分库 2. 垂直分表 四、水平分库分表 2. 测试水平分表
  • 一、背景发布上篇文章浅谈订单重构之路之后,有很多小伙伴想知道,分库分表具体是如何实现的。那么这篇文章具体介绍下,分库分表实战。二、目标本文将完成如下目标:* 分表数量: 256 分库...
  • 当单表达到几千万时,查询一次要很久,如果有联合查询,有可能会死在那分库分表主要就是解决这个问题,减小数据库的负担,缩短查询时间分库:1)按功能分用户类库、商品类库、订单类库、日志类、统计类库...1)按地区...
  • ShardingSphere 分库分表 什么是 ShardingSphere Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款相互独立,却又能够混合部署配合使用的...
  • 按照规矩,这里应该介绍一下golang和分库表,懒得写,跳过。本文主要介绍两种分表方式,hash和range,对应不同对业务特性,假设有这样一个user表,字段id,name,home,balance:user表数量大概1000w条:一个查询...
  • 当单表达到几千万时,查询一次要很久,如果有联合查询,有可能会死在那分库分表主要就是解决这个问题,减小数据库的负担,缩短查询时间分库:1)按功能分用户类库、商品类库、订单类库、日志类、统计类库...1)按地区...
  • Python+MySQL分表分库实战,适合对MySQL 分库分表感兴趣的读者。
  • MySQL分库分表原理

    2021-08-26 23:09:58
    文章目录1、为什么要分库分表02、分库分表03、不停机分库分表数据迁移4、分库分表实现 1、为什么要分库分表 分库分表目的:解决高并发,和数据量大的问题。 1、高并发情况下,会造成IO读写频繁,自然就会造成读写...
  • 五、分库分表问题 1、非partition key的查询问题 基于水平分库分表,拆分策略为常用的hash法。 端上除了partition key只有一个非partition key作为条件查询 映射法 基因法 注:写入时,基因法生成user_id,如图。...
  • 实际应用中MySQL分库分表实践总结原理 互联网系统需要处理大量用户的请求。比如微信日活用户破10亿,海量的用户每天产生海量的数量;美团外卖,每天都是几千万的订单,那这些系统的用户表、订单表、交易流...
  • 并以这些问题为线索逐步分析,在分库分表之前,有没有一些方案可以初步解决这些问题,随着订单数据量的增加,为什么这些方案会失效,最后导致不得不分库分表。 而分库分表方案具体该如何设计? 方案设计完成之后又该...
  • 因此,我结合去年做的一个大型分库分表项目,来复盘一下完整的分库分表从架构设计 到 发布上线的实战总结。 1.前言 为什么需要做分库分表。这个相信大家多少都有所了解。 海量数据的存储和访问成为了MySQL数据库...
  • 简单介绍了分库分表的概念以及相关问题。
  • 数据库分库分表除了使用中间件来代理请求分发之外,另外一种常见的方法就是在客户端层面来分库分表 —— 通过适当地包装客户端代码使得分库分表的数据库访问操作代码编写起来也很方便。本文的分库分表方案基于 ...
  • 分库分表实战-- Mycat实战

    多人点赞 热门讨论 2022-04-01 19:12:26
    Mycat、简介、核心概念、逻辑库、逻辑表、分片表、非分片表、ER表、全局表、分片节点、节点主机、分片规则、server配置、Schema配置、rule配置、Mycat安装、分库分表、读写分离、强制路由、主从延时切换、Mycat事务...
  • 这里也不去讲解其他分库分表的技术了,例如mycat,可以自行博文搜索了解 大库拆分成小库、大表 拆分成小表 一 数据库垂直拆分 当下springcloud 、dubbo。。类似的这些微服务越来越受欢迎,我们可以根据业务的...
  • Mysql分库分表工具ShardingSphere

    千次阅读 2022-03-30 10:15:32
    ShardingSphere是一款起源...并逐渐由原本只关注于关系型数据库增强工具的ShardingJDBC升级成为一整套以数据片为基础的数据生态圈,更名为ShardingSphere。到2020年4月,已经成为了Apache软件基金会的顶级项目。 Sha

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,122
精华内容 3,648
关键字:

mysql分库分表实战

mysql 订阅