检查带有 SELECT 的 INSERT 在 PyMySQL 中是否成功

Check if an INSERT with a SELECT was successfull in PyMySQL

我有一个 INSERT 查询,它从 SELECT 语句中获取值。但是由于 SELECT returns 数百万条记录,它给 MySQL 服务器带来了太多负载。因此,我们决定将 SELECT 查询分解为多个部分并通过 LIMIT 子句执行。

INSERT INTO target_table 
    SELECT * FROM source_table
    WHERE my_condition = value
    ...
    LIMIT <start>, <end>

我们将不断增加开始值和结束值,直到 SELECT returns 0 行。我也在考虑让这个多线程。

如何使用 PyMySQL?

我需要执行SELECT,得到结果然后生成INSERT吗?

首先,回答你的问题:在 PyMySQL 中,你得到的值是 cursor.execute:

的结果
execute(query, args=None)

Execute a query

Parameters:   
    query (str) – Query to execute.
    args (tuple, list or dict) – parameters used with query. (optional)

Returns: Number of affected rows

因此您可以重复执行查询,直到您得到的值小于您选择的范围。

总之,请考虑:

  • 首先你应该检查你是否可以优化你的select(假设它不像你的例子那么简单),例如通过添加索引。您可能还想测试仅选择和实际插入之间的区别,以大致了解哪个部分更相关。
  • 如果插入是导致问题的原因,则可能是事务的大小所致。在那种情况下,如果您也可以拆分事务,则将其拆分只会减少问题(尽管由于您考虑并行执行查询,这似乎不是问题)
  • 如果查询产生太多 (cpu) 负载,运行并行该查询的多个实例最多只能将其分布在多个内核上,这实际上会减少其他查询可用 cpu 时间。如果 "load" 与 I/O-load、有限资源的影响或 "general responsiveness" 相关,则有可能,例如一个小查询可能会在内存中生成一个小临时 table,而大查询可能会在磁盘上生成一个大临时 table(尽管特别是 offset,这不太可能,请参见下文。)否则,您通常需要在连续 运行 的(足够小的)部分之间添加小的停顿,以将相同的工作负载分散到更长的时间。
  • limit 只有在你有一个 order by 时才有意义(可能是主键),否则,在连续的 运行 中,第 m 行可以是与之前不同的行(因为顺序不固定)。这可能会或可能不会增加负载(和资源需求),具体取决于您的索引和您的 where 条件。
  • 对源 table 的更新也是如此,就好像您从结果集中添加或删除一行(例如,更改第一行 my_condition 的值),所有连续偏移量会发生变化,您可能会跳过一行或两次获得一行。您可能需要锁定行,这可能会阻止 运行 并行查询(因为它们锁定相同的行),并且如果您可以拆分事务,也可能会影响决策(请参阅第二个要点)。
  • 使用 offset 需要 MySQL 先查找然后跳过行。因此,如果将查询拆分为 n 部分,则第一行将需要处理 n 次(最后一行通常需要处理一次),因此总工作量(用于选择)将增加 (n^2-n)/2。因此,特别是如果选择行是最相关的部分(请参阅第一个要点),这实际上会使您的情况变得更糟:仅最后 运行 将需要找到与当前查询相同数量的行(尽管它扔掉了大部分),甚至可能需要更多资源,具体取决于 order by 的效果。

您可以通过在条件中使用主键来解决一些 offset 问题,例如有一个包含这样的循环:

select max(id) as new_max from 
where id > last_id and <your condition>  
order by id limit 1000  -- no offset!

如果new_maxnull则退出循环,否则执行插入:

insert ... select ... 
where id > last_id and id <= new_max and <your condition>

然后设置last_id = new_max,继续循环。

它使查询数量加倍,与 limitoffset 相比,您需要知道实际的 id。它仍然需要您的主键和您的 where-条件兼容(因此您可能需要添加适合的索引)。如果您的搜索条件找到很大比例(超过约 15% 或 20%)的来源 table,无论如何使用主键可能是最好的执行计划。

如果你想并行化这个(取决于你的交易要求,如果它可能是值得的,见上文),你可以首先获得主键的最大值 (select max(id) as max_id from ...) ,并给每个线程一个工作范围。例如。对于 max_id=3000 和 3 个线程,使用 (0..1000), (1001, 2000), (2001..3000) 之一启动它们并将其包含到第一个查询中:

select max(id) as new_max from 
where id > last_id 
  and id >= $threadmin_id and id <= $threadmax_id
  and <your condition>  
order by id limit 1000 

如果这些范围大小相等,则可能取决于您的数据分布(并且您可能会在您的情况下找到更好的范围;尽管计算确切范围需要执行查询,因此您可能无法做到准确) .