检查带有 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_max
为null
则退出循环,否则执行插入:
insert ... select ...
where id > last_id and id <= new_max and <your condition>
然后设置last_id = new_max
,继续循环。
它使查询数量加倍,与 limit
和 offset
相比,您需要知道实际的 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
如果这些范围大小相等,则可能取决于您的数据分布(并且您可能会在您的情况下找到更好的范围;尽管计算确切范围需要执行查询,因此您可能无法做到准确) .
我有一个 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_max
为null
则退出循环,否则执行插入:
insert ... select ...
where id > last_id and id <= new_max and <your condition>
然后设置last_id = new_max
,继续循环。
它使查询数量加倍,与 limit
和 offset
相比,您需要知道实际的 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
如果这些范围大小相等,则可能取决于您的数据分布(并且您可能会在您的情况下找到更好的范围;尽管计算确切范围需要执行查询,因此您可能无法做到准确) .