MySQL 对 VARCHAR(60) 进行分区
MySQL Partitioning a VARCHAR(60)
我有一个非常大的 5 亿行 table,其中包含以下列:
id
- Bigint
- 自动递增主索引。
date
- Datetime
- 每个日期大约 150 万行,超过 1 年的数据将被删除。
uid
- VARCHAR(60)
- 用户 ID
sessionNumber
- INT
start
- INT
- 开始时间的纪元。
end
- INT
- 结束时间的纪元。
- 更多列与此查询不相关。
uid
和 sessionNumber
的组合形成一个唯一索引。我还有一个日期索引。
由于体积庞大,我想对 table 进行分区。
我的大部分访问都是按日期进行的,因此按日期范围分区似乎很直观,但由于日期不是唯一索引的一部分,所以这不是一个选项。
选项 1:RANGE PARTITION
日期和 BEFORE INSERT TRIGGER
我真的没有遇到 uid
和 sessionNumber
唯一性被侵犯的常规问题。源数据是一致的,但是跨越两天的会话可以连续两天插入,午夜是第一个的结束时间和第二个的开始时间。
我正在尝试了解是否可以删除唯一键并使用触发器
- 检查前一天是否有具有相同标识符的会话,如果有,
- 更新结束日期。
- 取消实际插入。
但是,我不确定我是否可以 1) 在同一个 table 上触发更新。或 2) 防止实际插入。
选项 2:LINEAR HASH PARTITION
UID
我的第二个选择是在 UID 上使用线性散列分区。但是,我看不到任何使用 VARCHAR 并将其转换为用于 HASH
分区的 INTEGER 的示例。
但是我找不到从 VARCHAR 转换为 INTEGER 的允许方法。例如
ALTER TABLE mytable
PARTITION BY HASH (CAST(md5(uid) AS UNSIGNED integer))
PARTITIONS 20
returns 不允许分区函数。
HASH 分区必须使用 32 位整数。但是您不能简单地使用 CAST()
.
将 MD5 字符串转换为整数
代替 MD5,CRC32()
可以采用任意字符串并转换为 32 位整数。但这也不是一个有效的分区函数。
mysql> alter table v partition by hash(crc32(uid));
ERROR 1564 (HY000): This partition function is not allowed
您可以使用 KEY Partitioning 而不是 HASH 分区按字符串进行分区。 KEY 分区接受字符串。它通过 MySQL 的内置 PASSWORD() 函数传递任何输入字符串,这基本上与 SHA1 有关。
但是,这会导致您的分区策略出现另一个问题:
mysql> alter table v partition by key(uid);
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function
您的 table 的主键 id
不包含您要作为分区依据的列 uid
。这是 restriction of MySQL's partitioning:
every unique key on the table must use every column in the table's partitioning expression.
这是我正在测试的table(如果你把它包含在你的问题中会是个好主意):
CREATE TABLE `v` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`date` datetime NOT NULL,
`uid` varchar(60) NOT NULL,
`sessionNumber` int(11) NOT NULL,
`start` int(11) NOT NULL,
`end` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uid` (`uid`,`sessionNumber`),
KEY `date` (`date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
在继续之前,我想知道你为什么要使用分区? “庞大的规模”不是划分 table.
的理由
与任何优化一样,分区是为了您要优化的特定查询而完成的。任何优化都会以牺牲其他查询为代价来改进一个查询。优化与table无关。 table 很高兴坐在那里有 50 亿行,它不在乎。优化是针对 查询 .
所以您需要知道您要针对哪些 查询进行优化。然后决定一个策略。分区可能不是您需要优化的查询集的最佳策略!
我假设您的 'uid' 是一种 128 位 UUID 类型的值,它可以存储为 BINARY(16),因为这通常是值得的。
接下来,远离 'datetime' 类型,因为它像压缩字符串一样存储,并且不包含任何时区信息。将 date-time-values 存储为纯数值(自 UNIX-epoch 以来的秒数),或者让 MySQL 为您执行此操作并使用 timestamp(N) 类型。
也不要调用列 'date',不仅因为它是保留字,而且因为该值也包含时间详细信息。
接下来,远离使用 latin1 以外的任何其他字符作为您的(所有)table 的字符集。只在列级别执行 UTF-8-ness。这是为了防止不必要的 byte-wide 列和索引随着时间的推移逐渐出现。养成这个习惯,几年后你会很高兴地回头看,我保证。
这使得 table 看起来像:
CREATE TABLE `v` (
`uuid` binary(16) NOT NULL,
`mysql_created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`visitor_uuid` BINARY(16) NOT NULL,
`sessionNumber` int NOT NULL,
`start` int NOT NULL,
`end` int NOT NULL,
PRIMARY KEY (`uuid`),
UNIQUE KEY (`visitor_uuid`,`sessionNumber`),
KEY (`mysql_created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
PARTITIONED BY RANGE COLUMNS (`uuid`)
( PARTITION `p_0` VALUES LESS THAN (X'10')
, PARTITION `p_1` VALUES LESS THAN (X'20')
...
, PARTITION `p_9` VALUES LESS THAN (X'A0')
, PARTITION `p_A` VALUES LESS THAN (X'B0')
...
, PARTITION `p_F` VALUES LESS THAN (MAXVALUE)
);
要让KEY (mysql_created_at)
只在date-part上,需要一个计算列,可以加in-place,然后上面的索引也轻加,所以我会把它留作作业。
我有一个非常大的 5 亿行 table,其中包含以下列:
id
-Bigint
- 自动递增主索引。date
-Datetime
- 每个日期大约 150 万行,超过 1 年的数据将被删除。uid
-VARCHAR(60)
- 用户 IDsessionNumber
-INT
start
-INT
- 开始时间的纪元。end
-INT
- 结束时间的纪元。- 更多列与此查询不相关。
uid
和 sessionNumber
的组合形成一个唯一索引。我还有一个日期索引。
由于体积庞大,我想对 table 进行分区。
我的大部分访问都是按日期进行的,因此按日期范围分区似乎很直观,但由于日期不是唯一索引的一部分,所以这不是一个选项。
选项 1:RANGE PARTITION
日期和 BEFORE INSERT TRIGGER
我真的没有遇到 uid
和 sessionNumber
唯一性被侵犯的常规问题。源数据是一致的,但是跨越两天的会话可以连续两天插入,午夜是第一个的结束时间和第二个的开始时间。
我正在尝试了解是否可以删除唯一键并使用触发器
- 检查前一天是否有具有相同标识符的会话,如果有,
- 更新结束日期。
- 取消实际插入。
但是,我不确定我是否可以 1) 在同一个 table 上触发更新。或 2) 防止实际插入。
选项 2:LINEAR HASH PARTITION
UID
我的第二个选择是在 UID 上使用线性散列分区。但是,我看不到任何使用 VARCHAR 并将其转换为用于 HASH
分区的 INTEGER 的示例。
但是我找不到从 VARCHAR 转换为 INTEGER 的允许方法。例如
ALTER TABLE mytable
PARTITION BY HASH (CAST(md5(uid) AS UNSIGNED integer))
PARTITIONS 20
returns 不允许分区函数。
HASH 分区必须使用 32 位整数。但是您不能简单地使用 CAST()
.
代替 MD5,CRC32()
可以采用任意字符串并转换为 32 位整数。但这也不是一个有效的分区函数。
mysql> alter table v partition by hash(crc32(uid));
ERROR 1564 (HY000): This partition function is not allowed
您可以使用 KEY Partitioning 而不是 HASH 分区按字符串进行分区。 KEY 分区接受字符串。它通过 MySQL 的内置 PASSWORD() 函数传递任何输入字符串,这基本上与 SHA1 有关。
但是,这会导致您的分区策略出现另一个问题:
mysql> alter table v partition by key(uid);
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function
您的 table 的主键 id
不包含您要作为分区依据的列 uid
。这是 restriction of MySQL's partitioning:
every unique key on the table must use every column in the table's partitioning expression.
这是我正在测试的table(如果你把它包含在你的问题中会是个好主意):
CREATE TABLE `v` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`date` datetime NOT NULL,
`uid` varchar(60) NOT NULL,
`sessionNumber` int(11) NOT NULL,
`start` int(11) NOT NULL,
`end` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uid` (`uid`,`sessionNumber`),
KEY `date` (`date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
在继续之前,我想知道你为什么要使用分区? “庞大的规模”不是划分 table.
的理由与任何优化一样,分区是为了您要优化的特定查询而完成的。任何优化都会以牺牲其他查询为代价来改进一个查询。优化与table无关。 table 很高兴坐在那里有 50 亿行,它不在乎。优化是针对 查询 .
所以您需要知道您要针对哪些 查询进行优化。然后决定一个策略。分区可能不是您需要优化的查询集的最佳策略!
我假设您的 'uid' 是一种 128 位 UUID 类型的值,它可以存储为 BINARY(16),因为这通常是值得的。
接下来,远离 'datetime' 类型,因为它像压缩字符串一样存储,并且不包含任何时区信息。将 date-time-values 存储为纯数值(自 UNIX-epoch 以来的秒数),或者让 MySQL 为您执行此操作并使用 timestamp(N) 类型。 也不要调用列 'date',不仅因为它是保留字,而且因为该值也包含时间详细信息。
接下来,远离使用 latin1 以外的任何其他字符作为您的(所有)table 的字符集。只在列级别执行 UTF-8-ness。这是为了防止不必要的 byte-wide 列和索引随着时间的推移逐渐出现。养成这个习惯,几年后你会很高兴地回头看,我保证。
这使得 table 看起来像:
CREATE TABLE `v` (
`uuid` binary(16) NOT NULL,
`mysql_created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`visitor_uuid` BINARY(16) NOT NULL,
`sessionNumber` int NOT NULL,
`start` int NOT NULL,
`end` int NOT NULL,
PRIMARY KEY (`uuid`),
UNIQUE KEY (`visitor_uuid`,`sessionNumber`),
KEY (`mysql_created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
PARTITIONED BY RANGE COLUMNS (`uuid`)
( PARTITION `p_0` VALUES LESS THAN (X'10')
, PARTITION `p_1` VALUES LESS THAN (X'20')
...
, PARTITION `p_9` VALUES LESS THAN (X'A0')
, PARTITION `p_A` VALUES LESS THAN (X'B0')
...
, PARTITION `p_F` VALUES LESS THAN (MAXVALUE)
);
要让KEY (mysql_created_at)
只在date-part上,需要一个计算列,可以加in-place,然后上面的索引也轻加,所以我会把它留作作业。