TiDB 是 PingCAP 公司受 Google Spanner / F1 论文启发而设计的开源分布式 HTAP (Hybrid Transactional and Analytical Processing) 数据库,结合了传统的 RDBMS 和NoSQL 的最佳特性。TiDB 兼容 MySQL,支持无限的水平扩展,具备强一致性和高可用性。TiDB 的目标是为 OLTP(Online Transactional Processing) 和 OLAP (Online Analytical Processing) 场景提供一站式的解决方案。
TiDB 是一个分布式 NewSQL 数据库。它支持水平弹性扩展、ACID 事务、标准 SQL、MySQL 语法和 MySQL 协议,具有数据强一致的高可用特性,是一个不仅适合 OLTP 场景还适合 OLAP 场景的混合数据库。
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。
MySQL是一种关系数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
MySQL所使用的 SQL 语言是用于访问数据库的最常用标准化语言。MySQL 软件采用了双授权政策,分为社区版和商业版,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,一般中小型网站的开发都选择 MySQL 作为网站数据库。
高度兼容MySql
大多数情况下,无需修改代码即可从MySql轻松迁移至TiDB,分库分表后的MySql集群亦可通过TiDB工具进行实时迁移。
水平弹性扩展
通过简单地增加新节点即可实现TiDB的水平扩展,按需扩展吞吐或存储,轻松应对高并发、海量数据场景。
分布式事务
TiDB 100% 支持标准的ACID事务。
真正金融级高可用
相比于传统主从(M-S)复制方案,基于Raft的多数派选举协议可以提供金融级的100%数据强一致性保证,且在不丢失大多数读本的前提下,可以实现故障的自动恢复(auto-failover),无需人工介入。
一站式HTAP解决方案
TiDB作为典型的OLTP(联机事务处理)行存数据库,同事兼具强大的OLAP(联机分析处理)性能,配合TiSpark,可提供一站式HTAP解决方案,一份存储同时处理OLTP&OLAP无需传统繁琐的ETL过程。
云原生SQL数据库
TiDB是为云而设计的数据库,同Kubernetes深度耦合,支持公有云、私有云和混合云,使部署、配置和维护变得十分简单。
TiDB的设计目标是100%的OLTP场景和80%的OLAP场景,更复杂的OLAP分析可以通过TiSpark项目来完成。TiDB对业务没有任何侵入性,能优雅的替换传统的数据库中间件、数据库分表分库等Sharding方案。同时它也让开发运维人员不用关注数据库Scale的细节问题,专注于业务开发,极大的提升研发的生产力。
在 TiDB 中执行的事务 b,返回影响条数是 1(认为已经修改成功),但是提交后查询,status 却不是事务 b 修改的值,而是事务 a 修改的值。
可见,MySql事务和TiDB事务存在这样的差异:MySql事务中,可以通过影响行数,作为写入(或修改)是否成功的依据;而TiDB中,这却是不可行的!
作为开发者我们需要考虑下面的问题:
同步RPC调用中,如果需要严格依赖影响条数以确认返回值,那将如何是好?
多表操作中,如果需要严格依赖某个主表数据更新结果,作为是否更新(或写入)其他表的判断依据,那将如何是好?
原因分析及解决方案
其简要流程如下:
在事务提交的PreWrite阶段,当“锁检查”失败时:如果开启冲突重试,事务提交将会进行重试;如果未开启冲突重试,将会抛出写入冲突异常。
可见,对于MySql,由于在写入操作时加上了排他锁,变相将并行事务从逻辑上串行化;而对于TiDB,属于乐观锁模型,在事务提交时才加锁,并使用事务开启时获取的“全局时间戳”作为“锁检查”的依据。
所以,在业务面避免TiDB事务差异的本质在于避免锁冲突,即,当事务执行时,不产生别的事务时间戳(无其他事务并行)。处理方式为事务串行化。
TiDB事务串行化
在业务层,可以借助分布式锁,实行串行化处理,如下:
基于Spring和分布式锁的事务管理器拓展
在Spring生态下,spring-tx中定义了统一的事务管理器接口:PlatformTransactionManager,其中有获取事务(getTransaction)、提交(commit)、回滚(rollback)三个基本方法;使用装饰器模式,事务串行化组件可做如下设计:
其中,关键点有:
超时时间:为避免死锁,锁必须有超时时间;为避免锁超时导致事务并行,事务必须有超时时间,而且锁超时时间必须大于事务超时时间(时间差最好在秒级)。
加锁时机:TiDB中“锁检查”的依据是事务开启时获取的“全局时间戳”,所以加锁时机必须在事务开启前。
事务模板接口设计
隐藏复杂的事务重写逻辑,暴露简单友好的API:
public interface LockTransactionTemplate {
/**
* 阻塞模式事务模板
* @param action 事务回调action
* @return
*/
public <T> T lockableExecute(TransactionCallback<T> action, LockKeys<?>...LockKeys);
}
/**
* 非阻塞模式事务模板
* @param action 事务回调action
* @return
*/
public <T> T lockableExecute(NonBlockingAcquireCallback<T> action, LockKeys<?>...LockKeys);
TiDB 查询和 MySQL 的差异
在 TiDB 使用过程中,偶尔会有这样的情况,某几个字段建立了索引,但是查询过程还是很慢,甚至不经过索引检索。
索引混淆型(举例)
表结构:
CREATE TABLE `t_test` (
`id` bigint(20) NOT NULL DEFAULT '0' COMMENT '主键id',
`a` int(11) NOT NULL DEFAULT '0' COMMENT 'a',
`b` int(11) NOT NULL DEFAULT '0' COMMENT 'b',
`c` int(11) NOT NULL DEFAULT '0' COMMENT 'c',
PRIMARY KEY (`id`),
KEY `idx_a_b` (`a`,`b`),
KEY `idx_c` (`c`)
) ENGINE=InnoDB;
查询:如果需要查询 (a=1 且 b=1)或 c=2 的数据,在 MySQL 中,sql 可以写为:
SELECT id from t_test where (a=1 and b=1) or (c=2);
MySQL 做查询优化时,会检索到 idx_a_b 和 idx_c 两个索引;但是在 TiDB(v2.0.8-9)中,这个 sql 会成为一个慢 SQL,需要改写为:
SELECT id from t_test where (a=1 and b=1) UNION SELECT id from t_test where (c=2);
小结:导致该问题的原因,可以理解为TiDB的sql解析还有优化空间。
冷热数据型(举例)
表结构:
CREATE TABLE `t_job_record` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`job_code` varchar(255) NOT NULL DEFAULT '' COMMENT '任务code',
`record_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '记录id',
`status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '执行状态:0 待处理',
`execute_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '执行时间(毫秒)',
PRIMARY KEY (`id`),
KEY `idx_status_execute_time` (`status`,`execute_time`),
KEY `idx_record_id` (`record_id`)
) ENGINE=InnoDB COMMENT='异步任务job'
数据说明:
冷数据,status=1的数据(已经处理过的数据);
热数据,status=0并且execute_time<=当前时间的数据。
慢查询:对于热数据,数据量一般不大,但是查询频率度很高,假设当前(毫秒级)时间为:1546361579646,则在MySql中,查询sql为:
SELECT * FROM t_job_record where status=0 and execute_time<= 1546361579646
这个在 MySQL 中很高效的查询,在 TiDB 中虽然也可从索引检索,但其耗时却不尽人意(百万级数据量,耗时百毫秒级)。
原因分析:在 TiDB 中,底层索引结构为 LSM-Tree,如下图:
当从内存级的C0层查询不到数据时,会逐层扫描硬盘中各层;且merge操作为异步操作,索引数据更新会存在一定的延迟,可能存在无效索引。由于逐层扫描和异步merge,使得查询效率较低。
优化方式:尽可能缩小过滤范围,比如结合异步job获取记录频率,在保证不遗漏数据的前提下,合理设置execute_time筛选区间,例如1小时,sql改写为:
SELECT * FROM t_job_record where status=0 and execute_time>1546357979646 and execute_time<= 1546361579646
优化效果:耗时10毫秒级别(以下)。
关于查询的启发
在基于TiDB的业务开发中,先摒弃传统关系型数据库带来的对sql先入为主的理解或经验,谨慎设计每一个sql,如DBA(数据库管理员)所提倡:设计sql时务必关注执行计划,必要时请教DBA。
和MySql相比,TiDB的底层存储和结构决定了其特殊性和差异性;但是,TiDB支持MySql协议,他们也存在一些共同之处,比如在TiDB中使用“预编译”和“批处理”,同样可以获得一定的性能提升。
服务端预编译
在MySql中,可以使用PREPARE stmt_name FROM preparable_stm对sql语句进行预编译,然后使用EXECUTE stmt_name [USING @var_name [,@var_name]…]执行预编译语句。如此,同一sql的多次操作,可以获得比常规sql更高得性能。
mysql-jdbc源码中,实现了标准的Statement和PreparedStatement的同时,还有一个ServerPreparedStatement实现,ServerPreparedStatement属于PreparedStatement的拓展,三者对比如下:
容易发现,PreparedStatement和Statement的区别主要区别在于参数处理,而对于发送数据包,调用服务器端的处理逻辑是一样(或类似)的;经测试,二者速度相当。其实PreparedStatement并不是服务器端预处理的;ServerPreparedStatement才是真正的服务器端预处理,速度也较PreparedStatement快;其使用场景一般是:频繁的数据库访问,sql数量有限(有缓存淘汰策略,使用不宜会导致两次IO)。
批处理:
对于多条数据写入,常用sql为insert…values(…),(…);而对于多条数据更新,亦可以使用update…case…when…then…end来减少IO次数,但他们都有一个特点,数据条数越多,sql越复杂,sql解析成本也更高,耗时增长可能高于线性增长。而批处理,可以复用一条简单的sql,实现批量数据的写入或更新,为系统带来更低、更稳定的耗时。
对于批处理,作为客户端,java.sql.Statement 主要定义了两个接口方法,addBatch 和 executeBatch 来支持批处理。
批处理的简要流程说明如下:
经业务中实践,使用批处理方式的写入(或更新),比常规 insert … values(…),(…)(或 update … case … when… then… end)性能更稳定,耗时也更低。
TiDB Server 负责接收 SQL 请求,处理 SQL 相关的逻辑,并通过 PD 找到存储计算所需数据的 TiKV 地址,与 TiKV 交互获取数据,最终返回结果。 TiDB Server 是无状态的,其本身并不存储数据,只负责计算,可以无限水平扩展,可以通过负载均衡组件(如LVS、HAProxy 或 F5)对外提供统一的接入地址。TiDB采用go语言编写。
Placement Driver (简称 PD) 是整个集群的管理模块,其主要工作有三个: 一是存储集群的元信息(某个 Key 存储在哪个 TiKV 节点);二是对 TiKV 集群进行调度和负载均衡(如数据的迁移、Raft group leader 的迁移等);三是分配全局唯一且递增的事务 ID。PD通过内嵌etcd来支持数据分布和容错。PD 采用go语言编写。
PD 是一个集群,需要部署奇数个节点,一般线上推荐至少部署 3 个节点。PD在选举的过程中无法对外提供服务,这个时间大约是3秒。
TiKV Server 负责存储数据,从外部看 TiKV 是一个分布式的提供事务的 Key-Value 存储引擎,基于Google Spanner, HBase 设计,但脱离了底层较为复杂的HDFS。通过RocksDB将key-value值存在本地地盘,使用 Raft 协议做复制,保持数据的一致性和容灾。存储数据的基本单位是 Region,每个 Region 负责存储一个 Key Range (从 StartKey 到 EndKey 的左闭右开区间)的数据,每个 TiKV 节点会负责多个 Region 。TiKV 使用 Raft 协议做复制,保持数据的一致性和容灾。副本以 Region 为单位进行管理,不同节点上的多个 Region 构成一个 Raft Group,互为副本。数据在多个 TiKV 之间的负载均衡由 PD 调度,这里也是以 Region 为单位进行调度。TiKV采用Rust语言编写。
TiSpark 作为 TiDB 中解决用户复杂 OLAP 需求的主要组件,将 Spark SQL 直接运行在 TiDB 存储层上,同时融合 TiKV 分布式集群的优势,并融入大数据社区生态。至此,TiDB 可以通过一套系统,同时支持 OLTP 与 OLAP,免除用户数据同步的烦恼。
TiDB Operator 提供在主流云基础设施(Kubernetes)上部署管理 TiDB 集群的能力。它结合云原生社区的容器编排最佳实践与 TiDB 的专业运维知识,集成一键部署、多集群混部、自动运维、故障自愈等能力,极大地降低了用户使用和管理 TiDB 的门槛与成本。
Connectors组件,是MySQL向外提供的交互组件,如java,.net,php等语言可以通过该组件来操作SQL语句,实现与SQL的交互。
提供对MySQL的集成管理,如备份(Backup),恢复(Recovery),安全管理(Security)等
负责监听对客户端向MySQL Server端的各种请求,接收请求,转发请求到目标模块。每个成功连接MySQL Server的客户请求都会被
创建或分配一个线程,该线程负责客户端与MySQL Server端的通信,接收客户端发送的命令,传递服务端的结果信息等。
接收用户SQL命令,如DML,DDL和存储过程等,并将最终结果返回给用户。
首先分析SQL命令语法的合法性,并尝试将SQL命令分解成数据结构,若分解失败,则提示SQL语句不合理。
对SQL命令按照标准流程进行优化分析。
缓存和缓冲组件
什么是MySQL存储引擎?
MySQL属于关系型数据库,而关系型数据库的存储是以表的形式进行的,对于表的创建,数据的存储,检索,更新等都是由MySQL
存储引擎完成的,这也是MySQL存储引擎在MySQL中扮演的重要角色。
研究过SQL Server和Oracle的读者可能很清楚,这两种数据库的存储引擎只有一个,而MySQL的存储引擎种类比较多,如MyISAM存储
引擎,InnoDB存储引擎和Memory存储引擎。
MySQL之所以有多种存储引擎,是因为MySQL的开源性决定的。MySQL存储引擎,从种类上来说,大致可归结为官方存储引擎和第三
方存储引起。MySQL的开源性,允许第三方基于MySQL骨架,开发适合自己业务需求的存储引擎。
MySQL存储引擎作用?
MySQL存储引擎在MySQL中扮演重要角色,其作比较重要作用,大致归结为如下两方面:
作用一:管理表创建,数据检索,索引创建等
作用二:满足自定义存储引擎开发。
MySQL引擎种类?
不同种类的存储引擎,在存储表时的存储引擎表机制也有所不同,从MySQL存储引擎种类上来说,可以分为官方存储引擎和第三方存储引擎。
当前,也存在多种MySQL存储引擎,如MyISAM存储引擎,InnoDB存储引擎,NDB存储引擎,Archive存储引擎,Federated存储引擎,Memory
存储引擎,Merge存储引擎,Parter存储引擎,Community存储引擎,Custom存储引擎和其他存储引擎。
其中,比较常用的存储引擎包括InnoDB存储引擎,MyISAM存储引擎和Momery存储引擎。
几种典型MySQL存储引擎比较?
实际存储MySQL 数据库文件和一些日志文件等的系统,如Linux,Unix,Windows等。
TiDB 架构是 SQL 层和 KV 存储层分离,相当于 InnoDB 插件存储引擎与 MySQL 的关系。从下图可以看出整个系统是高度分层的,最底层选用了当前比较流行的存储引擎 RocksDB,RockDB 性能很好但是是单机的,为了保证高可用所以写多份,上层使用 Raft 协议来保证单机失效后数据不丢失不出错。保证有了比较安全的 KV 存储的基础上再去构建多版本,再去构建分布式事务,这样就构成了存储层 TiKV。有了 TiKV,TiDB 层只需要实现 SQL 层,再加上 MySQL 协议的支持,应用程序就能像访问 MySQL 那样去访问 TiDB 了。
在上面的"MySql整体架构"基本上都讲过了,再看一下别人写的吧!
时光传送门