说有一张桌子。该表用于跟踪书的章节。该表具有以下结构:
CREATE TABLE Chapters(
id INT PRIMARY KEY NOT NULL,
storyId INT references Books(id)
title TEXT NOT NULL,
body TEXT NOT NULL
);
现在,在此表格中, 正文 列将包含大量正文文本。想象一下平均章节在平均小说中有多大,你会得到一个粗略的想法。它将保留数百千字节(甚至一兆字节)的字符串数据。
现在,有些情况下您不需要本章的“正文”,但需要其他内容,例如标题。例如,如果我试图建立一本书中章节的“书籍索引”,我会执行这样的查询:
SELECT title FROM Chapters WHERE storyId = 1
当然,查询会给我回复故事章节的标题。但是,查询是否会使用大量内存,因为它必须将生成的行(列和所有行)加载到内存中,并且表中的每一行都有一个“重”列(“主体”列)?
我问这个是因为(根据我的理解 - 如果我错了,请纠正我)这就是它在文档存储数据库中的工作方式。 MongoDB中的每一行(或“文档”)必须首先加载到内存中,即使您只想从中返回单个字段。因此,如果我在MongoDB中执行类似的查询,它会通过将大的“body”字段加载到内存中来“浪费”内存,即使我想要返回的唯一字段是“title”字段。
对于大多数SQL实现,这些问题是否相同?我特别要求PostgreSQL,但我也有兴趣知道MySQL是否有不同的方式。
答案 0 :(得分:6)
如果您没有选择该列,那么它就不应该占用资源。根据您使用的特定类型的SQL的工作方式,额外的空间可能会导致更大的页面,因此服务器必须遍历更大的磁盘空间才能找到您需要的行,但在您的行中例如,您通过ID(可能是索引)进行选择,因此即使这样也不会发生。即使在确实发生这种情况的情况下,额外的列也不会被放入内存中,只需在服务器搜索您需要的行时跳过它。
对于SQL的某些变体,像TEXT
这样的东西甚至不会与数据行的其余部分一起存储 - 使用的指针指向磁盘上的位置。实际上是保持。在这些情况下,你甚至不会有更大页面的问题。
当然,所有这些都将特定于您正在使用的SQL变体的内部。我不是MySQL或PostgreSQL的专家,所以如果我的任何解释都不适用于那些特定的SQL实现,那么任何人都可以纠正我。
答案 1 :(得分:3)
即使您没有选择该列, 也会占用资源。在如果MyISAM表将使用更多VFS的情况下,对于Innodb将使用更多的缓冲池。为了缓存目的,记录(无论是从索引还是表中检索)都是完整的(但在MyISAM的情况下,VFS在提供另一层抽象的页面上运行,但是整个记录一次性读取,但可能会分期从缓存中逐出。)
即使它被立即丢弃,仍然会有性能影响 - 因为这些是可变长度记录,DBMS必须将流上的句柄前进到下一条记录,除了它使用索引的情况取消引用一个表,它不能直接跳到正确的位置(即使它可以,寻求是昂贵的)。即,对于全表扫描或索引扫描,记录的大小会影响性能。将MyISAM表转换为使用fixed-length records通常会对性能产生显着影响(但不要尝试使用BLOB和CLOB的表)。
包含记录的表的另一个问题是记录迁移,每个记录的大小可能会发生显着变化。记录按照定义的顺序累积,但在更新时,特定记录可能会变得太大而无法容纳先前占用的孔。然后将记录迁移到表的末尾。这也会对性能产生重大影响,也是固定大小记录更快的另一个原因。
答案 2 :(得分:2)
在PostgreSQL中,如果大字段大于物理页面大小(默认为8kB),则不会有性能损失。行必须适合单个物理页面,所以如果你有一个不错的章节大小,那么你将很容易超过这个限制。这样大的行可以压缩和/或烘烤(是的,像面包一样)。
TOAST存储大型字段,而其他字段以通常方式存储。这样可以避免在提取其他字段时I / O等上的任何开销。优秀的PG文档更详细地说明了这一点:
与更多方案相比,该方案具有许多优点 简单的方法,例如允许行值跨越页面。 假设查询通常通过比较来限定 相对较小的关键值,执行者的大部分工作都将是 使用主行条目完成。 TOASTed属性的重要值 只会在结果时拉出(如果完全选中) set被发送到客户端。因此,主表要小得多 它的更多行适合共享缓冲区高速缓存 没有任何外线存储。排序集也缩小,排序 更常见的是完全在记忆中完成。
答案 3 :(得分:1)
答案是
但
***我对健康的定义是a)表被正确编入索引,数据库被正确地进行碎片整理并且数据库设计正确。
答案 4 :(得分:1)
来自docs:
对于在
ROW_FORMAT=DYNAMIC
或ROW_FORMAT=COMPRESSED
中创建的表格,BLOB
,TEXT
或VARCHAR
列的值可能完全存储在页外,具体取决于它们的长度和整行的长度。对于存储在页外的列,聚簇索引记录仅包含指向溢出页的20字节指针,每列一个。是否在页外存储任何列取决于页面大小和行的总大小。当行太长而无法完全适合聚簇索引的页面时,InnoDB会选择最长的列进行页外存储,直到该行适合聚簇索引页。如上所述,如果某行在压缩页面上不适合自己,则会发生错误。
这意味着您的大多数TEXT
字段都会偏离页面,并且不会严重影响不使用它们的查询。
当您执行以下查询时:
SELECT title
FROM Chapters
WHERE storyId = 1
在正确索引的数据库上,会发生以下情况:
引擎会在storyId = 1
的辅助索引中查找storyId
的所有条目。在您的案例storyId
和id
中,索引是索引键和主键上的B树。您可以将其视为表的一个子集,它只包含两个字段:storyId, Id
,始终对它们进行排序,并在原始表更新时更新。这允许快速定位任何给定storyId
的值。
引擎获取上一步中找到的id
的所有值,并为每个值查找表中的title
。 InnoDB
中的表也是B-Tree,即它们是由主键id
排序的。通过id
找到每个给定记录的速度也很快,因为当您更新表时,订单会由引擎维护。
如果title
存储在页外,引擎会获取第一个溢出页面的地址并遍历单链表,直到获得所有数据。如果页面上有title
,则引擎会立即将其返回。
现在,还记得我在第2步说“快”吗?
确切的速度取决于网页的大小。
B-Tree的设计方式是它的深度,即你可以通过id
找到页面的跳数,取决于它存储的页面数量(不是记录)以及它存储的时间长度主键是。
这意味着如果您的记录很大(即页面上存储了长列),则每页获取的记录数量会减少,因此会有更多页面保存相同数量的记录,并且PK上的索引查找会减少高效。
但是,如果title
存储在页面上,则可以通过您不需要执行一个(或多个)其他页面查找来获得其值来减轻这一点。