MySQL:更新大表的最佳方法

时间:2016-10-25 16:00:21

标签: mysql query-optimization

我有一张包含大量数据的表格。数据来源是外部api。每隔几个小时,我需要同步数据库,以便从外部api更新更新。我正在进行完全同步(api不允许增量同步)。

同步发生时,我想确保数据库中的数据也可供读取。所以,我遵循以下步骤:

  1. 我在表中有一个cloumn,它作为数据是否可读的标志。只有标记设置的数据标记为已读。
  2. 我将api中的所有数据插入到表中。
  3. 写完所有数据后,我将删除表中设置了标志的所有数据。
  4. 删除后,我正在更新表并为所有行设置标志。
  5. 表有大约5000万行,预计会增长。表中有一个customerId字段。同步通常基于customerId将其传递给api。

    我的问题是,上面的第3步和第4步花了很多时间。查询类似于:

    步骤3 - > delete from foo where customer_id=12345678 and flag=1

    步骤4 - > update foo set flag=1 where customer_id=12345678

    我已尝试基于customer_id对表进行分区,并且在customer_id行数较少的情况下效果很好但对于某些customer_id,每个分区本身的行数大约为500万。

    大约90%的数据在两次同步之间不会发生变化。我怎样才能做到这一点?

    我在考虑只使用更新查询而不是插入查询,然后检查是否有任何更新。如果没有,我可以为同一行发出插入查询。这样,任何更新都将与插入一起处理。但我不确定该操作是否会在更新过程中阻止对此进行读取查询。

2 个答案:

答案 0 :(得分:0)

对于您的设置(只读数据,完全同步),更新表的最快方法是根本不更新,而是将数据导入另一个表并在之后重命名以使其成为新表。

像原始表格一样创建一个表格,例如使用

create table foo_import like foo;

如果您有例如触发器,也可以添加它们。

从现在开始,让导入api将其(完整)同步写入此新表。

同步完成后,交换两个表:

RENAME TABLE foo TO foo_tmp, 
    foo_import TO foo, 
    foo_tmp to foo_import;

它(确切地说)只需要一秒钟。

这个命令是原子的:它将等待访问这些表的事务完成,它不会出现没有表foo的情况,并且它将完全失败(并且不做任何事情),如果其中一个表格不存在或foo_tmp已存在。

最后一步,清空导入表(现在包含旧数据),为下次导入做好准备:

truncate foo_import;

这又需要一秒钟。

其余的查询可能会假设为flag=1。直到(如果有的话)您更新代码以不再使用该标志,您可以将其默认值设置为1以保持其兼容,例如使用

alter table foo modify column flag tinyint default 1;

由于你没有外键,它不必打扰你,但对于有类似问题的其他人来说,知道外键会被调整可能会有用,所以外键引用{{1重命名表后将引用foo。为了使它们再次指向新表foo_import,必须删除它们并重新创建它们。其他所有内容(例如视图,查询,程序)都将按当前名称解析,因此他们将始终访问当前的foo

答案 1 :(得分:0)

CREATE TABLE new LIKE real;
Load `new` by whatever means you have; take as long as needed.
RENAME TABLE real TO old, new TO real;
DROP TABLE old;

RENAME是原子的,"瞬时&#34 ;; real始终是"可用。

(我不认为需要flag。)

或...

由于您实际上正在更新表的一部分,请考虑这些......

如果块很小......

  1. 将新数据加载到tmp表
  2. 删除旧行
  3. INSERT ... SELECT ...移动新行。(将新数据放在表中可能是实现此目的的最快方法。)
  4. 如果大块很大,并且你不想锁定表太长",还有其他一些技巧。但首先,客户的每一行都有某种形式的唯一行号吗? (我考虑一次批量移动一堆或多行,但在拼写之前需要更多细节。)