|
| 1 | +--- |
| 2 | +title: TiDB 区块链大整数最佳实践 |
| 3 | +summary: 介绍 区块链大整数 在 TiDB 中的应用 |
| 4 | +--- |
| 5 | + |
| 6 | +# TiDB 区块链大整数最佳实践 |
| 7 | + |
| 8 | +## 区块链大整数 |
| 9 | + |
| 10 | +现在大部分程序语言直接支持 64 bit 的内置类型,即直接支持相关运算。而超过 64 bit 的的整数,需要自己来实现。 |
| 11 | +大整数是 区块链 的很重要的组成部分。比如用大整数来标识基础数据类型,如 账号、地址、交易 hash、区块 hash;比如有许多大整数计算的算法,比如 merkerl,pow 等。 所以区块链应用场景,一般来说,大整数支持是必须的。 |
| 12 | + |
| 13 | +## TiDB 大整数 解决方案 |
| 14 | + |
| 15 | +TiDB 没有直接支持大整数的存储与计算(默认支持的最大整数是 `64bit`),针对 大整数 比较通用的解决方案是: |
| 16 | + |
| 17 | +- 用 `binary(M)` 来存储大整数 —— `M` 表示大整数的字节数(比如,`U256` 用 `binary(32)` 来表示)。 |
| 18 | +- **必须** 高位补 0,保证 M 个字节全部填满。 |
| 19 | +- **必须** 是 `big-endian` 字节序列—— 这个由应用保证,生成的字节序列是 `big-endian` 的。 |
| 20 | +具体实现细节、原理请看下文。 |
| 21 | + |
| 22 | +## 大整数在 TiDB 实现原理 |
| 23 | + |
| 24 | +### 1. 存储 |
| 25 | + |
| 26 | +TiDB 默认支持的最大整数类型是 64bit,所以跟大多数程序语言一样,是默认不支持大整数的。 |
| 27 | +但是,TiDB 是数据库,如果仅仅存储这类大整数数据,还是没问题的。 |
| 28 | +在 TiDB 用 `binary(M)` 来存储大整数。其中,M 是根据 大整数 `bit 数` 除以 8 得到。 |
| 29 | + |
| 30 | +> 一般情况下,大整数 `U/S + bit`位数一起来表示类型。其中,`U`表示无符号整数;`S`表示有符号整数。所以,U256 即表示 无符号的 256bit 的整数。当然,还可以有 有符号 128bit 整数 -\> S128。当然,计算机直接支持的,u64 表示 64bit 的无符号整数。 |
| 31 | +
|
| 32 | +> `binary(M)` ,`M` 表示字节数。所以,如果用 `binary(M)` 来表示 `U256`,那么就需要 32字节`(32=256/8)` =\> `binary(32)` 来存储 `U256`/`S256`。 |
| 33 | +
|
| 34 | +### 2. 字节序(Byte Order) |
| 35 | + |
| 36 | +字节序,大家应该都有了解。或者,网上资料、说明一大坨,自己看吧。 |
| 37 | + |
| 38 | +简单来说,0x03020100 需要 4字节存储,假设从位置 p 开始 |
| 39 | + |
| 40 | +```C++ |
| 41 | +// 存储中存放示例 |
| 42 | +// big-enian |
| 43 | +p[0] = 0x03; |
| 44 | +p[1] = 0x02; |
| 45 | +p[2] = 0x01; |
| 46 | +p[3] = 0x00; // 大(高位)存储 放在了 小数据(低位数据)。 |
| 47 | +// little-endian |
| 48 | +p[0] = 0x00; |
| 49 | +p[1] = 0x01; |
| 50 | +p[2] = 0x02; |
| 51 | +p[3] = 0x03; // 大(高位)存储 放在了 大数据(高位数据)。 |
| 52 | +``` |
| 53 | + |
| 54 | +所以,很明显的,排序还跟 ByteOrder 相关。一般系统的默认 ByteOrder ,指的是系统内置类型整数的存储到 byte 中的顺序。而大整数,是我们自己实现的。 |
| 55 | + |
| 56 | +而系统默认排序算法,一般都是从 bytes 的低字节开始到最后一个字节,一个个遍历比较。 |
| 57 | +> binary 本质上来说也是 bytes,所以排序规则也是 从 `低字节(byte[0])` 到 `高字节(byte[len-1])` 一个个字节进行比较。 |
| 58 | +> |
| 59 | +> 我们知道,整数中,高位数据肯定比低位数据大(比如 `百位的 1` 比 `十位 的任何数字`都大)。所以正常的整数比较,都是高位数据跟高位数据比较 —— 这就要求 binary 存储数据,`高位数据` 存储在 `低位存储`,从上面示例可以知道,这是 `big-endian`!!! |
| 60 | +
|
| 61 | +### 3. 排序 |
| 62 | + |
| 63 | +我们已经知道,binary 必须要以 big-endian 存储。但是,这里还有一个注意点:不会比较长度。具体看如下例子: |
| 64 | +> A = 0x0203 |
| 65 | +> B = 0x010203 |
| 66 | +> 如果作为整数比较,那么 `0x0203 < 0x010203` 得到 `A < B` |
| 67 | +> 但是,如果 binary 比较,那么会先比较最高位字节即 `0x02 > 0x01` ,所以 `A > B` |
| 68 | +> 附注:如果最高位字节相等,那么比较下一个 字节;如此反复; |
| 69 | +
|
| 70 | +所以,我们期望的是用 binary 比较 有 正数比较 相同的效果。其实,这里面最重要的点是没比较长度——既然它不比较,那么我们就弄成长度相等(高位补0),那就不需要比较长度了!!! |
| 71 | + |
| 72 | +其实,我们一般的程序语言内置类型(比如 u64),本质上来说就是 高位补0 了,所以即等长比较大小的。 |
| 73 | + |
| 74 | +> OK,用了定长数据,继续上面的例子,来看看效果: |
| 75 | +> 我们假设都是 4字节整数(u32) |
| 76 | +> A = `0x0000 0203` |
| 77 | +> B = `0x0001 0203` |
| 78 | +> 所以根据刚刚算法,`0x00 == 0x00` , 然后 `0x00 < 0x01` ,得到 `A < B` ,符合预期!!!。 |
| 79 | +
|
| 80 | +### 4. 小结 |
| 81 | + |
| 82 | +所以,这里 TiDB 来处理大整数,两个基本的问题都解决了: |
| 83 | + |
| 84 | +- 存储: binary(M) 来存储,M 可以根据大整数 bit数 来计算 。 |
| 85 | +- 字节序: 必须要以 big-endian 生成的 bytes ,来存储到 binary 中。 |
| 86 | + - 注意,这个也是 应用来保证的。只有以这种方式存储,才能得到满足预期的结果 |
| 87 | +- 排序: 通过补0 来保证存储的 binary 长度一致,所以 binary 默认排序可以满足。 |
| 88 | + - 注意,这里的补0不是字符串 `'0'(0x30)` ,而是数值`0(0x00)`; 而之前的示例都是用 hex 表示,即整数数据,而非字符串 -- **切记切记**。 |
| 89 | + - 有了排序,存储的时候就可以按序存储,查询的时候,也可以高效的利用索引,而且查询的数据也会符合预期。 |
| 90 | +- 展示: 一般情况下,binary 一般是用 hex 格式展示 |
| 91 | + - 如果是 非打印字符,那么一般默认用 hex 展示。 |
| 92 | + - 我们这里的二进制数据,所以是没法打印的,所以只能用 hex |
| 93 | +- 其它: 其它的种种 大整数功能,请应用程序来实现 |
| 94 | + - 其它功能,包括但不限于: 基本的整数运算,安全(溢出)等等。 |
| 95 | + |
| 96 | +## 大整数 在应用程序实现 |
| 97 | + |
| 98 | +一般情况下,高级程序语言本身库来支持 大整数,而且支持一整套运算。 |
| 99 | + |
| 100 | +> 比如, Golang 有 `big.Int` (系统库);C/C++ 有 `gmp` ; Java 有 `BigInteger` 等。 |
| 101 | +
|
| 102 | +- 基本功能: 应用端都是没问题的。 |
| 103 | +- 安全: 溢出等安全问题,一般 整数运算 类似,要注意。 |
| 104 | +- 存储: 要注意在转换的时候,要补0 来达到 特定字节数的 bytes。 |
| 105 | +- 字节序: 一定要是 big-endian (不过一般默认都是该字节序)。 |
0 commit comments