前言
紧接着上一篇文章TiDB经验分享01 的内容接着往下更新,在上一篇文章中主要论述了TiDB的基础架构和其包含的两种存储引擎 TiKV TiFlash,这一篇我们主要论述TiDB在生产使用的特点 和MySQL的对比 以及TiDB在生产应用中正确的使用方式。
TiDB的特点
首先南国论述下TiDB 这款数据库的一些优点,具体论述如下:
● 与 MySQL 的兼容型比较好,绝大多数 SQL 语句和相关工具都可以直接使用。
○ 支持慢查询日志
○ 支持和 MySQL 之间的主从同步等功能。
○ TiDB 的默认隔离级别为可重复读,支持修改。
○ 支持调整 sql mode、autocommit、time zone 等参数。
○ 存储(TiKV)和计算(TiDB)均可以横向拓展。
○ 数据默认保存三个副本(可调整),可以确保绝大多数情况下的数据安全。
○ 各个组件都具备高可用性,部分实例宕机不影响整体的可用性。
○ 底层支持tikv行存和tiflash列存两种存储形式。
○ 支持历史数据查询。set @@tidb_snapshot="2016-10-08 16:45:26"
之后就可以查询这个时间点的历史数据。
■ 历史数据默认只保存 10 分钟,可以根据实际需求修改。
■ 历史数据保留的时间会影响备份,如果备份时间比较长,需要适当的调高这个保留时间。
简单总结下,TiDB这款数据库和MySQL的兼容性很好,能够支持MySQL的大部分语法包括DDL DML等,同时它自身计算和存储相分离的特性使得它进行水平扩展非常方便(个人认为计算和存储相分离这点 是未来大势 例如Kakfa对比pulsar)
接下来讲述一下tidb使用过程中的缺点:
● 不支持非 UTF-8 的字符集(例如 GBK)。
○ 暂时只能依靠 dump
来做严格一致性的备份。
■ dump
的备份和备份恢复都非常慢。
■ 可以考虑使用硬盘 snapshot 方式来做备份(基于 LVM),备份速度可以做到较快,但是数据恢复速度仍然很慢。
■ 3.1 GA 会提供官方的备份方案。
○ TiDB 默认不会对数据进行 hash 和拆分,有可能会出现热点效应,导致少量 TiKV 节点的负载高于其他节点。
■ TiDB 提供了工具,可以手工重新调整数据分布。
■ 也可以使用 TiDB 的隐藏主键,增加 SHARD_ROW_ID_BITS
的表定义来做到数据拆分。
TiDB与mysql的兼容性
TiDB 高度兼容 MySQL 5.7 协议、MySQL 5.7 常用的功能及语法。MySQL 5.7 生态中的系统工具 (PHPMyAdmin、Navicat、MySQL Workbench、mysqldump、Mydumper/Myloader)、客户端等均适用于 TiDB。
但 TiDB 尚未支持一些 MySQL 功能,官方解释 可能的原因如下:
- 有更好的解决方案,例如 JSON 取代 XML 函数。
- 目前对这些功能的需求度不高,例如存储流程和函数。
- 一些功能在分布式系统上的实现难度较大。
除此以外,TiDB 不支持 MySQL 复制协议,但提供了专用工具用于与 MySQL 复制数据
- 从 MySQL 复制:TiDB Data Migration (DM) 是将 MySQL/MariaDB 数据迁移到 TiDB 的工具,可用于增量数据的复制。
- 向 MySQL 复制:TiCDC 是一款通过拉取 TiKV 变更日志实现的 TiDB 增量数据同步工具,可通过 MySQL sink 将 TiDB 增量数据复制到 MySQL。
这里着重讲解几个实际应用会遇到的差异说明:
自增ID
TiDB 的自增列既能保证唯一,也能保证在单个 TiDB server 中自增,但不保证多个 TiDB server 中自增,不保证自动分配的值的连续性。不建议将缺省值和自定义值混用,若混用可能会收到 Duplicated Error 的错误信息。TiDB 可通过 tidb_allow_remove_auto_inc 系统变量开启或者关闭允许移除列的 AUTO_INCREMENT 属性。删除列属性的语法是:ALTER TABLE MODIFY 或 ALTER TABLE CHANGE。
TiDB 不支持添加列的 AUTO_INCREMENT 属性,移除该属性后不可恢复。
存储引擎
仅在语法上兼容创建表时指定存储引擎,实际上 TiDB 会将元信息统一描述为 InnoDB 存储引擎。TiDB 支持类似 MySQL 的存储引擎抽象,但需要在系统启动时通过–store 配置项来指定存储引擎。
也就是说tidb建表的时候会统一描述为InnoDB引擎,即使你创建表的时候语句显示声明创建MyISAM的表,show create table时也会是InnoDB。如果需要在TiDB上创建MyISAM的表,可能需要联系DBA重启数据库。使用tidb 尽量避免大事物
a、单个事务包含的 SQL 语句不超过 5000 条(默认)
b、每个键值对不超过 6MB
c、键值对的总数不超过 300,000
d、键值对的总大小不超过 100MBTiDB 使用乐观事务模型,在执行 UPDATE、INSERT、DELETE 等语句时,只有在提交过程中才会检查写写冲突,而不是像 MySQL 一样使用行锁来避免写写冲突。类似的,诸如 GET_LOCK() 和 RELEASE_LOCK() 等函数以及 SELECT … FOR UPDATE 之类的语句在 TiDB 和 MySQL 中的执行方式并不相同。所以业务端在执行 SQL 语句后,需要注意检查 COMMIT 的返回值,即使执行时没有出错,COMMIT 的时候也可能会出错(低版本的tidb 可能会出现该问题)。
TiDB 和 MySQL 对比
● TiDB 集群中,存储(TiKV)节点和计算(TiDB Server)节点均可以横向拓展,拓展之后的集群读的性能上限远远高于 MySQL 单节点的性能。
● 单个TiDB Server 节点的性能不如 MySQL,且 TiDB Server 的数据聚合、计算能力不强,较复杂的查询比较多的时候可能很容易触及 TiDB Server 的瓶颈。
● 虽然集群读的性能上限很高,但是 TiDB 集群的数据写入能力偏弱,通过对 TiKV 进行调整后,可以在一定时间内(30min)保持较稳定的写入性能(约 25000 QPS),但是长时间的写入必定会出现剧烈的写入性能波动。
● TiDB OLAP 业务的性能很好,对复杂查询和计算有良好的支持。
● TiDB 集群满负载下,单次写入的耗时通常会达到 150ms 左右,OLTP 查询的时间一般在 5~50ms,这个时间不会因为横向扩展而降低。当出现过量的压力时,会出现性能降级的现象,相关延时大幅度增加。
● 负载较低时,耗时也会相应降低,会低于 100ms。
● TiDB 单个线程能承受的负载远低于 MySQL,因此需要较多的线程的并发(~600)才能充分利用服务器的性能,并使得整体性能超越 MySQL。
TiDB 的适用范围
TiDB 适合以下业务场景使用:
● 需要存储大量的数据,以 OLTP 为主的业务,OLAP 业务也可以酌情考虑。
● 拥有大量服务器,会创建大量数据库连接,同时负载较高,希望能够对计算或者存储做横向扩展的 OLTP 业务。
● 对写入性能要求不高,但是 OLTP 只读能力要求很高(> 200k)的业务
TiDB 不适合以下业务场景使用:
● 小数据量,单库即可承载,不需要横向扩展的业务
● 需要有大量数据写入,对写入性能与延时要求较高的业务
tidb的正确使用方式
首先谈谈 Schema 设计的一些比较好的经验。由于 TiDB 是一个分布式的数据库,可能在表结构设计的时候需要考虑的事情和传统的单机数据库不太一样,需要开发者能够带着「这个表的数据会分散在不同的机器上」这个前提,才能做更好的设计。
和 Spanner 一样,TiDB 中的一张表的行(Rows)是按照主键的字节序排序的(整数类型的主键我们会使用特定的编码使其字节序和按大小排序一致),即使在 CREATE TABLE 语句中不显式的创建主键,TiDB 也会分配一个隐式的。有四点需要记住:
- 按照字节序的顺序扫描的效率是比较高的;
- 连续的行大概率会存储在同一台机器的邻近位置,每次批量的读取和写入的效率会高;
- 索引是有序的(主键也是一种索引),一行的每一列的索引都会占用一个 KV Pair,比如,某个表除了主键有 3 个索引,那么在这个表中插入一行,对应在底层存储就是 4 个 KV Pairs 的写入:数据行以及 3 个索引行。
- 一行的数据都是存在一个 KV Pair 中,不会被切分,这点和类 BigTable 的列式存储很不一样。
表的数据在 TiDB 内部会被底层存储 TiKV 切分成很多 64M 的 Region(对应 Spanner 的 Splits 的概念),每个 Region 里面存储的都是连续的行,Region 是 TiDB 进行数据调度的单位,随着一个 Region 的数据量越来越大和时间的推移,Region 会分裂/合并,或者移动到集群中不同的物理机上,使得整个集群能够水平扩展。
● 尽可能批量写入,但是一次写入总大小不要超过 Region 的分裂阈值(64M),另外 TiDB 也对单个事务有大小的限制(单次插入少于30000行)。
● 存储超宽表是比较不合适的,特别是一行的列非常多,同时不是太稀疏,一个经验是最好单行的总数据大小不要超过 64K,越小越好。大的数据最好拆到多张表中。
● 对于高并发且访问频繁的数据,尽可能一次访问只命中一个 Region,这个也很好理解,比如一个模糊查询或者一个没有索引的表扫描操作,可能会发生在多个物理节点上,一来会有更大的网络开销,二来访问的 Region 越多,遇到 stale region 然后重试的概率也越大(可以理解为 TiDB 会经常做 Region 的移动,客户端的路由信息可能更新不那么及时),这些可能会影响 .99 延迟;另一方面,小事务(在一个 Region 的范围内)的写入的延迟会更低,TiDB 针对同一个 Region 内的跨行事务是有优化的。另外 TiDB 对通过主键精准的点查询(结果集只有一条)效率更高。
总之正确使用 TiDB 的姿势,或者说 TiDB 的典型的应用场景是:
● 大数据量下,MySQL 复杂查询很慢;
● 大数据量下,数据增长很快,接近单机处理的极限,不想分库分表或者使用数据库中间件等对业务侵入性较大,架构反过来约束业务的 Sharding 方案;
● 大数据量下,有高并发实时写入、实时查询、实时统计分析的需求;
● 有分布式事务、多数据中心的数据 100% 强一致性、auto-failover 的高可用的需求。
预期数据行数不会超过 5000w 的场景下通常用不到 TiDB,TiDB 是为大规模的数据场景设计的。如果还想记住一句话,那就是单机 MySQL 能满足的场景也用不到 TiDB。
其他知识点
自增 ID 的差异
● TiDB 的自增 ID (Auto Increment ID) 只保证自增且唯一,并不保证连续分配。TiDB 目前采用批量分配的方式,所以如果在多台 TiDB 上同时插入数据,分配的自增 ID 会不连续。
● 如果有大量连接同时向 TiDB 写入数据,可能导致分配的自增 ID 很快就变得很大。
● 如果表结构中包含了自增 ID,建议不要混用缺省值和自定义值。
Tidb 利用GC快照读恢复数据
当遇到数据被误更新或误删除的情况,很多人想到的是 Oracle 的闪回或者 MySQL 的基于 binlog 实现的闪回工具。作为 NewSQL 的佼佼者,TiDB 可以直接通过标准 SQL 读取历史数据,无需特殊的 client 或者 driver。即使在更新/删除数据后,表结构发生了变化,TiDB 依旧能够按照旧的表结构定义将数据读取出来。
TiDB 事务的实现采用了 MVCC(多版本并发控制)机制,当更新/删除数据时,不会做真正的数据删除,只会添加一个新版本数据,并以时间戳来区分版本。当然历史数据不会永久保留,超过一定时间的历史数据将会被彻底删除,以减小空间占用,同时避免因历史版本过多引起的性能开销。
TiDB 使用周期性运行的 GC(Garbage Collection,垃圾回收)来进行清理,默认情况下每 10 分钟一次。每次 GC 时,TiDB 会计算一个称为 safe point 的时间戳(默认为上次运行 GC 的时间减去 10 分钟),接下来 TiDB 会在保证在 safe point 之后的快照都能够读取到正确数据的前提下,删除更早的过期数据。
TiDB 的 GC 相关的配置存储于 mysql.tidb 系统表中,可以通过 SQL 语句对这些参数进行查询和更改:
重要参数说明:
tikv_gc_life_time:历史版本保留时间
tikv_gc_run_interval:触发历史版本gc间隔时间
tikv_gc_safe_point: 最早数据版本截止时间
tikv_gc_concurrency:gc并发度
备注:在企业应用中,业务开发账户可能没有权限查看上图中的信息,如果遇到需要用到GC快照恢复历史数据时 可以联系dba知晓tidb设置的历史版本保留时间。
总结:利用GC快照读取数据最大的功能是再数据错误被删除或者覆盖时,通过快照可以查看过去某个时间的数据,然后利用过去时间的正确数据来手动替换掉现在的错误数据。
例如 查找快照数据
SET @@tidb_snapshot="2021-04-26 19:00:00";
select report_date,sum(revenue) from report_day_hour where adn_id=1 and report_date>=20210101 and report_date<=20210106 group by report_date order by report_date;
当然,当你设置的快照时间超过了设置时间,查询会报错:
TIDB事务过大transction too large解决方法
由于TIDB二阶段提交的特性,对事务大小有做限制:
- 单个事务包含的 SQL 语句不超过 5000 条(默认)
- 单条 KV entry 不超过 6MB
- KV entry 的总条数不超过 30w
- KV entry 的总大小不超过 100MB
因此批量导入或者批量删除数据时 在tidb里经常会遇到transction too large的情况。批量插入数据可以使用 set tidb_batch_insert=1;
完成后将参数关闭 set tidb_batch_insert=0
该操作在生产环境中慎用,因为这样insert 会把大事务分批执行,如果中途报错,已插入的数据不会回滚,丢失事务的原子性。
批量删除数据可以使用 set tidb_batch_delete=1;
完成后将参数关闭 set tidb_batch_delete=0
批量删除被切分为小事务,也可以使用 limit 加循环的方式进行操作。
update可以通过limit加循环的方式实现。
ADD COLUMN
ALTER TABLE.. ADD COLUMN
语句用于在已有表中添加列。在 TiDB 中,
ADD COLUMN 为在线操作,不会阻塞表中的数据读写。