导读
本文来自 TiDB 社区合肥站走进蔚来汽车——吴记老师的演讲《TiDB 在新能源车企的实践:MySQL 到 TiDB 的迁移思考》。
这次分享深入探讨了新能源车企蔚来汽车从 MySQL 迁移到 TiDB 的过程与实践,包括迁移过程中的挑战和动机,以及面对单表数据量增长至 20 亿条时的应对策略;此外,也将分享其使用 TiDB 过程中常见的问题与解决方法,帮助大家更有效地应用 TiDB 解决企业数据库管理中的挑战。
关于蔚来
蔚来是一家全球化的智能电动汽车公司,致力于通过提供高性能的智能电动汽车与极致用户体验。2023 年第三季度中国汽车市场销量 566.8 万辆,同比增长 2.4%,其中新能源车型销量合计接近 200 万辆,同比增长 36%。其中,蔚来在中国 30 万元以上的纯电汽车市场中位列第一,市场份额占比 45%。
业务挑战
随着业务的快速扩张,蔚来公司内部某些业务的数据量急剧增加,部分业务的日增数据量达到千万级别。在 MySQL 数据库中,一些表的记录数已超过 20 亿条。在多种业务场景中,对这些大型表进行联接查询导致严重的性能瓶颈,查询效率低下,甚至经常超时。由于查询需求的多样性,传统基于 hash 的分表策略已无法满足业务需求。
我们目前面临的数据库挑战主要包括:
**1. 性能问题:**在执行包含 20 亿记录的大表与不同规模的其他表(百万、几十万、几万)的联接查询时,性能显著下降,特别是对于聚合函数如 count
的查询几乎不可行。
**2. 时间维度跨度大:**大多查询场景需要结合时间维度进行时间范围查询,通常要查询中过滤最近半年的数据,但也有可能需要查询历史数据。
**3. 表结构复杂性:**大型表初始包含 20 多亿条记录,拥有 30 多个字段,其中约 10 个字段需要与其他三个表进行联接查询。
**4. 写入与同步延迟:**部分数据库表的单表写入数据量巨大,导致主从复制(master-slave replication)出现延迟,影响多个业务流程。
**5. DDL 执行缓慢:**在 MySQL 中,由于单表数据量过大,执行数据定义语言(DDL)操作变得非常缓慢,有时需要数小时才能完成。
为了解决这些问题,可能需要考虑以下策略:
- **优化查询:**重写查询逻辑,减少不必要的联接和数据扫描。
- **索引优化:**为常用于联接和查询的字段创建索引,提高查询效率。
- **分区表:**根据业务逻辑对表进行分区,以提高查询和维护的性能。
- **读写分离:**通过读写分离来减轻主数据库的压力,提高查询响应速度。
- **分布式数据库:**考虑使用分布式数据库解决方案,以支持水平扩展和负载均衡。
- **异步处理:**对于不需要即时返回结果的查询,采用异步处理方式。
为什么选择 TiDB?
通过调研,蔚来数据应用团队将目光放到了分布式数据库上,TiDB 作为一款广泛使用的开源分布式 HTAP 数据库,已纳入团队的调研和应用范围。
在调研中,蔚来数据应用团队认为 TiDB 作为一款开源分布式关系型数据库,在线事务处理,在线分析处理融合型分布式数据库产品,具备水平弹性扩容或者缩容金融级高可用、实时 HTAP、云原生的分布式数据库、兼容 MySQL 5.7 /MySQL 8.0 协议和 MySQL 生态等重要特性。目标是为用户提供一站式 OLTP (Online Transactional Processing)、OLAP (Online Analytical Processing)、HTAP 解决方案。TiDB 适合高可用、强一致要求较高、数据规模较大等各种应用场景。
TiDB 的多项优势特性有效满足了蔚来数据应用团队在处理大规模数据和高并发事务时的需求:
**1. 分布式架构:**TiDB 采用分布式关系型数据库架构,有效突破了单机处理能力的局限,提升了整体性能,扩展性。
**2. 高可用性:**TiDB 通过使用 Raft 一致性算法,数据在各 TiKV 节点间复制为多副本,以确保某个节点宕机时数据的安全性,同时具备同城双中心、两地三中心的金融级高可用方案。
**3. 水平弹性扩展:**TiDB 不仅支持传统关系型数据库的事务和分析功能,还具备非关系型数据库的水平扩展能力和灵活性,提供了高性能的数据存储解决方案。
**4. 分布式强一致性事务处理:**TiDB 支持 ACID(原子性、一致性、隔离性、持久性)事务,确保在分布式环境下的数据一致性和完整性。
**5. MySQL 协议高度兼容性:**TiDB 与 MySQL 协议高度兼容,支持广泛的 MySQL SQL 语法以及 MySQL 生态系统工具,降低了从 MySQL 迁移到 TiDB 的学习成本和技术障碍,实现了平滑过渡。
**6. 灵活的分区功能:**TiDB 提供了灵活的分区机制,支持 hash、range、list、key 等分区,简化了数据管理和维护工作,使得业务逻辑与数据分片解耦,提高了查询效率。
7. 强大的数据同步工具:
a. DM 可以方便的实现数据从 MySQL(全量+增量)同步到 TiDB
b. TiCDC 工具支持基于 Binlog 的数据同步,允许 TiDB 与 MySQL 或者 TIDB 之间实现主从复制,确保数据的实时同步和一致性。
**8. 丰富的生态系统:**TiDB 拥有一个成熟的生态系统,包括 TiFlash 提供的列式存储引擎,优化了分析型查询的性能;TiSpark 允许 TiDB 作为存储层,结合 Spark 的强大计算能力,提供了灵活的大数据处理能力。
通过这些特性,TiDB 不仅为蔚来提供了一个高性能、高可用的数据库解决方案,还通过其强大的生态系统,支持蔚来在数据管理和分析方面的需求,推动了业务的持续创新和发展。
架构对比
蔚来数据应用团队从架构、存储层面对比了 TiDB 与 MySQL 的区别与优势:
TiDB 架构详细描述
TiDB Server 层:
- **SQL 解析与优化:**TiDB Server 负责接收客户端的 SQL 请求,进行语法解析和逻辑优化,生成执行计划。这一步骤是查询优化的关键,TiDB Server 会利用其优化器来决定最有效的查询执行路径。
- **分布式协调器 PD(Placement Driver):**PD 是 TiDB 的元数据管理组件,负责存储集群的元信息,包括数据分布和节点状态。它与 TiDB Server 交互,协调数据的分布和负载均衡。
- **分布式存储 TiKV:**TiKV 是一个分布式的键值存储系统,负责存储实际的数据。TiDB Server 通过 PD 与 TiKV 进行交互,获取或写入数据。
- **执行器:**在获取到数据后,TiDB Server 的执行器负责进行数据的进一步处理,包括合并、排序、分页和聚合等操作。
特点:
-
**水平扩展:**TiDB Server 可以轻松地通过增加节点来扩展系统的处理能力。
-
**高可用性:**TiDB Server 设计为无状态,可以快速故障转移,保证服务的连续性。
-
**强一致性:**通过分布式事务和 MVCC 机制,TiDB 保证了事务的 ACID 属性。
MySQL 架构详细描述
传统单体架构:
- **集中式处理:**MySQL 的所有数据库操作,包括 SQL 解析、查询优化、数据存储和检索,都在同一服务器上完成。
- **单一数据存储:**数据存储在本地磁盘或连接的存储系统中,没有分布式存储的概念。
- **垂直扩展依赖:**由于是单体架构,MySQL 通常通过增加单个服务器的硬件能力(如 CPU、内存、存储)来提升性能,这称为垂直扩展。
特点:
- **简化管理:**由于所有组件都在一个服务器上,管理和维护相对简单。
- **扩展性限制:**垂直扩展有其物理限制,当达到硬件极限时,性能提升会遇到瓶颈。
- **事务和并发处理:**MySQL 通过行锁和表锁等机制来处理并发和事务,但在高并发场景下可能会遇到性能瓶颈。
架构对比总结
- **扩展性:**TiDB 的分布式架构允许其水平扩展,而 MySQL 主要依赖垂直扩展。
- **容错能力:**TiDB 通过多节点和副本机制提供高可用性,MySQL 则依赖于主从复制和故障转移机制。
- **性能:**TiDB 通过分布式计算和存储优化了大规模数据集的性能,MySQL 在大规模数据集下可能会遇到性能瓶颈。
- **复杂性与灵活性:**TiDB 的架构较为复杂,但提供了更高的灵活性和扩展性;MySQL 架构简单,但在处理大规模和高并发场景时可能需要额外的优化措施。
<center>MySQL 架构图</center>
<center>TiDB 架构图</center>
存储层
MySQL 存储架构
InnoDB 存储引擎:
-
MySQL 的默认存储引擎是 InnoDB,它是一个健壮的事务型存储引擎,支持 ACID 事务。
-
所有数据都存储在表空间中,表空间可以包含多个数据文件和日志文件。
-
表数据以 B+树的索引结构存储,这为快速的数据访问提供了基础。
B+树索引结构:
-
主键索引和非主键索引都是 B+树结构,其中非主键索引的叶子节点存储主键值,用于快速定位到具体的数据行。
-
B+树的每个节点可以存储更多的键值,这意味着相比 B 树,B+树的高度更低,查询效率更高。
事务和 MVCC:
-
InnoDB 通过行级锁定和 MVCC 机制来支持高并发的读写操作。
-
通过 Undo 日志来实现 MVCC,允许在不锁定资源的情况下读取历史数据版本。
TiDB 存储层
TiKV 分布式键值存储:
-
TiKV 是 TiDB 的分布式存储层,它使用 RocksDB 作为其本地存储引擎,优化了写入性能和磁盘空间使用。
-
TiKV 将数据分散存储在多个节点上,通过 Raft 协议保证数据的强一致性和高可用性。
MVCC 版本控制:
- TiKV 使用 MVCC 机制来处理并发控制和历史数据版本,每个事务都会获取一个全局唯一的时间戳(TS)作为版本号。
- 通过这种方式,TiKV 可以支持同一时间点的多个事务读取到一致的数据快照。
数据存储格式:
- 主键数据存储格式为tablePrefix{tableID}_recordPrefixSep{Col1},其中 Value 包含了行数据的所有列值。
- 唯一索引的存储格式为tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue,Value为对应的行 ID。
- 非唯一索引的存储格式与唯一索引类似,但每个索引值后附加行 ID,Value 可能为 null。
特点:
- TiKV 的存储层设计为易于扩展,可以水平扩展以适应不断增长的数据量。
- 通过 Raft 协议,TiKV 能够在多个副本之间同步数据,提高了数据的可用性和容错能力。
存储层对比总结
- **扩展性:**TiDB 的 TiKV 存储层设计为分布式,易于水平扩展,而 MySQL 的 InnoDB 存储引擎通常需要垂直扩展。
- **并发控制:**TiDB 使用 MVCC 和 TSO(Timestamp Ordering)来实现并发控制,而 MySQL 使用行级锁定和 MVCC。
- **数据一致性:**TiKV 通过 Raft 协议保证跨多个节点的数据一致性,InnoDB 则依赖于单个服务器的事务日志和恢复机制。
- **存储效率:**TiKV 的 RocksDB 存储引擎优化了写入性能和压缩,而 InnoDB 的 B+树结构优化了读取性能。
MySQL 存储架构
TiDB 存储层架构
索引实现
MySQL 索引实现
B+树结构:
- MySQL 的索引基于 B+树结构,这是一种自平衡树,优化了读写性能和空间使用。
- B+树的所有数据都存储在叶子节点,内部节点仅存储键值和指向子节点的指针,这减少了查找过程中的磁盘 I/O 操作。
主键索引:
- 主键索引是聚簇索引,非主键索引是二级索引。聚簇索引的叶子节点直接包含行数据,而非主键索引的叶子节点包含主键值,用于快速跳转到聚簇索引。
非主键索引:
- 非主键索引的叶子节点不直接存储行数据,而是存储对应的主键值。查询时,需要通过主键值回表查询,访问聚簇索引以获取完整的行数据。
特点:
- B+树结构减少了查询过程中的 I/O 操作次数,提高了数据访问速度。
- 聚簇索引和非聚簇索引的设计,优化了数据的物理存储,减少了冗余和空间使用。
TiDB 索引实现
KV 存储模型:
- TiDB 的索引基于键值(Key-Value)存储模型实现。这种模型非常适合分布式环境,因为它允许数据的水平分割和分布式存储。
主键索引:
- 主键索引使用行的主键值作为键,行数据的序列化形式作为值。例如,如果 Col1 是主键,则键可能表示为tablePrefix{tableID}_recordPrefixSep{Col1}。
- 这种映射允许 TiDB 通过主键值直接访问对应的行数据,提供了高效的数据检索。
唯一索引:
- 唯一索引使用索引列的值作为键,行的主键值作为值。例如,键可能表示为tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue,值是对应的 RowID。
- 这种设计确保了索引的唯一性,并且可以通过索引值快速定位到具体的数据行。
非唯一索引:
- 非唯一索引与唯一索引类似,但允许同一个键对应多个值。在这种情况下,键仍然是索引列的值,但值是包含 RowID 的列表。
- 这允许 TiDB 处理具有相同索引值的多行数据。
特点:
- TiDB 的索引实现简化了分布式环境下的数据访问,通过键值对直接映射,提高了查询效率。
- 由于 TiDB 的存储层 TiKV 使用 RocksDB,索引数据也被优化存储,以减少磁盘空间的使用。
索引实现对比总结
- **数据访问方式:**TiDB 通过键值对直接映射数据,而 MySQL 通过 B+树结构进行索引。
- **分布式适应性:**TiDB 的索引实现更适合分布式环境,易于水平扩展。MySQL 的 B+树索引则优化了单个服务器上的数据访问。
- **查询效率:**TiDB 的索引实现允许快速的数据检索,特别是在分布式查询中。MySQL 的 B+树索引通过减少 I/O 操作提高了查询效率。
- **存储优化:**TiDB 的 RocksDB 存储引擎优化了索引数据的存储,而 MySQL 的 B+树结构减少了索引的存储空间需求。
<center>MySQL 索引 b+tree</center>
<center>TiDB 中 Rocksdb 分布式 leveldb lsm</center>
事务处理和 MVCC
MySQL 事务处理和 MVCC
InnoDB 事务模型:
- MySQL 的 InnoDB 存储引擎支持 ACID(原子性、一致性、隔离性、持久性)事务。
- InnoDB 使用行级锁定机制来处理并发写入,确保事务的隔离性。
MVCC 实现:
- InnoDB 通过 Undo Log 来实现 MVCC,允许在不锁定资源的情况下读取历史数据版本。
- Undo Log 记录了数据在事务开始前的状态,这样即使在其他事务修改了数据之后,当前事务仍然可以读取到事务开始前的数据状态。
- InnoDB 的 MVCC 主要通过 Read View 来实现,Read View 是一个快照,包含了在事务开始时所有已提交的数据的可见性信息。
锁机制:
- InnoDB 主要使用悲观锁,通过行锁和表锁来处理数据的并发访问,防止数据的不一致性。
- 行锁在 SELECT ... FOR UPDATE 或 INSERT/UPDATE/DELETE 操作时自动加锁,以保证事务的原子性和隔离性。
- 表锁在某些特定的操作,如全表扫描或某些类型的索引操作中使用。
TiDB 事务处理和 MVCC
TiDB 事务模型:
- TiDB 支持两种类型的锁:乐观锁和悲观锁,以适应不同的业务场景。
- 乐观锁:适用于写冲突较少的环境,通过检测在事务开始后数据是否被其他事务修改来避免锁的争用。如果检测到冲突,事务会进行重试。
- **悲观锁:**适用于高冲突环境,通过在事务开始时就锁定涉及的数据行,防止其他事务修改这些数据。
MVCC 实现:
-
TiDB 采用 MVCC 机制来提供在不锁定资源的情况下读取历史数据版本的能力,从而提高并发性能。
-
MVCC 通过为每个事务分配一个全局唯一的时间戳(TS),并使用这个时间戳来确定数据的可见性。
-
在 TiDB 中,每个数据行都保存了多个版本,每个版本都有一个开始和结束的时间戳。查询操作会根据当前事务的时间戳来确定应该读取哪个版本的数据。
事务与 MVCC 对比总结
- **锁机制:**TiDB 支持乐观锁和悲观锁,提供了更灵活的锁策略,而 MySQL 主要使用悲观锁。
- **MVCC 实现:**TiDB 使用时间戳和版本控制来实现 MVCC,而 InnoDB 使用 Undo Log 和 Read View。
- **并发性能:**TiDB 的 MVCC 机制通过减少锁的争用来提高并发性能,特别是在高并发读写的场景下。InnoDB 的 MVCC 通过 Undo Log 减少锁的使用,但在高冲突环境下可能仍然会遇到锁争用。
- **历史数据访问:**TiDB 和 InnoDB 都允许在不锁定资源的情况下访问历史数据版本,提高了系统的并发读取能力。
通过这种详细的事务和 MVCC 机制对比,我们可以更深入地理解 TiDB 和 MySQL 在事务处理和并发控制方面的差异,以及它们如何适应不同的业务场景和性能需求。
<center>TiDB 同时支持了乐观锁和悲观锁模式</center>
<center>MySQL 通过 undolog 实现( Redo Log、Undo Log、Bin Log ( https://zhuanlan.zhihu.com/p/528575954 ))</center>
<center>TiDB MVCC 的实现</center>
TiDB 其它关键特性
SQL 生命周期
- **TiDB SQL 执行:**分布式环境中,SQL 执行涉及多个组件和步骤,包括索引使用、存储引擎选择等。
- **性能分析工具:**使用EXPLAIN和EXPLAIN ANALYZE分析 SQL 执行计划和实际执行情况。
由于是分布式数据库,在 TiDB 中 SQL 的执行和 MySQL 有很大区别,如索引实现、存储机制等。
- 在 TiDB 中查询一条 SQL 是如何执行的,使用的引擎,索引等信息操作如下:
explain yoursql;
explain analyze yoursql; //真实执行
- SQL 语法的兼容性
TiDB 语法兼容了 MySQL 8.0 的绝大部分语法,目前仅发现新版的 MySQL 一些特殊语法不支持,比如 default CURRENT_DATE
;同时新增了一些语法,比如主键索引 auto_random
的类型,基本上业务上一般已经用的 MySQL 的 SQL 基本都支持。
分区的使用
- **TiDB 分区:**支持多种分区类型,如 Range、List 和 Hash 分区,简化数据管理并提高查询效率。
- **分区表分析:**自动分析分区数据分布,优化查询计划。
TiDB 当前支持的类型包括 Range 分区、Range COLUMNS 分区、Range INTERVAL 分区、List 分区、List COLUMNS 分区 和 Hash 分区。
- 查看分区的数据
/*查看分区的数据分布*/
SHOW STATS_META where table_name = "table_demo";
/*从分区直接查询数据*/
CREATE TABLE table_demo (
`id` bigint(20) primary key auto_random,
start_time timestamp(3)
) PARTITION BY RANGE (FLOOR(UNIX_TIMESTAMP(`start_time`)))
SELECT * FROM table_demo PARTITION (p1) where xxx;
/* 新增分区 */
ALTER TABLE table_demo ADD PARTITION(PATITION p2 VALUES LESS THAN ( FLOOR(UNIX_TIMESTAMP(`start_time`))
/* 删除分区 */
ALTER TABLE table_demo drop partition p1
- 分区表的说明:
TiDB 每个分区都是单独的一张表,会对每个分区进行统计,如查询的时候进行逻辑优化,推算数据在哪些分区里面;TiFlash 也支持分区。
- 分区表的分析
TiDB 分区表分析有利于统计索引(分区)的分布情况
show variables like '%tidb_auto_analyze_end_time%';set global tidb_auto_analyze_end_time = "06:00 +0000"; analysis table table_name; //分析表,有利于执行计划
列式存储 TiFlash
TiDB 提供了列式存储引擎 TiFlash,它是 TiKV 的列存扩展,在提供了良好的隔离性的同时,也兼顾了强一致性。
只需在 TiDB 做出一些设置,数据就可以从 TiKV->TiFlash 同步过去。
//增加 tiflash 副本
ALTER TABLE table_name SET TIFLASH REPLICA count;
//查看数据同步进度
SELECT * FROM information_schema.tiflash_replica WHERE TABLE_SCHEMA = '<db_name>' and TABLE_NAME = '<table_name>';
TiDB 可以根据表分析的情况综合索引信息和数据量,自动选择使用 TiFlash 或者 TiKV,也可以在 SQL 内指定使用的存储引擎,且支持多表。
select /*+ read_from_storage(tiflash[table_name]) */ ... from table_name;
迁移方案
原始的 MySQL 数据库中都是用户业务数据,蔚来数据团队为了稳妥采取了先将数据写入到 MySQL,再通过 DM 将数据同步到 TiDB 中,内部各大系统直接使用 TiDB 进行查询,大幅优化了查询性能。
同步之后,蔚来数据团队用 20 亿单表业务数据作验证,分别在 MySQL 和 TiDB 运行,进行性能对比:
经过优化,TiDB 的 Join 查询业务上 80% 查询达到 2s 内,20% 查询在 5s 内。Count 结果很快,用户体验非常好。
总结
目前,TiDB 已被蔚来多个业务部门广泛采用。业务方反馈 TiDB 真正解决了业务中的很多问题,并且在使用中表现非常平稳,稳定性超乎预料,大大增强了使用国产分布式数据库的信心。
- 活动回顾及 PPT 下载:https://asktug.com/t/topic/1020557
- 演讲视频实录:https://www.bilibili.com/video/BV1LC4y1C7Yq