【问题标题】:Why are INSERTS into my MySQL table taking so long为什么在我的 MySQL 表中插入需要这么长时间
【发布时间】:2015-03-05 12:42:43
【问题描述】:

我正在构建一个 MySQL 帐户数据库。我有一个从 json 文件导入整个数据库的例程。该例程在单个事务中运行, 删除每个表中的所有记录,调用:

ALTER TABLE <tablename> AUTO_INCREMENT = 1

在每个表上,然后使用 INSERT 语句从数据中加载表,例如

INSERT INTO Journal (idJournal, DocumentId, AccountId, Memo, JournalNum,
 Amount, Outstanding, Cleared, NameAddressId) 
 VALUES (11423, 4454, 14, 'Deposit', 1, 53.33, 53.33, 'X', 292)

(注意表格是按正确的顺序加载的,所以相关记录已经存在)。

我发现插入语句需要很长时间(有些长达 70 毫秒),尤其是与从 mysqldump 加载相同数据相比。

我可以做些什么来加快速度(除了使用 mysqldump 代替,我不想这样做,因为我希望备份数据为 json 格式)?

表定义:

  CREATE TABLE IF NOT EXISTS `accounts`.`Journal` (
    `idJournal` INT NOT NULL AUTO_INCREMENT,
    `DocumentId` INT NOT NULL,
    `AccountId` INT NOT NULL,
    `Memo` VARCHAR(45) NULL,
    `JournalNum` INT NOT NULL,
    `Amount` DECIMAL(10,2) NOT NULL,
    `Outstanding` DECIMAL(10,2) NOT NULL DEFAULT 0,
    `Cleared` CHAR(1) NOT NULL,
    `NameAddressId` INT NULL,
    PRIMARY KEY (`idJournal`),
    INDEX `fk_Journal_Document1_idx` (`DocumentId` ASC),
    INDEX `fk_Journal_Account1_idx` (`AccountId` ASC),
    UNIQUE INDEX `Document_Num` (`DocumentId` ASC, `JournalNum` ASC),
    INDEX `fk_Journal_NameAddress1_idx` (`NameAddressId` ASC),
    CONSTRAINT `fk_Journal_Document1`
   FOREIGN KEY (`DocumentId`)
   REFERENCES `accounts`.`Document` (`idDocument`)
   ON DELETE NO ACTION
   ON UPDATE NO ACTION,
    CONSTRAINT `fk_Journal_Account1`
   FOREIGN KEY (`AccountId`)
   REFERENCES `accounts`.`Account` (`idAccount`)
   ON DELETE NO ACTION
   ON UPDATE NO ACTION,
    CONSTRAINT `fk_Journal_NameAddress1`
   FOREIGN KEY (`NameAddressId`)
   REFERENCES `accounts`.`NameAddress` (`idNameAddress`)
   ON DELETE NO ACTION
   ON UPDATE NO ACTION)
  ENGINE = InnoDB

【问题讨论】:

  • 所以你对json文件的每一行发出单独的插入查询?
  • 我不禁想知道为什么有人会想到尝试这样的任务。 MySQL 有很多现成的backup strategies——为什么要自己动手?!
  • 因为我不严格备份,更多允许导出导入数据为json格式。

标签: mysql optimization


【解决方案1】:

在执行多次插入时,在单个语句中插入多行会更高效,尤其是当您有很多二级索引时:

INSERT INTO Journal (idJournal, DocumentId, AccountId, Memo, JournalNum,
  Amount, Outstanding, Cleared, NameAddressId) 
VALUES (11423, 4454, 14, 'Deposit', 1, 53.33, 53.33, 'X', 292),
       (11424, 4455, 15, 'Deposit', 1, 23.33, 23.33, 'X', 293),
       ...

这样,索引只更新一次(在语句末尾),而不是在每次插入之后。

如果您的数据库超过 1GB,您将遇到问题,因为您将达到 max_allowed_packet 的限制。在这种情况下,您可能必须将其分解为多个查询。

当您说“删除所有记录”时,我希望您的意思是删除表或截断表,而不是实际删除所有记录。

【讨论】:

  • 存在问题 - MySql 文档说如果有外键(有),则 TRUNCATE TABLE 一次删除一行。至于多次插入,这不允许在某些记录中而不是其他记录中可能缺少的字段。
  • 您可以命名 INSERT 中的所有列,并根据需要使用NULLDEFAULT 作为值。很高兴您正在阅读文档并发现有比使用 DELETE 清除表格更多的方法。
  • 如果我中途回滚事务会发生什么(例如由于输入数据无效)?一切都会像以前一样,还是 TRUNCATE 9 或 DROP,如果我使用它)不是交易友好的?关于 TRUNCATE 的文档在这一点上有点不清楚。
  • 您不能回滚 DROP 或 TRUNCATE,因为它们之前和之后都会执行 implicit commit
  • 好的,所以它回到了我删除所有记录的原始代码。 ALTER TABLE 怎么样 - 交易友好吗?
【解决方案2】:

这适用于“不”中断表的使用:

  1. CREATE TABLE new LIKE real;
  2. 通过批量 INSERT(或其他方式)加载 new
  3. RENAME TABLE real TO old, new TO real; -- 瞬时和原子
  4. DROP TABLE old;

没有 TRUNCATE,没有 AUTO_INCREMENT=1

【讨论】:

  • 听起来很有趣!如果我在处理了一些表而不是其他表之后回滚事务会发生什么?数据库会处于与开始时相同的状态吗?
  • 这很重要。我省略了一些细节:在第 2 步附近添加BEGIN ... COMMIT,加上检查错误的代码和ROLLBACK(而不是COMMIT)在适当的时候。请注意,这样的事务不能包括CREATERENAMEDROP,因此只有第2 步可以有begin...commit。
  • 我想我可以将所有数据加载到并行表中,提交,然后执行一次 RENAME 将它们全部重命名,然后进行一系列删除。我必须在开始时放置一些 DROP IF EXIST 语句,以防在删除完成之前断电。顺便说一句,如果在“瞬时”重命名发生时断电会发生什么?
  • 注意不要在一个 BEGIN..COMMIT 中进行大量活动。由于您有一个回退(通过重新加载),因此加载期间几乎没有任何事务完整性。
  • 是的,在重命名期间可能会出现问题。它很苗条,我怀疑周围会留下 tmp 表。重新运行加载脚本可能会遇到额外/缺失的表,需要手动修复。所有表都可以(应该)在一个 RENAME 中重命名。你现在拥有的是容易受到电源故障的影响。
猜你喜欢
  • 2012-05-11
  • 1970-01-01
  • 2011-12-07
  • 1970-01-01
  • 2016-05-29
  • 2013-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多