有关将更新和插入查询合并到mysql中的单个查询的查询


9

我想跟踪用户的更改历史记录,以便每当他更改个人资料时,我都需要获取旧数据并存储在历史记录中,并使用新数据进行更新。

我可以使用select获取旧数据,使用insert历史记录,最后使用update更改数据。

我可以在不使用存储过程,触发器等的情况下,在mysql的单个查询中拥有所有这些功能吗?例如使用锁等。


1
@savaranan:这个问题值得+1,因为它确实向DBA和开发人员强烈提示使用事务并充分利用数据库的ACID属性。
RolandoMySQLDBA 2011年

2
@savaranan:出于所有意图和目的,Jack提供了唯一可行的答案。实际上,杰克·道格拉斯采取了另一步骤,并通过执行SELECT ... FOR UPDATE强制对id = 10的每一行进行间歇锁定,以增加MVCC保护。他的回答进一步强调了Jack和我一直在说的重点:UPDATE和INSERT不可能,也永远不会是单个查询,对于您提出的SQL行为,它们只能是单个事务。
RolandoMySQLDBA 2011年

Answers:


13

要做到这一点,而不阻塞另一个用户尝试更新在同一时间相同的配置文件的风险,你需要锁定该行t1第一,然后使用一个交易(如罗兰在评论你的问题指出):

start transaction;
select id from t1 where id=10 for update;
insert into t2 select * from t1 where id=10;
update t1 set id = 11 where id=10;
commit;

在进一步锁定id = 10的每一行时,这非常出色。那应该是+2。我只能给+1 !!!
RolandoMySQLDBA 2011年

1

我不认为有一种方法可以将所有三个语句组合在一起。最接近的东西并没有真正帮助您,这就是SET SELECT。您最好的选择是触发。下面是一个触发器示例,我经常使用它来维护这种审计跟踪(由PHP构造):

$trigger = "-- audit trigger --\nDELIMITER $ \n".
    "DROP TRIGGER IF EXISTS `{$prefix}_Audit_Trigger`$\n".
    "CREATE TRIGGER `{$prefix}_Audit_Trigger` AFTER UPDATE ON `$this->_table_name` FOR EACH ROW BEGIN\n";

foreach ($field_defs as $field_name => $field) {
    if ($field_name != $id_name) {
       $trigger .= "IF (NOT OLD.$field_name <=> NEW.$field_name) THEN \n".'INSERT INTO AUDIT_LOG ('.
                    'Table_Name, Row_ID, Field_Name, Old_Value, New_Value, modified_by, DB_User) VALUES'.
                    "\n ('$this->_table_name',OLD.$this->_id_name,'$field_name',OLD.$field_name,NEW.$field_name,".
                    "NEW.modified_by, USER()); END IF;\n";
    }
}
$trigger .= 'END$'."\n".'DELIMITER ;';

-3

我发现此查询适用于SQL和MySQL服务器 INSERT INTO t2 SELECT * FROM t1 WHERE id=10; UPDATE t1 SET id=11 WHERE id=10;

希望这对将来的其他人也有用。


4
这不是一个真正的查询。这实际上是两个查询,应将其视为事务。
RolandoMySQLDBA 2011年

@rolandomysqldba:当我从应用程序代码发送到db服务器时,它可以作为单个查询很好地工作,在这里我将此集视为单个查询。你为什么这么说?。您能以有力的理由来证明这一点吗?
Saravanan

2
@saravanan:在InnoDB或任何符合ACID的RDBMS(Oracle,SQLServer,PostreSQL,Sybase等)的眼中,不可能将这两个SQL语句称为一个查询。作为符合ACID的数据库会将它们视为两个语句。默认情况下,InnoDB的自动提交功能已打开。第一条语句INSERT将作为单个事务执行。将生成多版本并发控制(MVCC)数据,以逐行将原始数据的副本保留在t2表中。如果在执行INSERT时MySQL崩溃,则InnoDB使用MVCC数据将t2回滚到其原始状态。
RolandoMySQLDBA 2011年

1
@saravanan:假设INSERT成功地工作了。INSERT产生的数据已提交(自动提交为ON),并且MVCC保护表t2被丢弃。当执行UPDATE时,将针对t1表生成MVCC并执行UPDATE。如果MySQL在UPDATE期间崩溃,则InnoDB使用t1上的MVCC数据回滚UPDATE。即使UPDATE仅更改了一行,也存在将记录从t1移到id为10的t2而不在t1中将id 10更改为id 11的可能性。为避免这种独特的情况,您需要执行以下操作……
RolandoMySQLDBA 2011年

@savaranan:将两个SQL语句视为一个事务。简单的方法是:BEGIN; 插入到t2 SELECT * FROM t1 id = 10; 更新t1 SET id = 11 WHERE id = 10; 承诺; 将两个SQL语句视为一个事务的最重要原因是,为INSERT创建的MVCC在UPDATE期间将仍然存在。如果在事务内部的UPDATE(更新)期间发生MySQL崩溃(BEGIN; ... COMMIT;块),则MVCC会将所有更改回滚到一致状态。如果INSERT和UPDATE均完成,则MVCC在最后一刻被丢弃。
RolandoMySQLDBA 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.