【问题标题】:Optimal way to insert records in MySQL master-detail table pair在 MySQL 主从表对中插入记录的最佳方法
【发布时间】:2023-03-26 07:52:02
【问题描述】:

我的项目中有一个单元用于执行用户之间的消息交换。为此,我使用了两个 MySQL 表:

CREATE TABLE `tmessages` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `header` varchar(100) DEFAULT NULL,
  `body` text,
  ...
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

那是主人。它包含有关发件人、标头、消息正文等的信息和

CREATE TABLE `tmessage_recipients` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `idmessage` int(10) unsigned NOT NULL DEFAULT '0',
  `idstuff_recipient` int(10) unsigned NOT NULL DEFAULT '0',
  ...,
  PRIMARY KEY (`id`,`idmessage`,`idstuff_recipient`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

作为详细信息,包括消息发送到的用户、打开消息的日期/时间等(其余信息由触发器设置)以下代码正常工作并发送消息。

var
  s: string;
  idmessage: longword;
  i: Word;
...    
      s := 'INSERT INTO `tmessages` SET ' + '`header`=' + QuotedStr(Edit12.Text) +
        ', ' + '`body`=' + QuotedStr(Memo3.Text);
      Hdm1.FDConnection1.ExecSQL(s);
      idmessage := Hdm1.FDConnection1.ExecSQLScalar('SELECT `id` FROM `tmessages` '
        + 'WHERE `header`=' + QuotedStr(Edit12.Text) + 'AND `body`=' +
        QuotedStr(Memo3.Text) + ' ORDER BY `id` DESC LIMIT 1');
      for i := 0 to ListBox3.Count - 1 do
      begin
        s := 'INSERT INTO `tmessage_recipients` SET ' + '`idmessage`=' +
          inttostr(idmessage) + ', ' + '`idstuff_recipient`=' +
          inttostr(ListBox3.ItemByIndex(i).Tag);
        Hdm1.FDConnection1.ExecSQL(s);
      end;

但我认为这不是最优的,因为我必须先获取 idmessage。如何优化程序并使其更可靠?可以在Hdm1.FDConnection1.ExecSQL() 的一次调用中完成插入吗?

编辑

使用 @Alex Tartan 和 @whosrdaddy 的 cmets,我得到了这样的结果。这是最佳方式吗?还是我需要改变一些东西?

  s:= 'INSERT INTO `tmessages` (`header`,`body`) VALUES(:p1, :p2);'#13#10 +
    'SET @last_id := (SELECT LAST_INSERT_ID());';
  for i := 0 to ListBox3.Count - 1 do
    s := s + #13#10'INSERT INTO `tmessage_recipients` (`idmessage`,`idstuff_recipient`)'
      + ' VALUES(@last_id,' + inttostr(ListBox3.ItemByIndex(i).Tag) + ');';
  Hdm1.FDConnection1.ExecSQL(s, [Edit12.Text, Memo3.Text]);

编辑 2

在@kobik 的提议下,我得出以下结论:

  s:= 'INSERT INTO `tmessages` (`header`,`body`) VALUES(:p1, :p2);'#13#10 +
    'SET @last_id := (SELECT LAST_INSERT_ID());';
  for i := 0 to ListBox3.Count - 1 do
    s := s + #13#10'INSERT INTO `tmessage_recipients` (`idmessage`,`idstuff_recipient`)'
      + ' VALUES(@last_id,' + inttostr(ListBox3.ItemByIndex(i).Tag) + ');';

  HDM1.FDQViaFDTransaction.Transaction.StartTransaction;
  try
    HDM1.FDQViaFDTransaction.ExecSQL(s, [Edit12.Text, Memo3.Text]);
    HDM1.FDQViaFDTransaction.Transaction.Commit;
  except
    HDM1.FDQViaFDTransaction.Transaction.Rollback;
  end;

编辑 3

在@Rick-James 的宝贵建议下,代码被转换为以下...

  s := 'INSERT INTO `tmessages` (`header`,`body`) VALUES(:p1, :p2);'#13#10 +
    'SET @last_id := (SELECT LAST_INSERT_ID());';
  s := s + #13#10'INSERT INTO `tmessage_recipients` ' +
    '(`idmessage`,`idstuff_recipient`)'#13#10'VALUES';
  for i := 0 to ListBox3.Count - 1 do
    s := s + #13#10'(@last_id,' + inttostr(ListBox3.ItemByIndex(i).Tag) + '),';
  s[High(s)] := ';';

  Hdm1.FDQViaFDTransaction.Transaction.StartTransaction;
  try
    Hdm1.FDQViaFDTransaction.ExecSQL(s, [Edit12.Text, Memo3.Text]);
    Hdm1.FDQViaFDTransaction.Transaction.Commit;
    ShowMessage('Message sent.');
    Button10Click(self);
  except
    Hdm1.FDQViaFDTransaction.Transaction.Rollback;
  end;

【问题讨论】:

  • 请停止使用串联查询,您很容易受到 SQL 注入的攻击。改用参数...
  • 更好地使用事务
  • 谢谢@whosrdaddy,我进行了编辑。您认为我需要更改其他内容吗?
  • 谢谢你,@kobik。您是指一些中间层(如DataSnap)还是从FDQuery 调用查询?
  • 您需要一个事务来保持数据的完整性。查看TFDTransaction 或使用mysql START TRANSACTION/COMMIT... 另外,如果您有新问题,请发布新问题。

标签: mysql delphi query-optimization master-detail


【解决方案1】:

为了提高效率,请使用多行 INSERT:

INSERT INTO table2 
    (id, x)
    VALUES
    (@last_id,"newVal_1"),
    (@last_id,"newVal_2"),
    (@last_id,"newVal_3");

【讨论】:

  • 谢谢你,Rick James,我已经编辑了我的帖子。不幸的是我不能接受一个以上的答案,我只能upwote。
  • 谢谢。在某些情况下,多行可提供 10 倍的加速。
【解决方案2】:

使用 mysql 变量:

INSERT INTO table1 (id,value) VALUES(1,'test');
SET @last_id := (SELECT LAST_INSERT_ID());
INSERT INTO table2 (id,colName) VALUES(@last_id,"newVal_1");
...
INSERT INTO table2 (id,colName) VALUES(@last_id,"newVal_n");

这样你只需要一次调用服务器

【讨论】:

  • 谢谢。我不知道 LAST_INSERT_ID() 函数!它是否返回表中的第一个键字段或名为 id 的字段?
  • 不。它返回设置了自动增量的列。 dev.mysql.com/doc/refman/5.0/en/getting-unique-id.html
  • 再次感谢您。我编辑了我的问题,包括您的回答和@whosrdaddy 的评论。我需要更改其他内容吗?
猜你喜欢
  • 1970-01-01
  • 2011-06-03
  • 1970-01-01
  • 2016-10-14
  • 1970-01-01
  • 1970-01-01
  • 2022-08-23
  • 2016-01-22
  • 2015-12-31
相关资源
最近更新 更多