【问题标题】:What do you think of this approach for logging changes in mysql and have some kind of audit trail您如何看待这种在 mysql 中记录更改并具有某种审计跟踪的方法
【发布时间】:2018-02-09 05:24:51
【问题描述】:

我现在已经阅读了几个主题,并做了一些关于将更改记录到 mysql 表的研究。先说明一下我的情况:

我有一个带有 table: 'ticket'

的票务系统

到目前为止,我已经创建了 triggers,它会在我的 table: 'ticket_history' 中输入一个重复的条目,其中有 "action" "user""timestamp" 作为附加列。经过几个星期的测试,我对这个构建有点不满意,因为每次更改都会在历史表中创建我的行的完整副本。我确实知道磁盘空间很便宜,我不应该担心它,但是为了检索某种日志或用户好看的历史记录是痛苦的,至少对我来说是这样。同样使用我编写的触发器,即使没有变化,我也会在历史记录中获得新的一行。但这只是我触发器的设计缺陷!

这是我的触发器:

BEFORE UPDATE ON ticket FOR EACH ROW
BEGIN
INSERT INTO ticket_history
SET
    idticket = NEW.idticket,
    time_arrival = NEW.time_arrival,
    idticket_status = NEW.idticket_status,
    tmp_user = NEW.tmp_user,
    action = 'update',
    timestamp = NOW();
END

我的新方法是为了避免触发

在这个话题上花了一些时间后,我想出了一个我想讨论和实施的方法。但首先我会对此有一些疑问:

我的想法是创建一个新表:

    id   sql_fwd        sql_bwd      keys      values    user       timestamp
    -------------------------------------------------------------------------
    1    UPDATE...      UPDATE...    status    5         14          12345678
    2    UPDATE...      UPDATE...    status    4         7           12345678

流程在我看来是这样的:

起初我会从数据库中选择一些或更多:

SELECT keys FROM ticket;

然后我在 2 个输入字段中显示数据:

<input name="key" value="value" /> <input type="hidden" name="key" value="value" />

点击提交并将其交给我的函数:

我会再次从 SELECT 开始:SELECT * FROM ticket; 并确保隐藏的输入字段 == 来自最新选择的值。如果是这样,我可以继续并知道同时没有其他用户更改了某些内容。如果隐藏字段不匹配,我将用户带回表单并显示一条消息。

接下来,我将为该操作构建 SQL 查询以及撤消这些更改的查询。

$sql_fwd = "UPDATE ticket 
            SET idticket_status = 1
            WHERE idticket = '".$c_get['id']."';";

$sql_bwd = "UPDATE ticket 
            SET idticket_status = 0
            WHERE idticket = '".$c_get['id']."';";

我在 ticket 上运行 UPDATE 并在我的新表中插入一个新条目以进行日志记录。

这样我可以尝试在两个用户同时编辑同一张票时捕获可能的覆盖,对于我的历史记录,我可以简单地查找键和值并生成某种列表。还有 SQL_BWD 我可以简单地撤消更改。

我的问题是:

  • 每次我想更新某些内容时进行额外的选择会不会很明显?
  • 我会失去一些使用触发器的好处吗?
  • 有什么大的缺点吗
  • 我的 mysql 服务器或 php 上是否有任何功能已经完成了类似的操作?
  • 或者是否有更简单的方法来做类似的事情
  • 我现在对触发器的微小改动是否已经足够了?
  • 如果我理解这一点,MySQL 仅在值已更改但触发器仍然执行时才执行更新,对吗?
  • 如果我能够更改触发器,我是否仍然可以在 2 个用户尝试在 mysql 服务器上同时编辑票证时以某种方式防止数据被覆盖,还是我会使用 PHP 来执行此操作?

谢谢你的帮助

【问题讨论】:

  • 我认为允许两个用户同时编辑同一张票是一个坏主意。实施一些外部锁定机制(以及超时或强制接管)来阻止第二个用户。
  • 我想过这一点,但不确定如何实施这种做法。例如,如果工作人员当前正在处理工单,假设正在添加与工单相关的新评论或消息,但同时客户致电并希望提高优先级。我试图对将要编辑和更新的数据进行分组,到目前为止,应该可以同时执行这两个操作。我的想法是存储一个时间戳并将票锁定 1 分钟左右,但如果我这样做并且某些票被锁定,因为电话开始响起并且噩梦开始

标签: php mysql database audit-trail


【解决方案1】:

我之前回答过similar 的问题。您会在该问题中看到一些不错的选择。

在您的情况下,我认为您正在合并几个问题 - 一个是“存储审计跟踪”,另一个是“管理许多客户可能想要更新单行的情况”。

首先,我不喜欢触发器。它们是其他一些操作的副作用,对于非平凡的情况,它们使调试变得更加困难。设计不佳的触发器或审计表确实会减慢您的应用程序的速度,您必须确保触发器逻辑在许多开发人员之间进行协调。我知道这是个人偏好和偏见。

其次,根据我的经验,要求很少是“随着时间的推移显示这个表的状态” - 它几乎总是“让我看到随着时间的推移系统发生了什么”,如果这个要求存在的话,它通常是相当高的优先级。例如,对于票务系统,您可能需要创建和更改票证状态的用户的姓名和电子邮件地址;类别/分类的名称,也许是项目的名称等。所有这些属性都可能是其他表的外键。当确实发生需要审计的事情时,要求可能是“让我立即查看”,而不是“让数据库开发人员花费数小时尝试从 8 个不同的历史表中拼凑出图片。在票务系统中,这可能是一个要求让工单详细信息屏幕显示此内容。

如果这一切都是真的,那么我认为由触发器填充的历史表不是一个好主意 - 您必须将所有业务逻辑构建到两组代码中,一组用于显示“常规”应用程序,另一组用于显示“常规”应用程序显示“审计跟踪”。

相反,您可能希望在数据模型中构建“时间”(这是我对另一个问题的回答的重点)。

从那时起,一种新的数据架构风格出现了,称为CQRS。这需要一种非常不同的方式来看待应用程序设计,但它是专门为reactive 应用程序设计的;这些提供了很多更好的方法来处理“如果有人在当前用户完成表单时编辑记录会发生什么”问题。 Stack Overflow 就是一个例子——我们可以在输入 cmets 或答案时看到问题是否已更新,或者是否发布了其他答案或 cmets。有一个reactive library for PHP

【讨论】:

  • 鉴于您的回答和相关链接的深度,这就是我想要的。像 CQRS 之类的东西听起来很有趣,我现在将研究所有这些,但现在对我的应用程序来说似乎有点矫枉过正。它让我知道从哪里开始以及如何在我的情况下进行。谢谢!很可能我会使用 VALID FROM, UNTIL 以稍微改变的方式,这应该与其他答案结合起来给我正是我想要的。远离触发器已经是一种解脱了
【解决方案2】:

我确实知道磁盘空间很便宜,我不应该担心它,但是为了检索某种日志或漂亮的历史记录对用户来说是很痛苦的,至少对我来说是这样。

大型历史表不一定是问题。巨大的表只使用磁盘空间,这很便宜。只有在对它们进行查询时,它们才会减慢速度。幸运的是,历史记录并不是你一直使用的东西,很可能它只是用来解决问题或审计。

对历史表进行分区很有用,例如按月或按周。这使您可以简单地删除非常旧的记录,更重要的是,由于前几个月的历史已经备份,您的每日备份计划只需要备份当月。这意味着庞大的历史记录表不会减慢您的备份速度。

这样我可以在两个用户同时编辑同一张票时尝试捕捉可能的覆盖

有一个简单的解决方案:

添加一列“version_number”。

当您选择意图修改时,您会获取此版本号。

然后,当用户提交新数据时,您可以:

UPDATE ... 
SET all modified columns,
    version_number=version_number+1 
WHERE ticket_id=...
    AND version_number = (the value you got)

如果有人介入并对其进行了修改,那么他们将增加版本号,因此 WHERE 将找不到该行。该查询将返回 0 的行数。因此您知道它已被修改。然后,您可以选择它、比较值并向用户提供冲突解决选项。

您还可以添加最后修改者、修改时间等列,并​​将此信息呈现给用户。

如果您希望打开修改页面的用户锁定其他用户,也可以这样做,但这需要超时(例如,以防他们打开窗口并回家)。所以这更复杂。

现在,关于历史:

您不想拥有一个名为“cmets”的大型 TEXT 列,每个人都可以在其中输入内容,因为每次有人添加一个字母时,都需要将它复制到历史记录中。

最好像论坛一样查看它:每张票就像一个主题,可以有一串 cmets(如帖子),存储在另一个表中,其中包含有关谁编写它、何时编写等信息。您也可以将其历史化。

使用触发器的缺点是触发器不知道登录的用户,只知道 MySQL 用户。因此,如果您想记录谁做了什么,您必须添加一个带有 user_id 的列,正如我上面建议的那样。您也可以使用 Rick James 的解决方案。两者都可以。

请记住,虽然 MySQL 触发器不会在外键级联删除时触发......所以如果以这种方式删除行,它将不起作用。在这种情况下,在应用程序中执行此操作会更好。

【讨论】:

    【解决方案3】:

    另一种方法...

    当工人开始做出改变时……

    1. 将时间和worker_id存储在行中。
    2. 继续执行任务。
    3. 当worker结束时,获取最后一个触及记录的worker_id;如果是他自己,一切都很好。清除时间和worker_id。

    另一方面,如果另一个工人溜进来,那么就需要一些解决方案。这进入了您的概念,即某些事情可以并行进行。

    • 评论可以添加到不同的表格中,因此不会发生冲突。
    • 更改优先级可能不是问题本身
    • 其他事情可能会更混乱。

    最好为 time & worker_ids (& ticket_id) 设置另一个表。这将允许标记多个工作人员当前正在处理单个记录。

    至于历史与当前,我(通常)喜欢有两张桌子:

    • 历史 - 详细列出更改的内容、时间和人员。这张表只有INSERTed 进入。
    • Current -- 工单的当前状态。这张表主要是UPDATEd

    另外,我更喜欢直接从应用程序的“数据库层”编写历史记录,而不是通过触发器。这使我可以更好地控制每个表中的内容和时间的详细信息。再加上“交易”是明确的。这让我有信心让两张表保持同步:

    BEGIN; INSERT INTO History...; UPDATE Current...; COMMIT;
    

    【讨论】:

      猜你喜欢
      • 2023-03-04
      • 2015-08-25
      • 2013-10-25
      • 2013-03-17
      • 2012-06-18
      • 2010-10-17
      • 2020-12-22
      • 2017-07-30
      • 2015-03-16
      相关资源
      最近更新 更多