将Mysql中的2000万条数据分批。请建议一种更快的方法?
Posted
技术标签:
【中文标题】将Mysql中的2000万条数据分批。请建议一种更快的方法?【英文标题】:Divide 20 million data in Mysql to batches. Please suggest a faster approach? 【发布时间】:2021-12-20 00:10:06 【问题描述】:我有一个数据库表 user_data,总行数为 2000 万行。它基本上是用户的地址数据。一个用户可以有多个地址。 user_id 为字符串格式。
我需要将 2000 万条数据分成 10 个批次,并针对每一行更新相应的 batch_no。具有相同 user_id 的用户应该在同一批次中。
为此,我使用 php 脚本和更新连接查询(用于连接的字段为 varchar 格式)。现在更新 2000 万行大约需要 60-70 分钟。 user_id 列的类型为 varchar(255) 并已编入索引。
我们将不胜感激任何有助于加快流程的帮助。
$query = "SELECT COUNT(DISTINCT user_id) from user_data WHERE set=1";
$stmt = $this->db->prepare($query);
$stmt->execute([':set'=> $this->set]);
$totalUserCount = $stmt->fetchColumn();
$limit = intval($totalUserCount/10);
$lastRecords = $totalUserCount%10;
$limit = $lastRecords > 0 ? $limit + 1 : $limit;
$lastOffset = false;
for($i = 0 ; $i < 10 ; $i++)
$offset = $limit * $i;
if($lastOffset)
$offset = ($limit * $i) + $lastRecords;
$query = "UPDATE user_data t1 INNER JOIN (SELECT distinct user_id FROM user_data
WHERE set=1 LIMIT :offset, :limit) AS t2
ON (t1.user_id = t2.user_id AND t1.set =1)
SET batch_no=:batch_no";
$stmt = $this->db->prepare($query);
$batchNo = ($i+1);
$stmt->bindParam(':batch_no',$batchNo,PDO::PARAM_INT);
$stmt->bindParam(':set',1,PDO::PARAM_STR);
$stmt->bindParam(':offset',$offset,PDO::PARAM_INT);
$stmt->bindParam(':limit',$limit,PDO::PARAM_INT);
$stmt->execute();
if($lastRecords==($i+1))
$limit--;
$lastOffset = true;
表结构和样本数据
--
-- Table structure for table `user_data`
--
CREATE TABLE `user_data` (
`id` int(11) NOT NULL,
`user_id` varchar(255) NOT NULL,
`address_1` varchar(255) NOT NULL,
`address_2` varchar(255) NOT NULL,
`set_no` int(11) NOT NULL,
`batch_no` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `user_data`
--
ALTER TABLE `user_data`
ADD PRIMARY KEY (`id`),
ADD KEY `idx_user_id` (`user_id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `user_data`
--
ALTER TABLE `user_data`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;
id(int) | user_id(varchar(255) | address_1 | address_2 | set_no | batch_no |
---|---|---|---|---|---|
1 | ABCDEFGH001 | street 12 | north avenue | 1 | 1 |
2 | ABCDEFGH001 | street 13 | north avenue | 1 | 1 |
3 | ABCDEFGH001 | street 14 | north avenue | 1 | 1 |
4 | GDJFDHFH004 | lane 13 | south avenue | 1 | 1 |
5 | HHSYEEEY002 | DF-01 | alabama | 1 | 2 |
6 | HHSYEEEY002 | GH-15 | central alabama | 1 | 2 |
7 | TETYEJEE056 | AKSH - 56 | north carolina | 1 | 2 |
【问题讨论】:
你在乎用什么来分割行吗?表的PRIMARY KEY
是什么?您是否需要使用batch_no
添加一个新列,还是要创建 10 个表或 10 个文件?你将如何处理每批?
@RickJames,主列是“id”。抱歉,之前没有正确更新。需要考虑 user_id 列以吐出批次。是的,需要有一个新的列'batch_no',需要用batch_no更新,这样每个batch可以从同一个表中单独提取batch_no进行处理。
batch_no
会被多次使用吗?如果没有,在创建 csv 文件时简单地计算 batch_no 会快得多。也就是说,UPDATE
非常昂贵,尤其是如果它不会被多次使用。
@RickJames,我只需要创建一个脚本,在最佳时间将完整数据分成多个批次。还有另一个系统将通过 batch_no 获取数据并进行处理。请让我知道是否可以采取任何措施来提高性能。
链接中的ORDER BY id LIMIT 1000,1
技巧是高效分块的主要技巧。它返回的 1 id
为一个块提供了一个边界。重复此操作以获取所有块边界。如果您想进一步讨论,请提出一个关于分块的新问题。
【参考方案1】:
这确实是 cmets - 但那里的空间有限。
您在谈论数据库性能,但没有提供表/索引结构的详细信息,也没有解释计划。
我需要将 2000 万条数据分成 10 个批次,并针对每一行更新相应的 batch_no
这听起来像是你只会做一次的事情 - 那么为什么要求让它更快呢?
您为什么要将数据分成 10 个批次?这些批次将用于什么用途?
我在您的代码中没有看到您为 $batchno
赋值的任何地方
这看起来像是 XY 问题。
假设 user_data.migration_batch 是一个标称值,并且 user_id 被实现为一个整数序列而没有重复间隙,您可以通过简单地运行更快地获得相同的结果(但结果本身具有非常可疑的值):
UPDATE user_data
SET migration_batch=MOD(user_id, 10);
(但请注意,这并不能解决当您实际开始使用批次号时会出现的性能问题)。
鉴于在 user_id 和 migration_batch 之间创建了简单的功能映射......您甚至需要费心将 migration_batch 存储在数据库中吗?
【讨论】:
symcbean,抱歉,这不是 migration_batch。这是batch_no。我已经纠正了。要求是将数据分成 10 个批次,并将其导出为 10 个 csv 文件。 batch_no 在这里设置 "$batchNo = ($i+1);"在代码中。此查询中的 MOD 函数“UPDATE user_data SET migration_batch=MOD(user_id, 10);”仅适用于整数、双精度等。这里 user_id 类型是字符串类型。 "并将其导出为 10 个 csv 文件" - 在 Linux/POSIX 系统上,mysqldump 和 split 将处理此问题(但日期格式和 blob 的潜在问题) 基于批次的数据导出由其他系统完成,它将基于batch_no获取数据。我必须把它分成10批。但是将其分成批次需要花费大量时间。我必须减少花费的时间。以上是关于将Mysql中的2000万条数据分批。请建议一种更快的方法?的主要内容,如果未能解决你的问题,请参考以下文章