【问题标题】:Check if an INSERT with a SELECT was successfull in PyMySQL检查带有 SELECT 的 INSERT 在 MySQL 中是否成功
【发布时间】:2019-01-29 16:06:39
【问题描述】:

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

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

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

我怎样才能用 PyMySQL 做到这一点?

是否需要执行SELECT,获取结果然后生成INSERT

【问题讨论】:

    标签: python mysql pymysql


    【解决方案1】:

    首先,回答你的问题:在 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 时间。如果“负载”与 I/O 负载、有限资源的影响或“一般响应性”有关,则有可能,例如一个小的查询可能会在内存中生成一个小的临时表,而大查询会在磁盘上生成一个大的临时表(尽管特别是 offset,这不太可能,见下文。)否则,您通常需要在 (足够小)连续运行的部分,以便在更长的时间内分散相同的工作负载。
    • limit 仅在您有 order by(可能通过主键)时才有意义,否则,在连续运行中,m-th 行可能与以前不同(因为顺序不固定)。这可能会也可能不会增加负载(和资源需求),具体取决于您的索引和where-条件。
    • 对源表的更新也是如此,就像您从结果集中添加或删除一行(例如更改第一行的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-condition 兼容(因此您可能需要添加适合的索引)。如果您的搜索条件在您的源表中找到了相当大的百分比(超过大约 15% 或 20%),那么无论如何使用主键可能是最好的执行计划。

    如果你想并行化这个(取决于你的事务要求,如果它可能有价值,见上文),你可以首先获得主键的最大值 (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 
    

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

    【讨论】:

    • 非常感谢您的描述性回答!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-27
    • 1970-01-01
    • 1970-01-01
    • 2011-02-21
    • 1970-01-01
    • 2012-05-11
    • 1970-01-01
    相关资源
    最近更新 更多