Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question]: 求教:为什么OceanBase建议使用业务字段做(联合)主键,不建议使用自增列做主键 ? #1955

Open
CodePlayer opened this issue May 8, 2024 · 12 comments

Comments

@CodePlayer
Copy link

Question

我看官方文档中存在如下描述:

ob-spec

如果有一个电商 SaaS,【订单表】有 商户ID( int ) 和 订单号( varchar(32) ) 存在唯一约束,这个时候我们也应该用 这两个字段 做联合主键吗?

众所周知,MySQL 采用的是 B+Tree 的结构来存储数据,一般建议 ID是自增的,这样可以避免随机插入,导致 MySQL 数据页频繁分裂,进而导致检索数据时 IO 开销增加。

然而,商户ID + 订单号 也很明显不是自增的,所以我才有此疑问。
对于这种场景,如果 OceanBase 仍然推荐使用它们联合作为主键,是因为 LSM-Tree 能够很好地处理 裂页 问题么 ?

此外,除了主键索引,我们一般还会额外在关键业务表上创建几个二级索引。
在 MySQL 中,因为每个二级索引的叶子节点都会存储相应行的主键值。所以,如果主键较大,那么每个二级索引的叶子节点所需的空间也会更大,因为它们需要存储更大的主键值。

如果主键是 bigint 的自增ID,在每个二级索引上存储,关联主键也就占用 8个字节 的空间。
如果主键是 int + varchar(32) 这种联合索引,是不是就意味着,关联主键也要占用至少 4+32 个字节的空间呢?
如果有多个二级索引(一般2~4个),是不是也要重复多占用不少空间 ?这样的话,存储空间 以及 维护这些二级索引的开销不是也增大了许多么 ?

@CodePlayer CodePlayer changed the title [Question]: 求教:建议使用业务字段做主键或做联合主键,不建议使用自增列做主键 ? [Question]: 求教:为什么OceanBase建议使用业务字段做主键或做联合主键,不建议使用自增列做主键 ? May 8, 2024
@CodePlayer CodePlayer changed the title [Question]: 求教:为什么OceanBase建议使用业务字段做主键或做联合主键,不建议使用自增列做主键 ? [Question]: 求教:为什么OceanBase建议使用业务字段做(联合)主键,不建议使用自增列做主键 ? May 8, 2024
@hnwyllmm
Copy link
Contributor

hnwyllmm commented May 8, 2024

不按顺序回答你的问题。
先说“如果主键是 int + varchar(32) 这种联合索引,是不是就意味着,关联主键也要占用至少 4+32 个字节的空间呢?”
--> 在OceanBase 中 int也是按照8位来存储的,所以这里应该就是 8+32=40字节,一条数据比bigint自增ID多了32个字节。按照1亿个数据量来算,就多了大概3G的存储。如果有3个索引,也不过10G的存储。先不说你肯定不在乎这10G的存储空间(毕竟业务量都上亿了),OceanBase在存储时还会压缩的,最终的数据,即使是多副本,也通常比MySQL占用的存储空间少了50%,具体可以看看官网上的一些案例描述,或许会有案例可以帮到你们。


再看“自增ID作为主键”的问题,在分布式数据库上这是一个老生常谈的话题,看看gpt的回答:

写入热点:自增主键意味着新插入的行总是拥有比之前高的主键值。这在分布式数据库中可能会造成主键值集中在某个范围内,从而导致所有新的插入操作都集中在特定的分区或节点上。这种情况创建了所谓的“写入热点”,可能会降低系统的性能和扩展性。

分布式事务冲突:在分布式系统中保持主键的唯一自增值需要跨多个节点协调,这可能会导致无法避免的分布式事务冲突和协调开销。同步自增值可能会导致延迟和额外的性能损耗。

扩展性限制:自增主键强制了数据的插入顺序,这可能导致数据在物理存储上的不均匀分布,对于需要水平扩展的分布式数据库来说,这是一个不利因素。扩展操作需要平衡和迁移数据,而自增主键可能会使这一过程更加复杂和低效。


网上也有很多自增主键的博客。大概意思都是自增主键在非分区数据库上都挺和谐,但是分区数据库上很困难。
对此,OceanBase还对自增列做了优化,参考自增 noorder 模式:https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000751258


在来看“对于这种场景,如果 OceanBase 仍然推荐使用它们联合作为主键,是因为 LSM-Tree 能够很好地处理 裂页 问题么 ?”
LSM-Tree不像B+树,没有列页的问题。新来的数据更新,不管是新增、更新还是删除,都是先在内存上操作的。在一定的平衡场景下,性能不会受到“数据太分散”相关的影响。而LSM的合并操作,都是在后台进行的。

@CodePlayer
Copy link
Author

CodePlayer commented May 8, 2024

首先,非常感谢大佬的热心回复~!

  1. 关于10G的空间,我其实还算是比较在乎的(哈哈~)。不过在性能不会因此而劣化,基于数据压缩的实现,存储占用还能更小的情况,也肯定就不会在意了。
  2. 综上所述,如果是 分区 表,就建议使用业务字段做主键;如果是非分区的单表或者小表,用 自增ID 似乎也是不错的选择?
  3. “OceanBase 的 int 也是用 8个字节 来存储”,那请问 tinyintsmallint 之类的呢,也是 8 个字节 吗 ?
  4. 有些业务,可能涉及到增量汇总统计的需求,比如 每5分钟增量汇总每个商户的订单总额,之前已经统计到了 ID=1000000 了,接下来再继续增量统计新增部分的,阶段性统计完毕后,再保存新的断点,下一次统计就从新的断点继续增量统计。这种需求场景下,如果使用 自增ID 似乎还是会更方便一些 ?

@hnwyllmm
Copy link
Contributor

hnwyllmm commented May 8, 2024

  1. 如果未来几年内都是小表,不需要考虑使用什么方式作为主键,性能和存储影响都不大
  2. 整数都是8字节,不过在最底层存储时会编码压缩,所以最终占用的存储空间比8字节少,内存中都是8字节。
  3. 看起来使用“订单时间”字段过滤更加合理。假设你有一个应用程序定时扫描数据出报表,而有一段时间这个程序卡住或者异常了,过了一个小时才恢复,那使用时间字段来分割五分钟没问题,而使用ID时无法分割的。

@CodePlayer
Copy link
Author

  1. 如果未来几年内都是小表,不需要考虑使用什么方式作为主键,性能和存储影响都不大
  2. 整数都是8字节,不过在最底层存储时会编码压缩,所以最终占用的存储空间比8字节少,内存中都是8字节。
  3. 看起来使用“订单时间”字段过滤更加合理。假设你有一个应用程序定时扫描数据出报表,而有一段时间这个程序卡住或者异常了,过了一个小时才恢复,那使用时间字段来分割五分钟没问题,而使用ID时无法分割的。

感谢大佬的热心回复~

用 ID 只是为了实现尽快统计全部增量,而不是为了实现按照时间段分组统计。
如果是时间段查询,还需要注意写入延迟的问题。

比如 事务1 先插入 '12:00:00' 的数据,事务2 后写入 '11:59:59' 的数据。如果增量查询恰好在两个事务之间执行,进行增量统计,保存的下一次查询断点就是 查询到的范围内的最大值,即 '12:00:00',这就漏掉了后面延迟写入的 事务2的 '11:59:59' 的数据。

这种情况,就要考虑延迟写入的最大时间延迟,比如 30s 或 1分钟。
如果最大延迟为 30s,则在 12:00:00 只能查询 '11:59:30' 之前的数据,否则可能会漏掉数据。

另外,想问一下大佬。OceanBase 社区版貌似支持的最大分区数是 8192,请问这个分区数限制只是一级分区,还是包含二级分区呢?如果按照 商户ID+按天的日期 来分区,就很容易超出该限制。

@hnwyllmm
Copy link
Contributor

按照 商户ID+按天的日期 来分区

你们的商户数量很少,并且每天的数据量比较大?

@CodePlayer
Copy link
Author

按照 商户ID+按天的日期 来分区

你们的商户数量很少,并且每天的数据量比较大?

是的,商户不会太多。
我想知道 8192 的分区数限制 ,是一级分区的数量限制吗,还是包含二级 ?
谢谢!

@hnwyllmm
Copy link
Contributor

有二级分区的时候,一级分区只是一个逻辑上的概念。
是二级分区的限制。

@CodePlayer
Copy link
Author

有二级分区的时候,一级分区只是一个逻辑上的概念。 是二级分区的限制。

感谢回复~

如果 8192 是二级分区的限制,那就比较麻烦了:

  1. 如果只按照 商户ID 分区的话,但部分商户的数据,单日就可能过亿;
  2. 如果按照 商户ID+按天 分区的话,分区数量有限制,完全不够用。

@hnwyllmm
Copy link
Contributor

你们之前用的什么数据库?

我看了下文档,并没有说限制是8192:
https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000751429

@CodePlayer
Copy link
Author

CodePlayer commented May 11, 2024

你们之前用的什么数据库?

我看了下文档,并没有说限制是8192: https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000751429

之前是 MySQL。
目前用的是 5.7.25-OceanBase_CE-v4.2.2.0 。我用上述文档中的命令试过,根本查不到这个参数 max_partition_num
执行 ALTER SYSTEM SET max_partition_num = 65536,也直接提示 5099 - System config unknown ( 用 sys 租户 root 账号执行也是如此 )。

您提供的文档连接,我之前看过,文档属于 4.2.3 (可惜目前还是 Beta )版本的,里面写的是:

该配置项从 V4.2.1 BP3 版本开始引入。

难道这个参数只有 4.2.1 才有,4.2.2 反而没有了 ?

我看 4.2.2 的文档里面写的就是 8192 。4.2.3 的文档里面才调整为区间 [ 8192 ~ 65536 ]

感谢回复~

@hnwyllmm
Copy link
Contributor

4.2.1 是一个LTS版本,会把某些高版本的功能patch回4.2.1。这个功能就是从4.2.3 patch到4.2.1的。
但是4.2.2不是一个LTS版本,很多功能并没有从4.2.3或者后续版本 patch回来,所以功能不会全面覆盖4.2.1。
你看看4.2.1是否可以满足你们的需求,改到LTS版本会更容易升级。

@CodePlayer
Copy link
Author

CodePlayer commented May 13, 2024

4.2.1 是一个LTS版本,会把某些高版本的功能patch回4.2.1。这个功能就是从4.2.3 patch到4.2.1的。 但是4.2.2不是一个LTS版本,很多功能并没有从4.2.3或者后续版本 patch回来,所以功能不会全面覆盖4.2.1。 你看看4.2.1是否可以满足你们的需求,改到LTS版本会更容易升级。

好的,谢谢,非常感谢!
不过建议官方对 LTS 版本做一个明显的标记,会更好一些~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants