034、TiDB特性_AUTO_RANDOM

使⽤场景

AUTO_RANDOM ⽤于解决⼤批量写数据⼊ TiDB 时因含有整型⾃增主键列的表⽽产⽣的热点问题。简而言之:就是数据处理不要只在一个region上。 通过随机的产生主键,让数据分布在不同的region上。auto_random: 是一个字段属性,用于自动随机填充默认列值以解决热点问题

auto_random实现原理

  • 是一个8字节的bigint整数
  • 最高位为符号位
  • 默认63 ~ 59为随机位(shard bits),每次插入行记录时随机生成一个1 ~ 32的随机数
  • 若要使用不同长度的随机位可调整auto_random括号中的数量
  • 基于分片的随机生成器
  • last_insert_id(): 与auto_increment的情况类似,检索上次插入操作所使用的值

使用限制

  • auto_random 列只能为bigint类型
  • 主键属性为nonclustered时,即使是整型主键列,也不支持使用auto_random
  • 不支持使用alter table来修改auto_random属性
  • 不支持修改含有auto_random属性的主键列的列类型
  • 不支持与auto_increment同时指定在同一列上
  • 不支持与列的默认值default 同时指定在同一列上
  • 插入数据时,不建议显式的指定含有auto_random列的值
  • 它是一个基于分片的随机生成器
  • ⽀持将 AUTO_INCREMENT 属性改为 AUTO_RANDOM 属性。
  • AUTO_RANDOM 列的数据很难迁移到 AUTO_INCREMENT 列上,因为AUTO_RANDOM 列⾃动分配的值通常都很⼤。
  • 插⼊数据时,不建议⾃⾏显式指定含有 AUTO_RANDOM 列的值。

相关参数

  • PK_AUTO_RANDOM_BITS
    PK_AUTO_RANDOM_BITS=5 : 这个参数表示的是会往多少个region(分片)中分配。5代表 2^5次方 32个region
mysql> select TIDB_ROW_ID_SHARDING_INFO, TIDB_PK_TYPE
    -> from information_schema.tables
    -> where table_schema='test'
    -> and table_name='auto_random_t1';
+---------------------------+--------------+
| TIDB_ROW_ID_SHARDING_INFO | TIDB_PK_TYPE |
+---------------------------+--------------+
| PK_AUTO_RANDOM_BITS=5     | CLUSTERED    |
+---------------------------+--------------+
1 row in set (0.17 sec)
  • PRE_SPLIT_REGIONS
create table t (a int, b int) PRE_SPLIT_REGIONS=3;
开始写数据进表 t 后,数据会被写入提前切分好的 82^3次方)个 Region 中,这样也避免了刚开始建表完后因为只有一个 Region 而存在的写热点问题。

那如何将数据插入到这8个region中呢,这个时候就可以通过id(主键)来做这个事情,此时就可以用 AUTO_RANDOM (如果用auto_increment就不能打散了)

  • allow_auto_random_explicit_insert
    要使⽤显式插⼊的功能,需要将系统变量
@@allow_auto_random_explicit_insert 的值设置为 1 (默认值为 0 )。

建表事项

  • auto_increment
    以下⾯语句建⽴的表为例:
CREATE TABLE t (a bigint PRIMARY KEY AUTO_INCREMENT, b varchar(255))

在以上语句所建的表上执⾏⼤量未指定主键值的 INSERT 语句,示例如下:

INSERT INTO t(b) VALUES ('a'), ('b'), ('c')

如以上语句,由于未指定主键列的值( a 列),TiDB 会使⽤连续⾃增的⾏值作为⾏ ID,可能导致单个 TiKV 节点上产⽣写⼊热点,进⽽影响对外提供服务的性能。要避免这种写⼊热点,可以在执⾏建表语句时为 a 列指定 AUTO_RANDOM 属性⽽不是 AUTO_INCREMENT 属性。

  • AUTO_RANDOM

建表

CREATE TABLE t (a bigint PRIMARY KEY AUTO_RANDOM, b varchar(255))
或者
CREATE TABLE t (a bigint AUTO_RANDOM, b varchar(255), PRIMARY KEY (a))

此时再执⾏形如 INSERT INTO t(b) values… 的 INSERT 语句。

  • 隐式分配:如果该 INSERT 语句没有指定整型主键列( a 列)的值,或者指定为 NULL ,TiDB 会为该列⾃动分配值。该值不保证⾃增,不保证连续,只保证唯⼀,避免了连续的⾏ ID 带来的热点问题。
  • 显式插⼊:如果该 INSERT 语句显式指定了整型主键列的值,和AUTO_INCREMENT 属性类似,TiDB 会保存该值。注意,如果未在系统变量@@sql_mode 中设置NO_AUTO_VALUE_ON_ZERO ,即使显式指定整型主键列的值为 0 ,TiDB 也会为该列⾃动分配值。

注意

  • ⾃动分配值的计算⽅式如下:
    该⾏值在⼆进制形式下,除去符号位的最⾼五位(称为 shard bits)由当前事务的开始时间决定,剩下的位数按照⾃增的顺序分配。

  • 分片数量:
    若要使⽤⼀个不同的 shard bits 的数量,可以在 AUTO_RANDOM 后⾯加⼀对括号,并在括号中指定想要的 shard bits 数量。示例如下:

CREATE TABLE t (a bigint PRIMARY KEY AUTO_RANDOM(3), b varchar(255))

以上建表语句中,shard bits 的数量为 3 。shard bits 的数量的取值范围是 [1,16) 。

创建完表后,使⽤ SHOW WARNINGS 可以查看当前表可⽀持的最⼤隐式分配的次数:

SHOW WARNINGS
+-------+------+---------------------------------------------------
-------+
| Level | Code | Message 
 |
+-------+------+---------------------------------------------------
-------+
| Note | 1105 | Available implicit allocation times:
1152921504606846976 |
+-------+------+---------------------------------------------------
-------+
  • 注意
    为保证可隐式分配的次数最⼤,AUTO_RANDOM 列类型只能为BIGINT 。
    另外,要查看某张含有 AUTO_RANDOM 属性的表的 shard bits 数量,可以在系统表information_schema.tables 中 TIDB_ROW_ID_SHARDING_INFO ⼀列看到模式为 PK_AUTO_RANDOM_BITS=x 的值,其中 x 为 shard bits 的数量。

last_insert_id()

AUTO RANDOM 列隐式分配的值会影响 last_insert_id() 。
可以使⽤ SELECT last_insert_id() 获取上⼀次 TiDB 隐式分配的 ID,例如:

INSERT INTO t (b) VALUES ("b")
SELECT * FROM t;
SELECT last_insert_id()

可能得到的结果如下:

+------------+---+
| a | b |
+------------+---+
| 1073741825 | b |
+------------+---+
+------------------+
| last_insert_id() |
+------------------+
| 1073741825 |
+------------------+

兼容性

TiDB ⽀持解析版本注释语法。示例如下:

CREATE TABLE t (a bigint PRIMARY KEY /*T![auto_rand] auto_random */)
CREATE TABLE t (a bigint PRIMARY KEY AUTO_RANDOM)

以上两个语句含义相同。
在 SHOW CREATE TABLE 的结果中, AUTO_RANDOM 属性会被注释掉。注释会附
带⼀个特性标识符,例如 /*T![auto_rand] auto_random */ 。其中auto_rand 表示 AUTO_RANDOM 的特性标识符,只有实现了该标识符对应特性的TiDB 版本才能够正常解析 SQL 语句⽚段。
该功能⽀持向前兼容,即降级兼容。没有实现对应特性的 TiDB 版本则会忽略表(带有上述注释)的 AUTO_RANDOM 属性,因此能够使⽤含有该属性的表。

示例

  • 新建表
# 新建表
DROP TABLE IF EXISTS test.auto_random_t1;
CREATE TABLE test.auto_random_t1 (
 id bigint PRIMARY KEY AUTO_RANDOM(3),
 name char(255));
  • 插入数据
## 插入数据
/* Populate Seed */
INSERT INTO test.auto_random_t1 (name) VALUES ('A');
INSERT INTO test.auto_random_t1 (name) VALUES ('B');
INSERT INTO test.auto_random_t1 (name) VALUES ('C');
INSERT INTO test.auto_random_t1 (name) VALUES ('D');
INSERT INTO test.auto_random_t1 (name) VALUES ('E');
INSERT INTO test.auto_random_t1 (name) VALUES ('F');
INSERT INTO test.auto_random_t1 (name) VALUES ('G');
INSERT INTO test.auto_random_t1 (name) VALUES ('H');
INSERT INTO test.auto_random_t1 (name) VALUES ('I');
INSERT INTO test.auto_random_t1 (name) VALUES ('J');
INSERT INTO test.auto_random_t1 (name) VALUES ('K');
INSERT INTO test.auto_random_t1 (name) VALUES ('L');
INSERT INTO test.auto_random_t1 (name) VALUES ('M');
INSERT INTO test.auto_random_t1 (name) VALUES ('N');
INSERT INTO test.auto_random_t1 (name) VALUES ('O');
INSERT INTO test.auto_random_t1 (name) VALUES ('P');
INSERT INTO test.auto_random_t1 (name) VALUES ('Q');
INSERT INTO test.auto_random_t1 (name) VALUES ('R');
INSERT INTO test.auto_random_t1 (name) VALUES ('S');
INSERT INTO test.auto_random_t1 (name) VALUES ('T');
INSERT INTO test.auto_random_t1 (name) VALUES ('U');
INSERT INTO test.auto_random_t1 (name) VALUES ('V');
INSERT INTO test.auto_random_t1 (name) VALUES ('W');
INSERT INTO test.auto_random_t1 (name) VALUES ('X');
INSERT INTO test.auto_random_t1 (name) VALUES ('Y');
INSERT INTO test.auto_random_t1 (name) VALUES ('Z');
INSERT INTO test.auto_random_t1 (name) VALUES ('a');
INSERT INTO test.auto_random_t1 (name) VALUES ('b');
INSERT INTO test.auto_random_t1 (name) VALUES ('c');
INSERT INTO test.auto_random_t1 (name) VALUES ('d');
INSERT INTO test.auto_random_t1 (name) VALUES ('e');
INSERT INTO test.auto_random_t1 (name) VALUES ('f');
INSERT INTO test.auto_random_t1 (name) VALUES ('g');
INSERT INTO test.auto_random_t1 (name) VALUES ('h');
INSERT INTO test.auto_random_t1 (name) VALUES ('i');
INSERT INTO test.auto_random_t1 (name) VALUES ('j');
INSERT INTO test.auto_random_t1 (name) VALUES ('k');
INSERT INTO test.auto_random_t1 (name) VALUES ('l');
INSERT INTO test.auto_random_t1 (name) VALUES ('m');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
INSERT INTO test.auto_random_t1 (name) VALUES ('x1');
  • 查看last_insert_id() ,当前分配的值
/* Check last_insert_id() */
SELECT LAST_INSERT_ID();
  • 查看这张表的分片值
/* Greetings to CBO */
ANALYZE TABLE test.auto_random_t1;
/* select 'test.auto_random_t1' as Title; */
/* desc test.auto_random_t1; */
select TIDB_ROW_ID_SHARDING_INFO, TIDB_PK_TYPE
from information_schema.tables
where table_schema='test'
and table_name='auto_random_t1';

mysql> select TIDB_ROW_ID_SHARDING_INFO, TIDB_PK_TYPE
    -> from information_schema.tables
    -> where table_schema='test'
    -> and table_name='auto_random_t1';
+---------------------------+--------------+
| TIDB_ROW_ID_SHARDING_INFO | TIDB_PK_TYPE |
+---------------------------+--------------+
| PK_AUTO_RANDOM_BITS=3     | CLUSTERED    |
+---------------------------+--------------+
1 row in set (0.17 sec)
  • 查看每个分片的汇总个数
/* check value */
# 虽然是随机的,但每个region当中的id还是有一定规律。
SELECT substr(cast(id as CHAR),1,2) as id_prefix, count(*) as
approx_rows_in_shard
FROM test.auto_random_t1
GROUP BY id_prefix
HAVING approx_rows_in_shard > 1
ORDER BY id_prefix;
/*SHOW TABLE test.auto_random_t1 REGIONS\G*/

# 公有16个分片,每个分片的汇总个数
mysql> SELECT substr(cast(id as CHAR),1,2) as id_prefix, count(*) as
    -> approx_rows_in_shard
    -> FROM test.auto_random_t1
    -> GROUP BY id_prefix
    -> HAVING approx_rows_in_shard > 1
    -> ORDER BY id_prefix;
+-----------+----------------------+
| id_prefix | approx_rows_in_shard |
+-----------+----------------------+
| 10        |                    2 |
| 11        |                   29 |
| 15        |                    3 |
| 16        |                    4 |
| 19        |                    2 |
| 23        |                   31 |
| 34        |                   32 |
| 46        |                   18 |
| 57        |                   26 |
| 69        |                   14 |
| 80        |                   22 |
+-----------+----------------------+
11 rows in set (0.28 sec)

猜你喜欢

转载自blog.csdn.net/wangzhicheng987/article/details/130782100