将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万条数据分批。请建议一种更快的方法?的主要内容,如果未能解决你的问题,请参考以下文章

为什么MySQL单表不能超过2000万行?

Python - 寻找一种更有效的方法来重新编写字典中的键

GraphQL:一种更高效强大和灵活的数据提供方式

2000万条记录的SQL 库有多大?

如何为 5500 万条记录批量更新 postgres 中的单个列

php+mysql实现数据分批插入