web-dev-qa-db-ja.com

監査(履歴)テーブルとトリガーの作成を自動化するMySQLスクリプト?

規制や詐欺の要件については、DBのほとんどのテーブルに対するすべての変更を記録する必要があります。以前のプロジェクトでこれを非常にうまく行った方法は、各テーブルのコピーを作成することです。

  1. 4つの追加列:DateChanged、UserId、Action(Create、Update、delete)、およびIP
  2. Id pkは一意ではなくなりました。
  3. 他のすべての制約(FK、一意のインデックスなど)が削除されました。
  4. 監査テーブルは別の監査スキーマにあります

前のジョブで、Oracle DBAは、これらすべてを自動的に生成するスクリプトを作成し、次のことを行いました。

  1. 存在しない場合、監査と呼ばれる新しいスキーマを作成しました
  2. 通常のスキーマの各テーブルtを反復処理します:
    1. 接頭辞a_を除いて同じテーブル名で監査スキーマに新しいテーブルを作成しました。 a_t
    2. 元のテーブルと同じ列をすべて追加し、さらに4つの列(DateChanged、UserId、IPアドレス、アクション)を追加しました。
    3. 元のテーブルにトリガーを生成して追加します(まだそこにない場合)。
    4. 更新する場合、対応するa_テーブルに新しい行を書き込み、すべての列に更新前の(古い)値を含む「更新」のアクションを使用します(メインテーブルには新しい値が含まれます)
    5. 削除する場合は、メインテーブルの行のコピーである行を追加しますが、action = "Delete"です。
    6. 挿入する場合は、行を監査スキーマに追加します

注意:

  1. すべてのテーブルにはID PKがあります。
  2. いくつかのテーブルは、パフォーマンスのため、または必要ないために除外する必要があります(例:トリガーによって更新されるバランステーブル)

このシステムの優れた点は、何がいつ変更されたか、または誰が何を変更したかを照会し、変更の前後のレコードを確認できることです。変更は、個々の列レベルではなく、DB行レベルで行われます。

MYSQL 5.6でこのようなものに遭遇した人はいますか?私たちのチームには、このようなものを最初から作成できるDBAがいませんが、同様のものを変更するのに十分な知識があります。

5
John Little

今日これを書いたばかりです。これは、information_schemaデータベースで機能するselectステートメントであり、監査テーブルとトリガーのスキーマを生成します。

SET GLOBAL group_concat_max_len = 1000;

SET @dbName = "[[[your_db_name_here]]]";

SELECT concat("DROP TABLE IF EXISTS `", @dbName, "`.`", table_data.audit_table, "`;\r",
          "CREATE TABLE `", @dbName, "`.`", table_data.audit_table, "`\r",
          "(\r",
          "  `auditAction` ENUM ('INSERT', 'UPDATE', 'DELETE'),\r",
          "  `auditTimestamp` DATETIME DEFAULT CURRENT_TIMESTAMP,\r",
          "  `auditId` INT(14) AUTO_INCREMENT,",
          column_defs, ",\r"
          "  PRIMARY KEY (`auditId`),\r",
          "  INDEX (`auditTimestamp`)\r",
          ")\r",
          "  ENGINE = InnoDB;\r\r",
          "DROP TRIGGER IF EXISTS `", @dbName, "`.`", table_data.insert_trigger, "`;\r",
          "CREATE TRIGGER `", @dbName, "`.`", table_data.insert_trigger, "`\r",
          "  AFTER INSERT ON `", @dbName, "`.`", table_data.db_table, "`\r",
          "  FOR EACH ROW INSERT INTO `", @dbName, "`.`", table_data.audit_table, "`\r",
          "     (`auditAction`,", table_data.column_names, ")\r",
          "  VALUES\r",
          "     ('INSERT',", table_data.NEWcolumn_names, ");\r\r",
          "DROP TRIGGER IF EXISTS `", @dbName, "`.`", table_data.update_trigger, "`;\r",
          "CREATE TRIGGER `", @dbName, "`.`", table_data.update_trigger, "`\r",
          "  AFTER UPDATE ON `", @dbName, "`.`", table_data.db_table, "`\r",
          "  FOR EACH ROW INSERT INTO `", @dbName, "`.`", table_data.audit_table, "`\r",
          "     (`auditAction`,", table_data.column_names, ")\r",
          "  VALUES\r",
          "     ('UPDATE',", table_data.NEWcolumn_names, ");\r\r",
          "DROP TRIGGER IF EXISTS `", @dbName, "`.`", table_data.delete_trigger, "`;\r",
          "CREATE TRIGGER `", @dbName, "`.`", table_data.delete_trigger, "`\r",
          "  AFTER DELETE ON `", @dbName, "`.`", table_data.db_table, "`\r",
          "  FOR EACH ROW INSERT INTO `", @dbName, "`.`", table_data.audit_table, "`\r",
          "     (`auditAction`,", table_data.column_names, ")\r",
          "  VALUES\r",
          "     ('DELETE',", table_data.OLDcolumn_names, ");\r\r"
)
FROM (
   # This select builds a derived table of table names with ordered and grouped column information in different
   # formats as needed for audit table definitions and trigger definitions.
   SELECT
     table_order_key,
     table_name                                                                      AS db_table,
     concat("audit_", table_name)                                                    AS audit_table,
     concat(table_name, "_inserts")                                                  AS insert_trigger,
     concat(table_name, "_updates")                                                  AS update_trigger,
     concat(table_name, "_deletes")                                                  AS delete_trigger,
     group_concat("\r  `", column_name, "` ", column_type ORDER BY column_order_key) AS column_defs,
     group_concat("`", column_name, "`" ORDER BY column_order_key)                   AS column_names,
     group_concat("`NEW.", column_name, "`" ORDER BY column_order_key)               AS NEWcolumn_names,
     group_concat("`OLD.", column_name, "`" ORDER BY column_order_key)               AS OLDcolumn_names
   FROM
     (
       # This select builds a derived table of table names, column names and column types for
       # non-audit tables of the specified db, along with ordering keys for later order by.
       # The ordering must be done outside this select, as tables (including derived tables)
       # are by definition unordered.
       # We're only ordering so that the generated audit schema maintains a resemblance to the
       # main schema.
       SELECT
         information_schema.tables.table_name        AS table_name,
         information_schema.columns.column_name      AS column_name,
         information_schema.columns.column_type      AS column_type,
         information_schema.tables.create_time       AS table_order_key,
         information_schema.columns.ordinal_position AS column_order_key
       FROM information_schema.tables
         JOIN information_schema.columns
           ON information_schema.tables.table_name = information_schema.columns.table_name
       WHERE information_schema.tables.table_schema = @dbName
             AND information_schema.columns.table_schema = @dbName
             AND information_schema.tables.table_name NOT LIKE "audit\_%"
     ) table_column_ordering_info
   GROUP BY table_name
 ) table_data
ORDER BY table_order_key
INTO OUTFILE "[[[your_output_file]]]"
5
vanyo

Vanyoの回答 ほぼうまくいきました。 NEWおよびOLD参照でバックティックを移動すると、問題ありませんでした(`NEW.column1`の代わりにNEW.`column1`を取得します)。

group_concat("NEW.`", column_name, "`" ORDER BY column_order_key)               AS NEWcolumn_names,
group_concat("OLD.`", column_name, "`" ORDER BY column_order_key)               AS OLDcolumn_names
1
Matt2012