【问题标题】:How to remove millions of rows in MySQL?如何删除 MySQL 中的数百万行?
【发布时间】:2021-08-03 17:43:49
【问题描述】:

我有一张大桌子,我想把它变小。它有 ~2.3 亿 行。

两列都有索引。结构是:

+--------------+------------+
| id_my_value     | id_ref     |
+--------------+------------+
|         YYYY |       XXXX |
+--------------+------------+

我必须删除具有特定“id_ref”值的值。我尝试了以下方法:

sql = f"SELECT id_ref FROM REFS"
cursor.execute(sql)
refs = cursor.fetchall()
limit = 1000
for current in refs:
    id = current["id_ref"]
    sql = f"DELETE FROM MY_VALUES WHERE id_ref = {id} LIMIT {limit}" 
    while True:
      cursor.execute(sql)
      mydb.commit()
      if cursor.rowcount == 0:
        break

无论我设置为“限制”的值如何,查询都非常慢:

DELETE FROM MY_VALUES WHERE id_ref = XXXX LIMIT 10;

我也尝试过相反的方法。选择与特定 id_ref 关联的 id_value,然后删除:

SELECT id_value FROM MY_VALUES WHERE id_ref = XXXX LIMIT 10
DELETE FROM MY_VALUES WHERE id_value = YYYY

这是我的解释。

EXPLAIN DELETE FROM MY_VALUES WHERE id_ref = YYYY LIMIT 1000; 
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | 
+----+-------------+------------+------------+-------+---------------+------------+---------+-------+----------+----------+-------------+ 
| 1 | DELETE       | MY_VALUES   | NULL | range | id_ref | id_ref | 5 | const | 20647922 | 100.00 | Using where | 

它确实使用了正确的索引。

让这个操作在他的服务器上运行几天不会有任何问题。

  1. 进行这种“清洁”的正确方法是什么?

编辑

这是 SHOW CREATE TABLE MY_VALUES 的输出

MY_VALUES | CREATE TABLE `MY_VALUES` (
  `id_my_value` int NOT NULL AUTO_INCREMENT,
  `id_document` int NOT NULL,
  `id_ref` int DEFAULT NULL,
  `value` mediumtext CHARACTER SET utf8 COLLATE utf8_spanish_ci,
  `weigth` int DEFAULT NULL,
  `id_analysis` int DEFAULT NULL,
  `url` text CHARACTER SET utf8 COLLATE utf8_spanish_ci,
  `domain` varchar(64) CHARACTER SET utf8 COLLATE utf8_spanish_ci DEFAULT NULL,
  `filetype` varchar(16) CHARACTER SET utf8 COLLATE utf8_spanish_ci DEFAULT NULL,
  `id_domain` int DEFAULT NULL,
  `id_city` int DEFAULT NULL,
  `city_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_spanish_ci DEFAULT NULL,
  `is_hidden` tinyint NOT NULL DEFAULT '0',
  `id_company` int DEFAULT NULL,
  `is_hidden_by_user` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id_my_value`),
  KEY `id_ref` (`id_ref`),
  KEY `id_document` (`id_document`),
  KEY `id_analysis` (`id_analysis`),
  KEY `weigth` (`weigth`),
  KEY `id_domain` (`id_domain`),
  KEY `id_city` (`id_city`),
  KEY `id_company` (`id_company`),
  KEY `value` (`value`(15))

更新

我只是试图删除一个寄存器:

DELETE FROM MY_VALUES WHERE id_MY_VALUE = 8

该操作需要“永远”。为了防止超时,我关注了this SO question,所以我设置了:

show variables like 'innodb_lock_wait_timeout';
+--------------------------+--------+
| Variable_name            | Value  |
+--------------------------+--------+
| innodb_lock_wait_timeout | 100000 |
+--------------------------+--------+

【问题讨论】:

  • 您的查询不会运行,因为 VALUES 是一个保留字,如果您不关心它是否会持续数天,那么当删除查询运行速度极慢时,为什么会打扰您,无论这意味着什么
  • 向我们展示SHOW CREATE TABLE tablename 的输出。请edit您的问题。
  • @nbk,我已经编辑了这个问题以避免这种混淆。该列未命名为“VALUES”。我只是反映了它。
  • 好,这让我很恼火,你的问题看看,如果你的 qiery 使用 id_ref 上的索引,请使用 EYPLAIN 看看它是否被使用
  • 解释删除 id_ref = YYYY LIMIT 1000 的 MY_VALUES; |编号 |选择类型 |表|隔断 |类型 |可能的键 |关键 | key_len |参考 |行 |过滤 |额外 | +----+-------------+------------+------------+---- ---+---------------+------------+---------+------- +----------+----------+-------------+ | 1 |删除 | MY_VALUES |空 |范围 | id_ref | id_ref | 5 |常量 | 20647922 | 100.00 |使用位置 |它确实使用了正确的索引

标签: python mysql query-optimization


【解决方案1】:
a=0;
limit=1000;
while true
    b=a+1000
    sql = "delete from VALUES where id>{a} and id<={b}"
     cursor.execute(sql)
     mydb.commit()
     if cursor.rowcount == 0:
          break
     a=a+1000

【讨论】:

  • 请总是添加一些解释为什么这会更好
  • @Tom,这个答案假设“ids”是相关的,遗憾的是事实并非如此。身份证。是“随机”数字。
【解决方案2】:

首先要尝试。把它放在你的第二个cursor.execute() 之后。

cnx.commit()

在connector/python中,autocommit默认是关闭的。如果您不提交,您的 MySQL 服务器会缓冲您的所有更改(在您的情况下为删除),以便在您选择或程序崩溃时回滚它们。

我猜你的慢查询是

DELETE FROM `VALUES` WHERE id_ref=constant LIMIT 1000;

尝试这样做。 EXPLAIN 显示查询计划。

EXPLAIN DELETE FROM `VALUES` WHERE id_ref=constant LIMIT 1000;

它应该使用id_ref 行上的索引。您的索引可能没有足够的选择性,因此您的查询规划器选择了表扫描。在这种情况下,您可能会考虑提高 LIMIT,这样您的查询每次运行时都会做更多的工作。

你可以试试这个。如果我对表扫描的猜测是正确的,它可能会有所帮助。

DELETE FROM `VALUES` FORCE INDEX (your_index_on_id_ref) WHERE id_ref=constant LIMIT 1000;

(通常FORCE INDEX 是个糟糕的主意。但这个可能是个例外。)

你也可以试试这个:创建一个清理过的临时表,然后重命名表以将其投入使用。

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
CREATE TABLE purged_values AS 
SELECT * 
  FROM `VALUES`
 WHERE id_ref NOT IN (SELECT id_ref FROM `REFS`);

这需要一段时间。在零点三十点运行它。事务隔离级别有助于防止在此过程中与使用该表的其他会话发生争用。

然后您将拥有一个新的、已清除的表。您可以对其进行索引,然后进行这些重命名以将其投入使用。

ALTER TABLE `VALUES` RENAME TO old_values;
ALTER TABLE purged_values RENAME to `VALUES';

【讨论】:

  • 我已经用一个真实的例子运行了 EXPLAIN 命令。它采用正确的索引:| 1 |删除 |元值 |空 |范围 | id_ref | id_ref | 5 |常量 | 20647922 | 100.00 |使用位置 |
【解决方案3】:

最后我做了更多的实验,找到了一种方法。

第一步

删除数据库条目的 python 循环运行了约 12 小时。我添加了几行来测量执行时间:

      start = time.time()
      cursor.execute(sql)
      mydb.commit()
      end = time.time()

这是第一次测量的示例:

     1 > 900 > 0.4072246551513672
     2 > 900 > 1.7270898818969727
     3 > 900 > 1.8365845680236816
     4 > 900 > 1.124634027481079
     5 > 900 > 1.8552422523498535
     6 > 900 > 13.80513596534729
     7 > 900 > 8.379877090454102
     8 > 900 > 10.675175428390503
     9 > 900 > 6.14388370513916
     10 > 900 > 11.806004762649536
     11 > 900 > 12.884040117263794
     12 > 900 > 23.604055881500244
     13 > 900 > 19.162535905838013
     14 > 900 > 24.980825662612915
     ....

在 900 次迭代后,平均每次执行的时间约为 30 秒。附图供参考:

在我的情况下,这需要大约 80 天的时间来删除使用此实现的所有行。

最终解决方案

使用适当的值、索引等创建了一个临时表...

CREATE TABLE ZZ_MY_VALUES AS 
    SELECT * FROM ZZ_MY_VALUES WHERE ZZ_MY_VALUES.id_ref IN 
    (
        SELECT id_ref FROM MY_REFS WHERE id_ref = 3 OR id_ref = 4 OR id_ref = 5
    )

花了大约 3 小时,从 230M 行增加到 21M 行。 比原来的 3 个月统计要快一点。 :)

感谢大家的提示。

【讨论】:

    猜你喜欢
    • 2010-11-22
    • 2019-10-22
    • 2019-07-12
    • 2019-02-15
    • 1970-01-01
    • 1970-01-01
    • 2021-01-05
    • 2020-12-15
    • 2012-01-07
    相关资源
    最近更新 更多