在实体上存储更改:MySQL是合适的解决方案吗?

在实体上存储更改:MySQL是合适的解决方案吗?,mysql,database-design,entity-attribute-value,database-partitioning,temporal-database,Mysql,Database Design,Entity Attribute Value,Database Partitioning,Temporal Database,我想将所做的更改存储在“实体”表中。这应该像一个日志。目前在MySQL中使用此表实现: CREATE TABLE `entitychange` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `entity_id` int(10) unsigned NOT NULL, `entitytype` enum('STRING_1','STRING_2','SOMEBOOL','SOMEDOUBLE','SOMETIMESTAMP') NOT

我想将所做的更改存储在“实体”表中。这应该像一个日志。目前在MySQL中使用此表实现:

CREATE TABLE `entitychange` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `entity_id` int(10) unsigned NOT NULL,
  `entitytype` enum('STRING_1','STRING_2','SOMEBOOL','SOMEDOUBLE','SOMETIMESTAMP') NOT NULL DEFAULT 'STRING_1',
  `when` TIMESTAMP NOT NULL,
  `value` TEXT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
  • entity\u id
    =我的
    entity
    表的主键
  • entitytype
    =在
    实体
    表中更改的字段。有时只更改一个字段,有时更改多个字段。一次更改=一行
  • value
    =字段“新值”的字符串表示形式

示例:将Field
entity.somedouble
从3更改为2时,我运行以下查询:

UPDATE entity SET somedouble = 2 WHERE entity_id = 123;
INSERT INTO entitychange (entity_id,entitytype,value) VALUES (123,'SOMEDOUBLE',2);

我需要
选择过去15天特定实体和entitytype的更改。例如:在过去15天内,实体id
123
的最后一次更改为
SOMEDOUBLE

现在,有两件事我不喜欢:

  • 所有数据都存储为
    文本
    ——虽然大多数(小于1%)不是真正的文本,但在我的例子中,大多数值都是
    双值
    。这是个大问题吗
  • 插入时,表的速度变得非常非常慢,因为表已经有2亿行了。因此,目前我的服务器负载高达10-15

  • 我的问题:如何解决这两个“瓶颈”?我需要缩放。

    我的做法是:

  • 按如下方式存储:(单击浏览)-将更改存储在
    entitychange
    表中,然后根据其数据类型将值存储在
    entitychange[bool | timestamp | double | string]
  • 使用
    散列分区(实体id)
    ——我想到了大约50个分区
  • 我应该使用另一个数据库系统,也许是MongoDB吗

  • 现在我想我明白你们需要什么了,一个记录历史发生变化的可版本表。这可能是实现相同目标的另一种方法,您可以轻松地进行一些快速测试,以查看它是否比当前解决方案提供更好的性能。这是Symfony PHP框架在Doctrine中使用可版本化插件的方式。
    请记住,有两个键的主键唯一索引,版本和fk_实体。
    还可以查看保存的值。您将在未更改的字段中保存0值,在更改的字段中保存更改的值

    CREATE TABLE `entity_versionable` (
      `version` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
      `fk_entity` INT(10) UNSIGNED NOT NULL,
      `str1` VARCHAR(255),
      `str2` VARCHAR(255),
      `bool1` BOOLEAN,
      `double1` DOUBLE,
      `date` TIMESTAMP NOT NULL,
      PRIMARY KEY (`version`,`fk_entity`)
    ) ENGINE=INNODB DEFAULT CHARSET=latin1;
    
    
    INSERT INTO `entity_versionable` (fk_entity, str1, str2, bool1, double1, DATE)
    VALUES ("1", "a1", "0", "0", "0", "2013-06-02 17:13:16");
    INSERT INTO `entity_versionable` (fk_entity, str1, str2, bool1, double1, DATE)
    VALUES ("1", "a2", "0", "0", "0", "2013-06-11 17:13:12");
    INSERT INTO `entity_versionable` (fk_entity, str1, str2, bool1, double1, DATE)
    VALUES ("1", "0", "b1", "0", "0", "2013-06-11 17:13:21");
    INSERT INTO `entity_versionable` (fk_entity, str1, str2, bool1, double1, DATE)
    VALUES ("1", "0", "b2", "0", "0", "2013-06-11 17:13:42");
    INSERT INTO `entity_versionable` (fk_entity, str1, str2, bool1, double1, DATE)
    VALUES ("1", "0", "0", "1", "0", "2013-06-16 17:19:31");
    
    /*Another example*/
    INSERT INTO `entity_versionable` (fk_entity, str1, str2, bool1, double1, DATE)
    VALUES ("1", "a1", "b1", "0", "0", CURRENT_TIMESTAMP);
    
    
    SELECT * FROM `entity_versionable` t WHERE 
    (
        (t.`fk_entity`="1") AND 
        (t.`date` >= (CURDATE() - INTERVAL 15 DAY))
    );
    


    可能是提高性能的另一个步骤,可以是将所有历史日志记录保存在单独的表中,每月一次左右。这样,每个表中就不会有很多记录,而且按日期搜索会非常快

    这里有两个主要挑战:

  • 如何高效地存储数据,即占用更少的空间和易于使用的格式
  • 2-3。管理一个大表:归档、易于备份和恢复

    2-3。性能优化:更快的插入和选择

    高效存储数据

  • 字段。我建议使用
    VARCHAR(N)
    。 原因:


    • 使用N如果我面对您提到的问题,我会像下面那样设计日志表:

    • EntityName
      :(字符串)正在操作的实体。(必填)
    • ObjectId
      :正在操作的实体,主键
    • 字段名
      :(字符串)实体字段名
    • OldValue
      :(字符串)实体字段旧值
    • NewValue
      :(字符串)实体字段新值
    • UserCode
      :应用程序用户唯一标识符。(强制性)
    • TransactionCode
      :任何更改实体的操作都需要具有唯一的事务代码(如GUID)(必填),
      如果实体的更新更改了多个字段,则这些列将是跟踪更新(transaction)中所有更改的关键点
    • ChangeDate
      :交易日期。(强制性)
    • FieldType
      :枚举或文本显示字段类型,如text或Double。(强制性)
    • 使用这种方法
      可以跟踪任何实体(表)
      报告将是可读的
      只记录更改。
      事务代码将是通过单个操作检测更改的关键。

      顺便说一句

      不需要,在单个表中,您将有更改和数据类型

      Use partitioning by HASH(entity_id)
      
      我更喜欢按ChangeDate分区,或者为ChangeDate创建备份表,这些表足够旧,可以备份并从主日志表中删除

      Should I use another database system, maybe MongoDB?
      
      任何数据库都有其自身的优缺点,您可以在任何RDBMS上使用该设计。 MongoDB等基于文档的数据库的有用比较


      希望对您有所帮助。

      在工作中,由于客户情况(金融部门),我们几乎在每个表上都有日志表

      我们是这样做的:两个表(“正常”表和日志表),然后在插入/更新/删除正常表时触发,正常表存储关键字(I,U,D)和旧记录(更新时,删除)或新记录(插入时)在日志表中

      我们在同一个数据库模式中有两个表,这被称为a,研究人员20多年来一直在努力寻找存储和查询时态数据的最佳方法

      尝试在执行时存储EAV数据效率低下,因为在文本列中存储数字数据会占用大量空间,而且正如您所发现的,您的表越来越长

      另一个选项有时被称为第六范式(尽管6NF有多个不相关的定义),它是存储一个额外的表来存储要临时跟踪的每个列的修订。这类似于@xtrm的答案提出的解决方案,但它不需要存储未更改列的冗余副本。但它确实导致了表格数量的爆炸

      我已经开始阅读,它承诺处理结构和内容的时间变化。但我不知道
      Should I use another database system, maybe MongoDB?
      
      CREATE TABLE entitychange_somestring (
          entity_id INT NOT NULL PRIMARY KEY,
          ts TIMESTAMP NOT NULL,
          newvalue VARCHAR(50) NOT NULL, -- same type as entity.somestring
          KEY(entity_id, ts)
      ) ENGINE=MyISAM;
      
      CREATE TABLE `entity_versionable` (
        `version` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
        `fk_entity` INT(10) UNSIGNED NOT NULL,
        `str1` VARCHAR(255) DEFAULT NULL,
        `str2` VARCHAR(255) DEFAULT NULL,
        `bool1` TINYINT(1) DEFAULT NULL,
        `double1` DOUBLE DEFAULT NULL,
        `date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (`version`,`fk_entity`)
      ) ENGINE=INNODB AUTO_INCREMENT=230297534 DEFAULT CHARSET=latin1
      
      INSERT INTO `entity_versionable` (fk_entity, str1, str2, bool1, double1, DATE)
      SELECT 1, 'a1', 238, 2, 524627, '2013-06-16 14:42:25'
      FROM
      (
          SELECT a.N + b.N * 10 + c.N * 100 + d.N * 1000 + e.N * 10000 + f.N * 100000 + g.N * 1000000 + h.N * 10000000 + 1 N FROM 
           (SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
          ,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
          ,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c
          ,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) d
          ,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) e
          ,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) f
          ,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) g
          ,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) h
      ) t;