面试官:说说 30 亿数据的表结构,你如何设计?

面试官刁难:一张千万级大表,CRUD卡成狗,你怎么优化? 面试中被问到“某张表有千万数据,CRUD很慢怎么优化?”很多人一时语塞,其实套路很清晰:从索引、SQL入手,到冷热分离、分库分表,再到缓存与异步化,逐层展开,思路完整才能打动面试官。 阅读详情

Hello 我是方才,8人后端研发leader、6年后端经验、3年团队管理&架构经验。

专注于分享成体系的编程知识、职场经验、个人成长历程等!

文末,方才送你一份优质的技术资料,记得领取哟!

Hello,我是方才,上一篇和大家分享了方才工作 7 年总结出来的数据库表设计方法论,有粉丝反馈说期望看到一个完整的实践和示例,今天他来了。

全文很干,每一个步骤都是核心内容,看到最后,我相信对你一定可以回答面试官的这个问题。

背景介绍

今天方才就以财务系统的科目余额相关需求为例,给大家展示下在实际的企业级项目中,如何应用该方法论进行数据库表结构设计。

通过这个示例,我相信大家会发现业务需求分析和技术方案的设计才是表结构设计的关键,最终关于表结构和索引的设计是可以通过参考人家的经验贴快速掌握的,但业务分析能力和技术方案设计能力,需要长期的刻意练习以及在业务领域的深耕才能有所成就。

da161cc614b721fdb4acb3c8c85f06b7.png
image-本文目录导航

步骤1:需求分析

方才想再次强调一下:技术是为业务服务的,所以对业务需求的分析是最最最重要的,只有理解了需求,才有可能设计出合适的技术方案,从而设计出相对最优的表结构。

先看方法论中关于需求分析的可选维度:

3777b3bafe28c7ad6d2265e861be47b0.png
image-20250120162524223

接下来参考上图,我们开始对财务系统的科目余额相关需求进行分析(考虑大家可能没有接触过财务系统,以下分析更多会用大白话解释,而不是专业的业务概念去解释):

unsetunset业务概念unsetunset

相关的概念如下:

  • 账套:指一个独立的会计核算单位的全部账簿体系,用于记录企业或组织的财务信息;

  • 会计科目:可以简单理解为就是一个具有层级关系的分类类目;

  • 科目余额:是某个会计科目在某一特定会计期间上的金额,通常包括期初余额、本期增加额、本期减少额和期末余额;就好比个人银行账户,按月记账,会有一个月初余额、本月收入支出、月末余额一样。

  • 会计期间:记账的时间周期,可以简单理解为每个月就是一个会计期间,每年就有12个会计期间;

  • 会计凭证:发生在会计科目上的财务行为记录,就好比个人银行账户的一个账单记录,记录了收支双方的信息、发生金额、备注等;

  • 凭证分录:多个凭证分录组成一个完整的会计凭证,是具体发生在某个会计科目上的财务行为记录;

  • 会计账簿:以科目余额和会计凭证数据为依据,形成的数据报表;

  • 会计报表:以科目余额和会计凭证数据为依据,形成的数据报表;

unsetunset业务行为unsetunset

和会计科目余额相关的业务行为(其他无关的,方才就省略掉了):

  • 会计科目维护:国家对会计科目有制度约束,标准的会计科目大概有167个左右,同时允许在标准的会计科目下新增下级子科目;

  • 科目初始化:用户去维护记账初期的数据;比如你要开始记账,肯定需要先记录你有哪些账户,每个账户的当前余额有多少;

  • 凭证记录:会实时影响到会计科目的余额;比如你给朋友转账了,那你的账户的余额会减少,你朋友账户的余额会增加;

  • 会计账簿&报表的查询:按会计期间范围,查询对应期间的科目余额数据,并按一定的规则计算得到的报表;

  • 某个会计期间的科目余额:通常包括期初余额(等于上一期的期末)、本期发生额(根据凭证数据计算)和期末余额(根据期初和发生额计算)等等指标;

业务模型如下图(ps:业务概念的分析,是为了后续的技术方案的设计):

8699953f9af0b89bca45571a386eb888.png
image-20250122172207204

unsetunset相关数据量预估unsetunset

理解了业务概念和业务行为后,就需要对相关业务进行数据量预估,这样在设计技术方案时,才能建立有效的评估指标,在本次分析的财务系统的会计科目余额相关的业务,核心的业务数据量情况如下:

  • 账套数:前期5-10w,后期每年100w左右的增量;

  • 会计科目数:以单账套平均300个会计科目为计算,每年在 300*100w = 3 亿的增量;

  • 凭证数:单账套凭证总数平均为1000计算,每年在 1000*100w = 10 亿的增量;

  • 凭证分录数:单个凭证按平均4条分录计算,每年在 10亿*4 =40 亿的增量;

  • 会计期间维度的科目余额数据:若按会计期间维度全量存储,每年 3亿会计科目*12个月 = 36亿的增量(这个就是本文后续讨论分析的点);

ps:数据预估基础指标来源是商务和业务资料的综合分析得出,这里就不细说了。

考虑整体篇幅,后续方才就仅讨论会计期间维度的科目余额的表结构设计。所以只需要关注 会计科目数 10亿 和 凭证分录数 40亿 这两个指标了。

步骤2:拟定技术方案

前提说明:在这个量级的系统设计中,会先有业务架构设计-》应用架构设计&数据架构设计-》部署架构设计,此处讲述的技术方案属于应用架构设计中代码级别的设计。

本文的重点是讲解表结构设计,所以这里先明确一个前提:应用架构已经设计完成,数据库选型为分布式数据库TiDB,不需要分库分表(大家若对海量数据下分布式数据库TiDB的实战经验感兴趣的,可以在评论区告诉方才哟)。

基于上面的前提条件,我们就直接开始拟定关于会计科目余额计算相关的技术方案了。整个思路如下:

39026395d52795445191cdca7fee00f9.png
image-20250120164205451

结合之前的业务分析,会计科目余额相关的依赖如下:

  1. 会计科目余额基于 科目初始化数据+会计凭证&分录数据计算生成;

  2. 会计报表&会计账簿 是基于科目余额数据进行计算生成;等价替换下,也可以直接基于  科目初始化数据+会计凭证&分录数据计算生成;

所以整个方案的关键就是:

  1. 会计科目余额数据是否需要落库;

  2. 若需要落库,那落库的时机和落库的指标字段有哪些;

  3. 不同的方案,对应的会计报表&会计账簿生成逻辑是怎样的。

经过头脑风暴,拟定3个可选方案(ps:这里更多是感知整个分析的过程,对于方案的具体的内容和逻辑,方才这里做了省略):

  • 方案1:会计科目余额数据不落库,只是一个逻辑概念,通过接口基于科目初始化数据+会计凭证&分录数据实时计算生成。

  • 方案2:会计科目余额数据按会计期间维度,在凭证数据更新时,记录所有会计科目的所有指标;会计报表&账簿的生成直接查询需要的数据即可;

  • 方案3:会计科目余额数据按会计期间维度仅保存凭证分录的会计科目+本期发生额相关的指标,其他指标根据 科目初始化数据 + 会计期间维度的本期发生额 计算得来;

方案的指标对比:

评估指标

方案1

方案2

方案3

实现复杂度

本质都一样,均是根据 科目初始化数据+会计凭证&分录数据计算,区别就是中间结果是否落库

本质都一样

本质都一样

可能存在的性能点

跨期间查询时,需要查询对应期间的所有凭证分录数据,按平均值计算,需要查询4000条数据,但考虑峰值,可能会涉及查询到10万级别的数据在内存中计算的情况,可能会导致应用内存溢出或数据库压力过大拖垮整个系统。

科目余额数据更新频繁

,每次有凭证更新,均需要更新对应层级树的所有的指标;同时会计科目余额表数据量会达到每年 36亿级别,对数据库资源的需求更大,且数据量上去后查询压力较大。

若所有科目在每个会计期间均有凭证发生,会和方案2存在同样的问题,但这几乎不可能。
相比方案2,会计科目余额表数据量能缩减5-10倍,每年在 7亿级别,同时更新的数据量也会更小,无性能风险。

可扩展性:以指标公式更新为场景

仅需要更新代码,无侵入

更新代码,极端情况需要重算 36亿级别的数据,同时因为记录是全量指标,发生概率比较大

更新代码,极端了情况需要重算7亿级别的数据,相对而言指标很少,发生概率是比较小的。
可通过大数据组件重算回写。

线上数据分析

数据未入库,若用户反馈数据异常,分析难度较大。需提供单独的逻辑将数据临时落库,便于分析。

所见即所得,用户看到的数据,数据库都有,可以快速分析出是什么指标的问题

指标落库不全,若用户反馈数据异常,分析难度较大。需提供单独的逻辑将数据临时落库,便于分析。

通过多维度对方案的对比,最终确定使用方案3进行落地实施(通过对比,可以感知到不同的方案,会计科目余额表的结构是不一样的,甚至都不需要有)。

步骤3:表结构设计

有了确定的技术方案后,就进入到了完整的表结构设计阶段。

主要思路是参考数据库范式&反范式设计,结合阿里巴巴规约,以及历史经验的总结,完成从表名、字段名、字段类型的定义。

8e52ea820688c0144d58628db1132b1e.png
image-20250120163525047

unsetunset先看完整的DDLunsetunset

先简单看下完整的表结构,然后我们再完整讲解表结构设计一些技巧。

CREATE TABLE`kjkm_fse` (
`id`bigint(20) UNSIGNEDNOTNULL AUTO_INCREMENT COMMENT'主键',
`zt_id`bigint(20) UNSIGNEDNOTNULLCOMMENT'账套id',
`kjkm_id`bigint(20) UNSIGNEDNOTNULLCOMMENT'会计科目id',
`kjkm_bm`varchar(64) NOTNULLCOMMENT'会计科目编码-冗余字段,便于查询',
`kjqj_id`bigint(20) UNSIGNEDNOTNULLCOMMENT'会计期间id',
`ljjf_je`decimal(22,2) NOTNULLDEFAULT'0.00'COMMENT'本期借方发生额',
`ljdf_je`decimal(22,2) NOTNULLDEFAULT'0.00'COMMENT'本期贷方发生额',
`bqs_je`decimal(22,2) NOTNULLDEFAULT'0.00'COMMENT'本期数-金额(借-贷,结合科目方向计算得到)',
`bqs_sl`decimal(22,2) NOTNULLDEFAULT'0.00'COMMENT'本期数-数量',
`created_time` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
`updated_time` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间',
`create_user_id`varchar(32) NOTNULLCOMMENT'创建人用户id',
`update_user_id`varchar(32) NOTNULLCOMMENT'更新人用户id',
  PRIMARY KEY (`id`,`zt_id`),
UNIQUEKEY`uk_zt_kjqj_kjkm_id` (`zt_id`,`kjqj_id`,`kjkm_id`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=1COMMENT='会计科目-发生额'
PARTITIONBYHASH (`zt_id`) PARTITIONS3;

unsetunset数据库范式设计unsetunset

  1. 三范式

  • 第一范式(1NF):确保表中的每一列都是不可再分的原子数据项。例如,ljjf_je(本期借方发生额)和ljdf_je(本期贷方发生额)分别独立存储,每一列都是不可分割的原子数据项。

  • 第二范式(2NF):在满足1NF的基础上,确保表中的非主属性完全依赖于主键(这个主键大家可以理解为业务主键,而非ID)。通过将zt_id(账套ID)、kjqj_id(会计期间ID)和kjkm_id(会计科目ID)组合成唯一键,确保了表中的数据与这些关键字段的强关联性。

  • 第三范式(3NF):在满足2NF的基础上,确保表中的非主属性之间不存在传递依赖关系。在上面的表里,冗余了 kjkm_bm 字段,不符合该范式。

反范式设计

  • 反范式设计(Denormalization)是数据库设计中一种有意引入数据冗余的技术,旨在提高查询性能。

  • 注意点:反范式设计会增加数据存储和数据的一致性维护成本。

  • 适用于:冗余存储的字段需要用于查询,且更新频率较低或是通过预计算得到的扩展字段等情况。

  • 例如,本表中同时存储了 kjkm_id(会计科目Id) 和 kjkm_bm(会计科目编码)。这种设计虽然增加了存储空间,但减少了查询时的连表操作或计算成本,能提高查询性能。

  • 例如,本表的 bqs_je= ljjf_je - ljdf_je 是通过预计算得到的扩展字段,也是为了满足查询需求。

unsetunset命名规范unsetunset

包括表名和字段名,参考以下几点:

  1. 必须使用小写字母或数字,使用下划线分割;

  2. 尽可能顾明思议,表字段的注释要及时更新,特别是枚举字段;

  3. 禁用保留字,如 desc、range、match、delayed 等;

  4. **索引命名规范,名称前缀 uk_/ idx_**:唯一索引名为 uk_字段名;普通索引名则为 idx_字段名;

命名规范还是很好理解的,大家日常应该是使用英语单词更多点,财务这块名词太长,所以方才使用的是中文的首字母缩写(只要整个库保持一个风格,可读性也是很高的)。

unsetunset必备字段unsetunset

参考阿里规约,建议表的必备字段有3个:id, create_time, update_time。

说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。create_time, update_time 的类型均为 datetime 类型,前者现在时表示主动式创建,后者过去分词表示被动式更新。

这个可以根据实际情况,自己去约定,比如方才建的表就有5个必备的字段:

`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`created_time` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
`updated_time` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间',
`create_user_id`varchar(32) NOTNULLCOMMENT'创建人用户id',
`update_user_id`varchar(32) NOTNULLCOMMENT'更新人用户id',

注意:

  • updated_time 字段要有  ON UPDATE CURRENT_TIMESTAMP属性,该属性用于指定当表中的记录被更新时,该字段的值会自动更新为当前的时间戳(CURRENT_TIMESTAMP),这个特性通常用于记录数据最后被修改的时间。

  • create_user_id 是varchar(32)类型,在此处是为了兼容历史数据,一般情况也应该是  bigint(20) UNSIGNED。

unsetunset字段选型unsetunset

字段类型要尽量和实际类型保持一致,然后优先选择最小的存储长度类型即可。

整数类型

参考MySQL官网:https://dev.mysql.com/doc/refman/8.4/en/integer-types.html 。

不同整数类型的存储大小和取值范围:

Type

Storage (Bytes)

Minimum Value Signed

Minimum Value Unsigned

Maximum Value Signed

Maximum Value Unsigned

TINYINT

1

-1280127255
SMALLINT

2

-3276803276765535
MEDIUMINT

3

-83886080838860716777215
INT

4

-2147483648021474836474294967295
BIGINT

8

-2^6302^63-12^64-1

按最小存储长度原则:

  • 枚举类型的字段一般使用 TINYINT;

  • 注意字段可能的上下限,避免溢出,比如国民级软件的点赞数这种,就建议使用BIGINT(上次抖音出现点赞数为负数,就是因为 int溢出了);

  • 表的主键,明确数据量会持续增加,且无上限,就建议使用BIGINT;若是字典维护表这种内部使用的,主键就可以用 INT。

补充:关于整数类型在DDL中定义时括号中的数字的含义是显示宽度:比如 int(11) 中的 (11) 是一个 显示宽度(主要用于 ZEROFILL 选项时,指定数字显示时前面填充的零的数量),而不是数据类型的实际存储长度或精度,整数类型的存储长度是固定了的,int类型的储空间始终是 4 字节。

小数类型(比如金额字段)

对于小数类型,建议遵循阿里巴巴规约,使用decimal类型(如decimal(22,2)),禁止使用float和double,以确保数据的精确性。

注意: DECIMAL(5,2) 表示能存储任何具有 5 位数字和 2 位小数的值,值范围为 -999.99 到 999.99。

当然对于金额字段的存储,一般有两种方案,一种是使用 decimal类型 保留2位小数,单位一般为元及以上;另一种就是使用BIGINT类型,将单位转为分,进行存储。

字符串类型

MySQL中字符串类型,包括 CHAR、VARCHAR、BINARY、VARBINARY、BLOB、TEXT、ENUM 以及 SET。这里方才就只讲几个重点了:

  1. CHAR(M) 是定长字符串。CHAR 列的长度固定为创建表时声明的长度。M 表示列长度(是字符的个数,不是字节的个数),当存储字符长度不足M时,会用空格右填充到指定长度。适用于存储枚举code等场景。

  2. VARCHAR 是变长字符串。M 表示最大列长度(字符的最大个数)。VARCHAR 的空间占用大小不得超过 65535 字节。在选择 VARCHAR 长度时,应当根据最长的行的大小和使用的字符集确定。是最常用的字符串类型。

  • 下表是单个字符占用的字节数,以及 VARCHAR 列长度的取值范围(关于字符集后续会讲):

字符集

单个字符字节数

VARCHAR 最大列长度的取值范围

ascii

1

(0, 65535]

latin1

1

(0, 65535]

binary

1

(0, 65535]

utf8

3

(0, 21845]

utf8mb4

4

(0, 16383]

也就是说字符集为utf8mb4的 varchar类型的最大长度只能是 16383,若超过,你建表时会报错:

5b55afd6c193e41df0a6a04c055f6a51.png
image-20250122151028028
  1. 如果你需要存储更大字节的内容,就可以使用``LONGTEXT 类型,最大列长度为 4,294,967,295 字节;或者 使用二进制大文件LONGBLOB` 类型,最大列长度为 4,294,967,295 字节。

  2. 注意:对于字符串字段,若长度超过5000,就建议使用text类型,且独立出来一张表,用主键来对应,避免影响其它字段索引效率(后续讲解了聚簇索引就可以理解为什么了)。

在我们当前这个示例中,使用varchar即可:

`kjkm_bm` varchar(64) NOT NULL COMMENT '会计科目编码-冗余字段,便于查询',

日期和时间类型

MySQL 的日期和时间类型,包括 DATE、TIME、DATETIME、TIMESTAMP 以及 YEAR。

  • DATE 类型只包含日期部分,不包含时间部分。DATE 类型的格式为 YYYY-MM-DD,支持的范围是 0000-01-01 到 9999-12-31。

  • TIME 类型的格式为 HH:MM:SS[.fraction],支持的范围是 -838:59:59.000000 到 838:59:59.000000。

  • DATETIME 类型是日期和时间的组合,格式为 YYYY-MM-DD HH:MM:SS[.fraction]。支持的范围是 0000-01-01 00:00:00.000000 到 9999-12-31 23:59:59.999999。

  • TIMESTAMP 类型是日期和时间的组合,支持的范围是 UTC 时间从 1970-01-01 00:00:01.000000 到 2038-01-19 03:14:07.999999。注意:

    • 范围上限问题:TIMESTAMP 数据类型受 2038 年问题的影响。如果存储的值大于 2038,需使用 DATETIME 类型。

    • 时区的问题:当存储 TIMESTAMP 时,MySQL 会将当前时区的 TIMESTAMP 值转换为 UTC 时区。当读取 TIMESTAMP 时,MySQL 将存储的 TIMESTAMP 值从 UTC 时区转换为当前时区(DATETIME` 不会这样处理)。

  • YEAR 类型的格式为 YYYY,支持的值范围是 1901 到 2155,也支持零值 0000。

所以,方才建议,日期和时间类型字段,优先选用DATETIME,同时要合理利用其自动初始化或更新为当前时间的特性,比如说创建时间和更新时间:

`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

unsetunset逻辑删除与物理删除unsetunset

什么是物理删除?什么是逻辑删除?

  • 物理删除:把数据从硬盘中删除,可释放存储空间

  • 逻辑删除:给数据添加一个字段,比如is_deleted,以标记该数据已经逻辑删除。

根据方才的经验,建议是优先采用逻辑删除。更方便去最终一些问题或者手动回滚数据等。

但若满足以下场景,是更适合使用物理删除的:

  • 目标表的数据量较高,比如超过500万;

  • 且删除操作频繁,导致被删除的数据占比较高,比如超过 1/10;

  • 建议:对于核心业务数据,且无法通过其他数据派生而来,可以将删除的数据插入到额外的表中,用做备份。

这个场景下,无用的数据太多,会影响到查询和更新的效率了。

而刚好,会计科目余额表就符合这个场景,数据量超10亿,凭证更新会导致数据的频繁覆盖写入,同时本身数据就是可以通过期初数据+凭证数据计算得来,所以可以看到kjkm_fse这个表是没有is_deleted字段的。

unsetunset字段个数unsetunset

个人建议表中字段尽量不超过20个,最多不超过50个。

理由是:因为MySQL的聚簇索引特征,过多的字段会导致回表操作成本过高,影响查询性能。

unsetunset字符集的选择unsetunset

字符集影响的是字符串类型的存储,包括能否存储以及一个字符对应的字节长度。

MySQL 支持的字符集有utf8、utf8mb4、GBK、latin1等。

  • latin1:MySQL 默认字符集,1 个字节长度,所以容易出现乱码问题;

  • GBK :支持中文,但是不支持国际通用字符集,2 个字节长度;

  • utf8:支持中英文混合场景,国际通过,3 个字节长度;

  • utf8mb4: 完全兼容 utf8,4 个字节长度,可存储更多的字符;

方才推荐字符集优先选择utf8mb4,支持更广泛的字符集范围,通过建表语句 CHARSET=utf8mb4可以指定。

ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=1 COMMENT='会计科目-发生额'
PARTITION BY HASH (`zt_id`) PARTITIONS 3;

unsetunset排序规则的选择unsetunset

排序规则会影响对字符类型的排序以及查询,是非常重要的,一不小心就容易出现bug,且很难定位。

一个字符集可以有多种排序规则。排序规则的命名格式为 <character_set>_<collation_properties>。例如,utf8mb4 字符集有一个名为 utf8mb4_bin 的排序规则,它是 utf8mb4 字符集的二进制排序规则。下表是常见字符集和排序规则的后缀和含义:

后缀

含义

_bin

二进制排序规则,区分大小写

_ci

不区分大小写

_ai_ci

不区分重音和大小写

_0900_bin

Unicode UCA 9.0.0,二进制排序规则

_unicode_ci

(较旧的)Unicode UCA 排序规则,不区分大小写

_general_ci

较宽松的 Unicode 排序规则,不区分大小写

简单看两个示例就会理解深刻了。

下面这个示例,通过COLLATE=utf8mb4_general_ci 和COLLATE=utf8mb4_bin分别设置不同的排序规则:

CREATE TABLE`fc_test` (
`id`bigint(20) UNSIGNEDNOTNULL AUTO_INCREMENT COMMENT'主键',
`mc`varchar(64) NOTNULLCOMMENT'名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='不区分大小的测试';

INSERTINTO fc_test( mc) VALUES('fangcaicoding'),('Fangcaicoding'),('cdoing');


CREATETABLE`fc_test2` (
`id`bigint(20) UNSIGNEDNOTNULL AUTO_INCREMENT COMMENT'主键',
`mc`varchar(64) NOTNULLCOMMENT'名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='区分大小的测试';

INSERTINTO fc_test2
( mc)
VALUES('fangcaicoding'),('Fangcaicoding'),('cdoing');

查询和排序下:

-- 不区分大小写
select * from fc_test where mc = "Fangcaicoding" order by mc;
select * from fc_test order by mc desc;
-- 区分大小写
select * from fc_test2 where mc = "Fangcaicoding" order by mc;
select * from fc_test order by mc desc;

运行截图如下:

d558975ae1b7badf158163b1864e4f4f.png
image-20250122162008540

通过这个示例,我想大家都一定理解了字符集的排序规则的影响点了。

一般情况,方才推荐排序规则优先选择utf8mb4_bin,通过COLLATE=utf8mb4_bin语句设置,区分大小写,确保数据的准确性和一致性。

步骤4:索引的设计

关于索引,方才发现很多初中级程序员都没有形成一个方法论。很容易走两个极端,要么是除了主键没有其他任何索引,要么就是索引一大堆。

方才结合自己针对数十亿表的索引优化经验,总结如下:

06a9fe06fdf16018c48ebd1d3f0f03c8.png
image-20250120165626152

结合上面的脑图,针对kjkm_fse这个表,我们来一一分析下。

unsetunset关于主键unsetunset

方才建议所有的表均应该有主键,优先为数字类型,且保持自增性(若是辅助表,主键可以直接使用主表的)。

常用的主键生成机制有:

  • 数据库自增 auto_increment ;

  • 基于外部算法代码实现:比如雪花算法、百度Uid-Generator、美团Leaf等;

在kjkm_fse表是有自增id的,但因为数据量较大,使用了分区表(关于TiDB的分区表,大家若有兴趣,可以在评论区告诉方才哟),分区字段按规范需要作为主键的组合字段之一,所以该表的主键如下:

-- 省略了无效内容
CREATE TABLE `kjkm_fse` (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键'
  PRIMARY KEY (`id`,`zt_id`)
) PARTITION BY HASH (`zt_id`) PARTITIONS 3;

补充:如果一个表没有显示定义主键:

  • MySQL的 InnoDB 引擎会尝试使用第一个非空的唯一索引(NOT NULL 和 UNIQUE INDEX)作为聚簇索引。如果没有合适的唯一索引,InnoDB 会自动生成一个隐藏的 ROW_ID 列作为聚簇索引,这个隐藏列是递增的。

  • TiDB数据库,如果表没有主键,TiDB 会自动生成一个隐式的 _tidb_rowid 列作为行 ID。这个列的值是单调递增的。

unsetunset索引创建依据unsetunset

除了主键建议均有,其他索引的创建,是需要跟进实际情况进行判断的,方才总结规则如下:

  1. 前提:目标表的数据量级会超过10万;

  2. 对唯一性要求的字段;

  3. 必要的查询字段;

  4. 索引不是越多越好,过多的索引,会影响数据更新效率,同时会导致sql的自动优化出现非预期行为;

  5. 注意不是所有查询字段均需要:若可以通过其他必传查询字段,使得过滤后的数据量级小于10万,就可以不用创建;

unsetunset唯一索引unsetunset

方才建议,业务上有唯一特性的字段,必须建唯一索引或组合唯一索引:

理由:唯一键对 insert、update的性能损耗较小,对查询速度的提升是很明显的;同时根据墨菲定律,比如会产生脏数据

一定要注意:组合唯一键的所有字段均不能为空,否则可能导致唯一键约束失效。

  • 原因:根据MySQL官方文档,NULL表示“缺失的未知值”,它与任何其他值(包括另一个NULL值)进行比较时都不会返回真值。这种特性导致了NULL在唯一性约束中的特殊行为。

  • 效果直接看下面的示例截图就知道了,uk_typeId_mc2 是没有锁住的,数据1-3-8在逻辑上都是重复的:

39626eaa871e82c93db8e6251b9710af.png
image-20250122164336346

回到kjkm_fse这个表,在业务上,就要求一个账套下,一个会计期间,同一个科目的余额一定是只能有一条数据,所以就创建了组合唯一索引:

UNIQUE KEY `uk_zt_kjqj_kjkm_id` (`zt_id`,`kjqj_id`,`kjkm_id`)

ps:业务上是有根据 kjkm_bm会计科目编码查询需求的,但这里方才并没有针对该字段创建索引,是因为什么呢?可以参考索引的创建依据说明,业务上明确所有的查询,一定都会携带zt_id账套id参数,根据该参数,已经可以将数据量过滤至5000以内了,就没有必要再创建了。

unsetunset组合索引unsetunset

关于组合索引,方才就提两个技巧:

  • 尽量将区分度高的字段放在前面(后续分享了B+树索引后,就可以理解了);

  • 高频查询语句,可创建组合索引,利用索引覆盖机制优化sql性能。

unsetunset普通索引unsetunset

关于普通索引的创建,就优先参考上面的内容,有必要再创建。

需要注意的是,参考阿里规约,在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。(防止字段太长,索引内容过大,导致其他问题)。

说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来确定。

小结

我相信通过这个示例,大家对如何进行表结构设计,都有了自己的理解,也欢迎在评论区交流哟。

原创不易,如果对你有所帮助,记得帮方才点个赞、点个爱心,来个评论,这对方才很重要。

ps:本文的思维导图xmind源文件,都在资料库了,可以直接扫码领取。


unsetunset交流群unsetunset

茫茫人海,方才刚好写了这篇博客,而优秀的你又刚好看到且点了个赞。

相遇即是缘分,方才送你一份优质的资料,也可备注加群,方才拉你进入优质的技术交流群。

02067a35f6e98651526adafc8b58684d.jpeg
技术资料
Hive面试必问30题:从原理到优化,搞定面试官就靠这篇 理解原理:不仅要会写HiveQL,还要理解背后的执行原理实战经验:准备几个你优化过的实际案例,用数据说话最新动态:了解Hive社区的最新发展,展示你的学习能力综合能力:理解Hive在整体数据架构中的位置和角色掌握这30个问题,你就能在Hive面试中游刃有余。但记住,面试不仅是考察知识点,更是考察解决问题的思路和能力。希望本文不仅能帮助你在面试中脱颖而出,更能提升你的实际Hive开发能力。祝你在接下来的面试中取得好成绩!如果你在学习和面试过程中遇到任何问题,欢迎在评论区留言讨论。 阅读详情

相关推荐

从菜谱数据库实战看关系型数据库设计:从表结构到并发控制

数据库设计是软件开发中的核心基础能力,而关系型数据库凭借其严格的数据一致性、灵活的关联查询和成熟的事务机制,成为处理结构化数据的首选方案。以食谱管理为切入点,通过分析食材与菜谱的多对多关系、索引优化、视图封装、事务与并发控制等关键技术,可以系统理解关系模型如何保障数据完整性。这种设计思路广泛适用于电商、内容管理等场景,能够有效解决数据冗余和查询性能问题。MySQL作为主流关系型数据库,其表结构设计与优化方法具有很强代表性,同时达梦、人大金仓等国产数据库的适配也逐步成为政企项目的实际需求。本文从真实项目出发,

weixin_34005090的博客 263

面试官:说说30亿量级的表结构,你是如何设计的

今天方才就以财务系统的科目余额相关需求为例,给大家展示下在实际的企业级项目中,如何应用该方法论进行数据库表结构设计。通过这个示例,我相信大家会发现业务需求分析和技术方案的设计才是表结构设计的关键,最终关于表结构和索引的设计是可以通过参考人家的经验贴快速掌握的,但业务分析能力和技术方案设计能力,需要长期的刻意练习以及在业务领域的深耕才能有所成就。

方才coding 1452

DMA+Timer 生成 PWM 波形错位问题分析与解决

本文深入分析了DMA与Timer协同生成PWM波形时出现的错位问题,揭示了DMA传输启动时机与定时器溢出中断的微妙关系。通过占空比强制清零法和预装载值调校技巧,提供了有效的解决方案,并总结了PWM+DMA配置的最佳实践,帮助开发者避免类似问题。

weixin_29191081的博客 350

亿级表优化思路-SQL 篇

为啥会写亿级表优化?网上谈的不少但是不知道大伙儿是否在自己 Owner 的业务中验证过。我也来凑凑热闹。

pengyachuan的博客 1320

uni-app项目中引入Vant UI组件库(完美避坑!!!)纯净版

uni-app项目中引入Vant UI组件库(纯净版--无uni-ui)

Wzmlzrr的博客 3万+

达芬奇Pro开发板硬件验证全流程指南

FPGA开发板硬件验证是嵌入式系统可靠性的基石,其核心在于理解SoC启动时序、IO资源映射与高速接口物理层约束。基于Xilinx Zynq-7000异构架构的开发平台,需围绕电源完整性、时钟稳定性、信号完整性及软硬协同启动链路展开分层测试。技术价值体现在规避量产失效率、支撑高可靠工业控制与实时图像处理等场景。典型应用涵盖千兆以太网通信验证、DVP摄像头与RGB LCD显示链路调试、以及SFP+光纤接口PRBS误码率测试。本文聚焦达芬奇Pro系列开发板,深入解析JTAG下载、QSPI Flash重烧录、RGM

weixin_42453228的博客 126

1.亿级积分数据分库分表:总体方案设计

本篇博客从整体上介绍了一个完整的积分明细分表的实施方案,包括分表数量、分片键选择、分片算法、分布式ID、历史存量数据迁移、新老数据双向核对校验、灰度切流验证等

没头脑遇到不高兴 2701

在MDK5.37版本中添加Complier Version 5( 解决Missing Compiler Version 5 )

本文章仅作为个人学习记录,方法也是从其他博主的博客中得到的,链接如下:二、Keil——Missing Compiler Version 5 和 core_cm3.c 问题解决 - YI颗白菜 - 博客园 (cnblogs.com) 问题描述 更新最新的keil版本(MDK5.37)后,在编译以前版本的工程文件时,发现出现了非常多的报错,在网上搜索之后发现原来时编译器版本不匹配的问题,即MDK5.37中使用的是Version 6版本的编译器,而缺少Version 5,对于以前编写的工

leveling99的博客 1万+

亿级数据如何设计数据库

电商亿级数据库设计-原理实 战 1. 电商 数据库架构体系剖析 1.1 背景概述 随着互联网普及率的不断提高,中国电商稳步发展,市场零售额 不断增长。根据数据显示,2019年,全国网上零售额106324亿 元,比上年增长16.5%。与此同时,得益于社交电商的流量效 应,电商用户持续增加。数据显示,截至2019年底,中国移动电 商用户规模预计将突破7亿人。 而对于电商系统来说, 具有以下几个特点:...

okbin1991的博客 856

全志F1C200S嵌入式驱动开发(lcd屏幕驱动)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 lcd RGB屏幕作为比较经济、实用的显示工具,在实际场景中使用较多。它的信号来说,一般也比较简单,除了常规的数据信号,剩下来就是行同步、场同步、数据使能和时钟信号了。数据信号方面,常见的格式有RGB888、RGB666、RGB565。为什么有什么多选择,主要还是取决于soc pin脚的多少来定。当然,pin脚越多,显示的颜色就越精准,还原度就越高。 举例来说,假设

嵌入式-老费,一个分享专业嵌入式知识的blog 2797

TenserRt平台SMOKE 3D目标检测部署完整教程:从理论到实践

本教程详细介绍了在RK3588平台上部署SMOKE 3D目标检测模型的完整流程。SMOKE是一种创新的单阶段单目3D检测方法,通过关键点估计直接预测3D目标信息,有效解决了深度信息缺失等挑战。文章从理论基础出发,解析了SMOKE的核心算法和网络架构,包括DLA-34主干网络和关键点中心化策略。同时提供了详细的Docker环境配置、模型导出到ONNX格式的实践指南,以及TensorRT引擎构建和性能优化的关键技术要点。该教程为开发者在嵌入式平台上实现高效3D目标检测提供了完整的技术参考。

LXY_LOVE36的博客 373

海量数据的数据库表设计细则

1. 表分类设计和命名规则 一般海量数据就需要对表进行分类,主要分为如下: 主数据,其数据量基本稳定,不随时间而线性增长。比如,分公司,产品,经销商。 这种数据库表,我们一般以 tm_ 作为表名的前缀, 意思是 table of master data。 系统级数据,其数据量基本稳定,不随时间而线性增长。比如,用户权限控制,配置参数。 这种数据库表,我们一般以 ts_ 作为表名的前缀, 意思是 table of system。 日志数据表,随时间而线性增长,但会安排定时任务定期删除旧数据,保持总体数据量稳

一个人的江湖 2452

深入解析:AndroidManifest.xml 与 Qt 6.7.0 的整合

本文探讨了在Qt 6.7.0和CMake构建Android应用时,AndroidManifest.xml未被正确识别的问题。通过分析CMake配置错误、文件路径问题等可能原因,提出了在CMakeLists.txt中明确指定路径、确保目录结构正确等解决方案,并给出了示例项目演示。文章强调正确配置AndroidManifest.xml对获取权限和应用功能的重要性,为Qt开发者提供了实用的调试建议。

2501_90323865的博客 110

面试又双叒叕被问到数据库三大范式,该怎么答才能让面试官认可呢

面试问到三大范式,如何说能让面试官满意呢? 别想了,快上车,正规车~

陈哈哈的菜园子 6521

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

亿级订单多维查询系统架构设计与Elasticsearch优化实战

多维查询作为现代数据系统中的核心技术,通过倒排索引和分布式架构实现对海量数据的快速检索。其原理是将数据按维度分类建立索引映射,当用户组合多个条件筛选时,系统能快速定位匹配记录。这种技术在电商、金融等领域具有重要价值,能够支撑实时运营决策和复杂业务分析。在海量数据场景下,传统数据库面临查询性能瓶颈,需要结合Elasticsearch等专业搜索引擎构建分层查询架构。本文以亿级订单系统为案例,深入解析如何通过MySQL与Elasticsearch的协同优化,结合缓存策略和监控体系,实现毫秒级的多维查询响应。其中涉

weixin_34319111的博客 359

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

SQL 优化那些事:面试官为什么总爱问这些?

摘要:SQL优化是面试高频考点,因其直接关乎系统性能与成本。面试官考察重点包括:慢查询排查(EXPLAIN分析)、索引优化(最左前缀原则、失效场景)、SQL写法优化(分页、IN/EXISTS选择)以及表结构设计。典型问题如百万级数据分页、覆盖索引原理等,需结合执行计划具体分析。回答应强调实战思维:先定位问题(慢查询日志),再针对性优化(索引/SQL改写/结构调整)。掌握这些核心点能展现系统性数据库能力。

m0_46421522的博客 1477

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

必会的这15个Mysql优化问题,面试官、DBA都要高看你一眼,速度收藏

15个Mysql优化的技巧,一次性告诉你,面试、工作必备技能。 - 你在开发过程中是怎么排查SQL语句的? - 开发排查思路是什么? - 相关慢SQL设置参数是什么? - 有没有便捷工具? - 你是怎么排查生产环境SQL问题的? - 生成环境排除SQL应当着重注意什么? - 你知道怎么调优SQL吗? - 怎么设计表或优化表? - 为什么要合理的使用字段的长度? - 为什么要用冗余设计? - 临时表是什么? - 为什么垂直分表可以提升性能? - 怎么优化索引?...

4059

Java实习模拟面试之MySQL数据库表设计与约束实战:主键、检查约束与计算列

sc表设计与DDL:使用定义主键、非空、检查约束。表结构修改:使用动态扩展表结构。数据完整性:通过CHECK约束保证字段值在合理范围内。计算列实现:优先使用生成列(Generated Column)自动维护派生数据,替代复杂的触发器逻辑。NULL值处理:理解NULL在计算中的传播特性,合理设计默认值。数据类型选择:理解FLOAT(M,D)的含义,优先使用DECIMAL存储精确数值(如成绩、金额)。这些技能不仅是面试常考点,更是实际项目中保障数据质量的关键。

吾为热爱计科,致力于知识分享的践行者,此博客是我在这条道路上留下的足迹。 680

面试又双叒叕被问到数据库三大范式,该怎么答才能让面试官认可呢???

干饭人,干饭魂,吃饭干饭要拿盆   饭桌上同事问我面试时会不会问数据库的三大范式,回答的都咋样?   因为在他最近面试问这问题时,发现很多同学对范式概念很模糊,有人倒是准备了,直接背起标准答案来。。他表示很无语。   其实,三范式这类问题,面试官想考察的是我们平时开发中建表、字段时的一些经验和见解,并不是希望听到那些理论的东西。建议面试的兄弟们可以多从实际经验角度出发,比如先简单说一下各范式区别,然后通过一个实际场景(数据表)来谈一谈自己对各级范式的理解。让面试官get到他想听到的点,足矣。   废

Javaforeveryear的博客 300

我用蓝耘MaaS + 魔珐星云,打造了一个压迫感拉满的 AI 面试官

且目视他人之凝目如点点鬼火, 大胆去走你的夜路。在正式开始之前,先快速过一遍这次用到的“武器库”。如果你对某些组件已经很熟悉,可以直接跳过;如果还不太了解,看这段就够了。整个系统由四个核心组件构成,我按“大脑 → 神经 → 身体”的顺序来拆解:🧠 蓝耘 MaaS(大脑) 蓝耘的 MaaS(Model as a Service)平台,简单说就是把 DeepSeek、GLM、Qwen 这些主流大模型打包成标准的 API 服务。不用自己部署模型,不用管 GPU 驱动,注册就能调。最关键的是它完全兼容 OpenA

Pocker_Spades_A的博客 1万+

从码农到认证工程师:我用软考软件设计师证书涨薪30%的真实复盘

本文是一位Java开发者分享通过备考软考软件设计师(中级)认证,实现薪资增长30%的真实经历。文章详细复盘了从职业价值认知、三个月高效备考计划到面试晋升中活用证书的全过程,为技术人突破职业瓶颈提供了系统性方法和避坑指南。

seed的博客 583

面试官不能说的秘密:软件大厂面试经验

然而,大厂的面试门槛也相对较高,有着一套复杂且严格的选拔流程。作为一名软件公司的面试官,在此结合多年的面试经验,为大家详细剖析软件大厂面试的要点,希望能助力各位求职者顺利通过面试,开启理想的职业生涯。总之,软件大厂面试虽然具有一定难度,但只要做好充分的准备,了解面试流程和常见问题,掌握应对策略,注重细节和心态调整,就能够在面试中发挥出自己的最佳水平,增加成功入职的机会。面试是大厂招聘流程中最为关键的环节,一般包含多轮面试,从不同角度全面考查候选人。软件大厂的面试流程一般较为复杂,通常包含多个环节。

qq_20294455的博客 1001

如何30天让你的MySQL技能从零基础到专家级?面试官都被我震住了!

大家好,我是大头,98年,职高毕业,上市公司架构师,大厂资深开发,管理过10人团队,我是如何做到的呢?这离不开持续学习的能力,而其中最重要的当然是数据库技术了!对于所有开发来说,都离不开数据库,因为所有的数据都是要存储的。其他的技能可能会变,比如业务开发、算法开发、基础设施开发,也不管你是用Java、php、golang、C++等等。你都会用到数据库,因此,学好数据库对于我们来说就至关重要了。接下来大头将分享自己学习数据库的路线以及心得。后续也会根据这个路线分享所有的学习方法以及实操案例。

大头讲架构的博客 1297
上一篇: 最近我的 10 条深度思考!分享给你!
下一篇: 美好的 3 月,军哥高质量社群招人!!
军哥手记
军哥手记 业界专家认证 业界专家认证
博客等级 码龄21年 661粉丝 608原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值