我想存储我在“实体”表上所做的更改。这应该像一个日志。目前,它在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_id
=我entity
表的主键。entitytype
= entity
表中更改的字段。有时只改变一个字段,有时多个。一个变化=一行。value
=字段“新值”的字符串表示形式。将字段entity.somedouble
从3更改为2时的示例,我运行这些查询:
UPDATE entity SET somedouble = 2 WHERE entity_id = 123;
INSERT INTO entitychange (entity_id,entitytype,value) VALUES (123,'SOMEDOUBLE',2);
我需要select
过去15天内特定实体和实体类型的变化。例如:在过去15天内,最后一次更改SOMEDOUBLE
为entity_id 123
。
现在,有两件事我不喜欢:
TEXT
- 虽然大多数(小于1%)不是真正的文本,但就我而言,大多数值都是DOUBLE
。这是一个大问题吗?我的问题:我如何解决这两个“瓶颈”?我需要扩展。
我的方法是:
entitychange
表中,然后根据其entitychange_[bool|timestamp|double|string]
HASH(entity_id)
使用分区 - 我想到了~50个分区。 答案 0 :(得分:5)
如果我遇到你提到的问题,我会设计如下表所示的LOG表:
EntityName
:(字符串)正被操纵的实体。(必填)ObjectId
:被操纵的实体,主键。FieldName
:(字符串)实体字段名称。OldValue
:(字符串)实体字段旧值。NewValue
:(字符串)实体字段新值。UserCode
:应用程序用户唯一标识符。 (强制)TransactionCode
:任何更改实体的操作都需要有一个唯一的事务代码(如GUID)(必填),ChangeDate
:交易日期。 (强制)FieldType
:显示字段类型(如TEXT或Double)的枚举或文本。 (强制)采用这种方法
可以跟踪任何实体(表格)。报告将是可读的。只记录更改。
事务代码将是检测更改的关键点通过一个动作。
顺便说一句
Store the changes in the entitychange table and then store the value
according to its datatype in entitychange_[bool|timestamp|double|string]
不需要,在单个表中您将拥有更改和数据类型
Use partitioning by HASH(entity_id)
我更喜欢通过ChangeDate进行分区或为changeDate创建备份表,这些表已经足够大,可以从主LOG表中备份和卸载
Should I use another database system, maybe MongoDB?
任何数据库都有自己的概率和缺点,您可以在任何RDBMS上使用该设计。 基于文档的数据库(如MongoDB could be found here
)的有用比较希望对你有所帮助。
答案 1 :(得分:3)
现在我想我明白了你需要的东西,一个可更改记录历史的可版本表。这可能是实现相同目标的另一种方法,您可以轻松地进行一些快速测试,以确定它是否比您当前的解决方案提供更好的性能。它是Symfony PHP Framework在Doctrine中使用Versionable插件的方式。
请记住,有两个键的主键唯一索引,版本和fk_entity。
另请查看保存的值。您将在未更改的字段中保存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 :(得分:2)
这里有两个主要挑战:
2-3。管理大表:归档,便于备份和恢复
2-3。性能优化:更快的插入和选择
有效存储数据
value
已提交。我建议你做VARCHAR (N)
。
原因:
TEXT
数据类型会导致性能下降:(来自BLOB and Text data types上的manaul)使用临时表处理的查询结果中
TEXT
列的实例会导致服务器在磁盘而不是内存中使用表,因为MEMORY存储引擎不支持这些数据类型。使用磁盘会导致性能下降,因此只有在确实需要时才在查询结果中包含BLOB或TEXT列。例如,避免使用选择所有列的SELECT *。每个BLOB或TEXT值在内部由单独分配的对象表示。这与所有其他数据类型形成对比,在打开表时,每列分配一次存储。
基本上TEXT
用于存储大字符串和拼接文本,而VARCHAR()
设计为相对较短的字符串。
id
字段。 (更新,感谢@steve)我同意这个字段没有任何有用的信息。使用3列作为主键:entity_id
和entitype
以及when
。 TIMESTAMP
将很好地保证您不会重复。同样的列也将用于分区/子分区。表格可管理性 有两个主要选项:MERGE表和分区。 MERGE存储引擎基于My_ISAM,据我所知,它正在逐步淘汰。以下是对[MERGE存储引擎]的一些解读。2
主要工具是分区,它提供两个主要好处: 1.分区切换(通常是对大块数据的即时操作)和滚动窗口场景:在一个表中插入新数据,然后立即将所有数据切换到存档表中。 2.按排序顺序存储数据,启用分区修剪 - 仅查询包含所需数据的分区。 MySQL允许子分区进一步分组数据。
按entity_id
分区是有道理的。如果您需要长时间查询数据,或者在查询表时有其他模式 - 请使用该列进行子分区。除非在该级别切换分区,否则不需要对所有主键列进行子分区。
分区数取决于您希望该分区的db文件有多大。子分区数量取决于核心数量,因此每个核心可以搜索自己的分区,N-1子分区应该没问题,所以1核心可以做整体协调工作。
<强>优化强>
插入内容:
在没有索引的情况下,表格上的插入更快,因此插入大块数据(进行更新),然后创建索引(如果可能)。
为Text
更改Varchar
- 数据库引擎需要一些压力
最小的日志记录和表锁可能会有所帮助,但通常无法使用
选择
Text
到Varchar
肯定会有所改善。
拥有包含最新数据的当前表 - 过去15天,然后通过分区切换进行归档。在这里,您可以选择将表分区与归档表不同(例如,先按日期,然后是entity_id),并通过将小(1天)数据移动到临时表以及更改分区来更改分区方式。
< / LI>此外,您可以考虑按日期分区,您在日期范围内有很多查询。首先使用您的数据及其部分,然后确定哪种模式最适合它。
至于你的第三个问题,我不知道MongoDB的使用将如何特别有利于这种情况。
答案 3 :(得分:1)
这被称为temporal database,研究人员一直在努力寻找存储和查询时态数据超过20年的最佳方式。
尝试存储EAV数据的效率很低,因为在TEXT列中存储数字数据会占用大量空间,而且您的表越来越长,正如您所发现的那样。
另一个有时称为第六范式的选项(虽然有6NF有多个不相关的定义),是存储一个额外的表来存储您想要暂时跟踪的每列的修订。这类似于@ xtrm的答案提出的解决方案,但它不需要存储未更改的列的冗余副本。但它确实导致了桌子数量的激增。
我开始阅读Anchor Modeling,它承诺处理结构和内容的时间变化。但我还不太清楚它是否足以解释它。我只是链接到它,也许它对你有意义。
以下是一些包含时态数据库讨论的书籍:
答案 4 :(得分:1)
在TEXT
列中存储整数是不行的! TEXT
是最昂贵的类型。
我会为每个要监控的字段创建一个日志表:
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;
确实对它们进行了分区。
注意我建议使用MyISAM
引擎。您不需要此(这些)无约束,仅插入表的事务。
答案 5 :(得分:1)
为什么INSERTing如此缓慢,你可以做些什么来加快速度。
这些是我要看的东西(大致按照我将通过它们的顺序):
创建一个新的AUTO_INCREMENT-id并将其插入主键需要一个锁(InnoDB中有一个特殊的AUTO-INC锁,它在语句结束前一直保持,有效地充当表在你的场景中锁定)。这通常不是问题,因为这是一个相对较快的操作,但另一方面,如果(Unix)加载值为10到15,您可能会有进程等待该锁被释放。根据您提供的信息,我认为您的代理键'id'没有任何用处。查看删除该列是否会显着改变性能。 (顺便说一句,没有规则表需要一个主键。如果你没有主表,那没关系)
对于INSERT,InnoDB可能相对昂贵。这是为了允许诸如交易之类的附加功能而进行的权衡,可能会或可能不会影响您。由于您的所有操作都是原子操作,因此我认为不需要进行交易。也就是说,试试MyISAM吧。注意:对于大型表,MyISAM通常是一个糟糕的选择,因为它只支持表锁定而不是记录级别锁定,但它支持concurrent inserts,因此它可能是一个选择(特别是如果你删除了主键,见上文)
您可以使用数据库存储引擎参数。 InnoDB和MyISAM都有可以改变的选项。其中一些对TEXT数据的实际存储方式有影响,另一些则具有更广泛的功能。你应该特别注意的是innodb_flush_log_at_trx_commit。
如果(并且仅当)它们具有非NULL值,则TEXT列相对昂贵。您当前正在该TEXT列中存储所有值。值得尝试以下操作:在表中添加额外的字段value_int
和value_double
,并将这些值存储在相应的列中。是的,这将浪费一些额外的空间,但可能会更快 - 但这在很大程度上取决于数据库存储引擎及其设置。请注意,很多人对TEXT列性能的看法并不正确。 (见my answer to a related question on VARCHAR vs TEXT)
您建议在多个表格上传播信息。如果您的表完全独立,这只是一个好主意。否则,对于任何更改,您最终都会有多个INSERT操作,并且您很可能会使事情变得更糟。虽然规范化数据通常是好的(tm),但这可能会损害性能。
如何使SELECT快速运行
正确的密钥。和正确的钥匙。以防我忘记提及:正确的钥匙。您没有详细说明您的选择是什么样的,但我认为它们类似于“SELECT * FROM entitychange WHERE entity_id = 123 AND ts&gt; ...”。 entity_id和ts上的单个复合索引应足以使此操作快速。由于必须使用每个INSERT更新索引,因此可能值得尝试entity_id, ts
和ts, entity_id
的性能:它可能会产生影响。
分区。如果您没有在问题中提出问题,我甚至不会提起这个问题。你没有说你为什么要分区表。在性能方面,如果你有合适的密钥,它通常没有区别。有一些特定的设置可以提高性能,但你需要适当的硬件设置来配合这一点。如果您决定对表进行分区,请考虑使用entity_id或TIMESTAMP列进行分区。使用时间戳,最终可能会将归档系统与旧数据放在归档驱动器上。但是,这样的分区系统需要一些维护(随着时间的推移添加分区)。
在我看来,你并不关心查询性能和原始插入速度,所以我不会详细介绍SELECT性能。如果您对此感兴趣,请提供更多详细信息。
答案 6 :(得分:1)
我建议你在深度测试中做很多事情,但是从我的测试中我使用我之前发布的表定义INSERT和SELECT都取得了非常好的结果。我将详细介绍我的测试,以便任何人都可以轻松地重复并检查它是否会获得更好的结果。 在进行任何测试前备份您的数据。
我必须说这些只是测试,可能不会反映或改善你的实际案例,但它是一种很好的学习方式,也许是一种寻找有用信息和结果的方法。
我们在这里看到的建议非常好,你肯定会注意到通过使用大小而不是TEXT的预定义类型VARCHAR来提高速度。但是你可以获得速度,我建议不要因为数据完整性原因而使用MyISAM,请留在InnoDB。
的测试:强>
的 1。设置表并插入2亿个数据:
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
为了在约35分钟的表格中插入2亿行,请查看peterm已回答best ways to fill a table之一的其他问题。它完美无缺。
执行以下查询2次,以插入2亿行无随机数据(每次更改数据以插入随机数据):
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;
*由于您已经拥有2亿行真实随机数据的原始表,您可能不需要填充它,只需导出表数据和模式并将其导入到具有相同模式的新测试表中。这样,您将在新表中使用您的真实数据进行测试,并且您获得的改进也适用于原始数据。
<强> 2。更改新的性能测试表(或使用上面的步骤1中的示例获得更好的结果)。 一旦我们有了新的测试表设置并填充了随机数据,我们应该检查上面的建议,然后更改表格以加快它的速度:
OPTIMIZE TABLE
test
。entity_versionable
;
修理表test
。entity_versionable
;
*制作一个脚本来执行优化并使您的索引保持最新,每晚启动它。
第3。仔细阅读以下主题,改进您的MySQL和硬件配置。他们值得一读,我相信你会得到更好的结果。
<强> 4。最后,在测试表中测试你的INSERT和SEARCH。我的测试是用上面的表模式的+200万随机数据,它花费0,001秒来插入新行,大约2分钟搜索和SELECT 100百万行。然而它只是一个测试,似乎是好结果:)
<强> 5。我的系统配置:
<强> 6。阅读更多关于MySQL的信息:
O'Reilly High Performance MySQL
MySQL Optimizing SQL Statements
的 7。使用其他数据库:
MongoDB或Redis对于这种情况将是完美的,并且可能比MySQL快得多。两者都很容易学习,两者都有其优点:
- MongoDB:MongoDB log file growth
我肯定会选择 Redis 。如果您学习如何在Redis中保存日志,那么这将是以极快的速度管理日志的最佳方式:
redis for logging
如果您使用Redis,请记住以下建议:
Redis是用C编译的,它存储在内存中,有一些不同 自动将信息保存到磁盘的方法 (持久性),你可能不必担心它。 (万一发生灾难 您将结束大约1秒的日志记录)。
Redis用于管理数TB数据的很多站点, 有很多方法可以处理疯狂的信息量 它意味着它的安全性(在这里用于stackoverflow,暴雪,推特,youporn ......)
由于您的日志非常大,因此需要适合内存 无需访问硬盘即可获得速度。你可以 保存不同日期的不同日志,并仅设置其中一些日志 记忆。如果达到内存限制,您将不会有任何错误,一切仍然可以正常工作,但请查看Redis Faqs以获取更多信息。
我完全相信Redis会比这更快
MySQL的。您需要了解如何使用lists
和。{
sets
更新数据和查询/搜索数据。如果您可能需要非常高级的查询搜索,那么您应该使用MongoDB,但在这种情况下,简单的日期搜索将非常适合Redis。
答案 7 :(得分:0)
在工作中,由于客户条件(金融部门),我们几乎每张桌子上都有日志。
我们这样做了:两个表(“普通”表和日志表)然后触发插入/更新/删除正常表,它们存储关键字(I,U,D)和旧记录(在更新,删除时)或在日志表中的新文件(插入时)
我们在同一个数据库架构中有两个表