7

要求 :

Page#1 -> 显示用户和他们最近 10 篇博文的 1-2 行预览

Page#2 -> 显示带有全文的单个博文。

方法一:

MySQL table ->   userid -> varchar 50
                 post_id -> integer
                 post_title -> varchar 100
                 post_description -> varchar 10000

对于 page#1,从 blog_table 中选择 user_id、post_title 和 post_description。post_description 的子字符串用于在列表中显示预览。

对于 page#2,选择 user_id 、 post_title 、 post_description 其中 post_id = N

方法二:

 MySQL table ->   userid -> varchar 50
                  post_id -> integer
                  post_title -> varchar 100
                  post_brief -> varchar 250
                  post_description -> text

对于 page#1,从 blog_table 中选择 user_id、post_title 和 post_brief。

对于 page#2,选择 user_id 、 post_title 、 post_description 其中 post_id = N

是否存储两列,一列作为 varchar,一列作为文本(因为它访问文件系统,并且只在需要时才应该查询),是否值得性能优势?

因为,方法 2 将仅存储指向行中文本的指针,而方法 1 将在行中存储完整的 varchar 10K 字符串。它是否会影响可以驻留在 RAM 中的表数据量,从而影响查询的读取性能?

4

3 回答 3

16

SQL 查询的性能主要取决于 JOIN、WHERE 子句、GROUP BY 和 ORDER BY,而不是检索到的列。如果检索到的数据明显更多,这些数据可能必须通过网络才能由您的编程语言处理,这些列才会对查询的速度产生显着影响。这里情况不同。

简短回答:两种提议的设置之间的性能差异可能非常小。

为了获得良好的速度,您的post_id列应该有一个(唯一的)索引。您没有按任何其他列进行选择、排序或分组,因此数据可以直接来自表格,这是一个非常快速的过程。

您在这里谈论“页面”,所以我猜这些将呈现给用户 - 您似乎不太可能希望在同一页面上向人类显示数千篇博客文章的表格,因此您可能会这样做实际上,您的陈述中有 ORDER BY 和/或 LIMIT 子句,而您没有包含在您的问题中。

但是,让我们更深入地了解一下整个事情。假设我们实际上是直接从硬盘读取大量 TEXT 列,我们不会达到驱动器的最大读取速度吗?只检索一个 VARCHAR(250) 不会更快,特别是因为它为您节省了额外的 LEFT() 调用?

我们可以真正快速地取消 LEFT() 调用。字符串函数非常快——毕竟只是 CPU 切断了一些数据,这是一个非常快的过程。它们产生明显延迟的唯一时间是在 WHERE 子句、JOIN 等中使用它们时,但这并不是因为这些函数很慢,而是因为它们必须运行很多次(可能是数百万次)才能甚至会产生单行结果,甚至更多,因为这些使用通常会阻止数据库正确使用其索引。

所以最后归结为:MySQL 从数据库中读取表内容的速度有多快。而这又取决于您使用的存储引擎及其设置。MySQL 可以使用许多存储引擎,包括(但不限于)InnoDB 和 MyISAM。这两个引擎都为大对象(如 TEXT 或 BLOB 列)提供不同的文件布局(但有趣的是,还有 VARCHAR)。如果 TEXT 列存储在与行的其余部分不同的页面中,则存储引擎必须为每一行检索两个页面。如果它与其余部分一起存储,它将只是一页。对于顺序处理,这可能是性能的重大变化。

这里有一些背景阅读:

长答案:这取决于:)

您必须在自己的硬件上进行许多基准测试才能真正确定哪种布局实际上更快。鉴于第二种设置在其附加列中引入了冗余,因此在大多数情况下它的性能可能会更差。当且仅当表结构允许较短的 VARCHAR 列适合磁盘上的同一页面而长 TEXT 列将在另一个页面上时,它将执行得更好。

编辑:有关 TEXT 列和性能的更多信息

关于 BLOB 和内存处理似乎存在一个常见的误解。相当多的页面(包括 StackOverflow 上的一些答案 - 我会尝试找到它们,并给出额外的评论)指出 MySQL 无法在内存中处理 TEXT 列(和所有其他 BLOB),因此总是性能猪。那不是真的。真正发生的是这样的:

如果您运行一个涉及 TEXT 列的查询并且该查询需要处理一个临时表,那么MySQL 将不得不在磁盘上而不是在内存中创建该临时表,因为 MySQL 的MEMORY存储引擎无法处理 TEXT 列。请参阅此相关问题

MySQL 文档说明了这一点(该段落对于 3.2 到 5.6 的所有版本都是相同的):

使用临时表处理的查询结果中的 BLOB 或 TEXT 列的实例导致服务器使用磁盘上的表而不是内存中的表,因为 MEMORY 存储引擎不支持这些数据类型(参见第 8.4.3.3 节, “MySQL 如何使用内部临时表”)。使用磁盘会导致性能损失,因此只有在真正需要时才在查询结果中包含 BLOB 或 TEXT 列。例如,避免使用选择所有列的 SELECT *。

让人们感到困惑的是最后一句话——因为那只是一个坏例子。一个简单的SELECT *不会受到这个性能问题的影响,因为它不会使用临时表。例如,如果相同的选择是由非索引列排序的,则它必须使用临时表并且会受到此问题的影响。使用EXPLAINMySQL 中的命令来确定查询是否需要临时表。

顺便说一句:这些都不会影响缓存。TEXT 列可以像其他任何内容一样被缓存。即使查询需要一个临时表并且必须存储在磁盘上,如果系统有资源这样做,结果仍然可以被缓存,并且缓存不会失效。在这方面,TEXT 列就像其他任何东西一样。

编辑 2:有关 TEXT 列和内存要求的更多信息...

MySQL 使用存储引擎从磁盘检索记录。然后它将缓冲结果并按顺序将它们交给客户端。以下假设此缓冲区最终在内存中而不是在磁盘上(请参阅上面的原因)

对于 TEXT 列(和其他 BLOB),MySQL 将缓冲一个指向实际 BLOB 的指针。这样的指针仅使用几个字节的内存,但需要在将行交给客户端时从磁盘中检索实际的 TEXT 内容。对于 VARCHAR 列(以及除 BLOB 之外的所有其他列),MySQL 将缓冲实际数据。这通常会使用更多内存,因为您的大部分文本将不仅仅是几个字节。对于计算列,MySQL 也会缓冲实际数据,就像使用 VARCHAR 一样。

关于这一点的几点说明:从技术上讲,当 BLOB 移交给客户端时,它们也会被缓冲,但一次只能缓冲一个 - 对于大型 BLOB,可能不是全部。由于此缓冲区在每一行之后被释放,因此不会产生任何重大影响。此外,如果 BLOB 实际上与行的其余部分存储在同一页中,则它可能最终被视为 VARCHAR。老实说,我从来没有要求在单个查询中返回大量BLOB,所以我从未尝试过。

现在让我们实际回答(现已编辑)的问题:

第 1 页。用户概述和简短的博客文章片段。

您的选择几乎就是这些查询

SELECT userid, post_title, LEFT(post_description, 250) FROM `table_method_1`  <-- calculated based on a VARCHAR column
SELECT userid, post_title, LEFT(post_description, 250) FROM `table_method_2`  <-- calculated based on the TEXT column
SELECT userid, post_title, post_brief FROM `table_method_2`                   <-- precalculated VARCHAR column
SELECT userid, post_title, post_description FROM `table_method_2`             <-- return the full text, let the client produce the snippet

前三个的内存要求是相同的。第四个查询将需要更少的内存(TEXT 列将作为指针缓冲),但客户端的流量更多。由于流量通常通过网络传输(在性能方面很昂贵),这往往比其他查询慢 - 但您的里程可能会有所不同。TEXT 列上的 LEFT() 函数可以通过告诉存储引擎使用内联表格布局来加速,但这将取决于所存储文本的平均长度。

第2页。一篇博文

SELECT userid, post_title, post_description FROM `table_method_1` WHERE post_id=... <-- returns a VARCHAR
SELECT userid, post_title, post_description FROM `table_method_2` WHERE post_id=... <-- returns a TEXT

开始时内存要求很低,因为只会缓冲一行。由于上述原因,第二个将需要更少的内存来缓冲行,但需要一些额外的内存来缓冲单个 BLOB。

无论哪种情况,我很确定您不关心仅返回单行的选择的内存要求,因此这并不重要。

概括

如果您有任意长度的文本(或任何需要超过几千字节的文本),您应该使用 TEXT 列。这就是他们的目的。MySQL 处理这些列的方式在大多数情况下是有益的

日常使用只有两件事要记住:

  • 如果您实际上不需要它们,请避免选择 TEXT 列、BLOB 列和所有其他可能包含大量数据的列(是的,包括 VARCHAR(10000))。当您只需要几个值时,“SELECT * FROM whatever”的习惯会给数据库带来很多不必要的压力。
  • 当您选择TEXT 列或其他 BLOB 时,请确保选择不使用临时表。有疑问时使用EXPLAIN语法。

当您遵守这些规则时,您应该从 MySQL 获得相当不错的性能。如果您需要进一步优化,则必须查看更精细的细节。这将包括存储引擎和相应的表格布局、有关实际数据的统计信息以及有关所涉及硬件的知识。根据我的经验,我通常可以摆脱性能狂,而无需深入挖掘。

于 2013-03-08T21:36:23.420 回答
2

方法 2 看起来更好,但如果您在其中存储 HTML,post_brief 也可以是 TEXT 列,如果它是纯文本,您可以将所有内容存储在一列中并使用

SELECT user_id, post_title, LEFT(post_description,255) AS post_brief FROM blog_table.

考虑 MySQL 5.6,它要快得多,您可以在 InnoDB 中使用 FULLTEXT 索引,因此在搜索帖子时会有很大帮助

于 2013-02-21T09:58:39.543 回答
1

选项 2 对我来说也不错。由于博文会很大,在这些列上应用函数也需要时间。

如果你问我, post_description 的数据类型应该是blob/text。尽管 blob 列不支持搜索,但这将是更好的选择。

拥有两列的唯一缺点是,您必须确保 desc 和 Brief 同步(也许您也可以将其作为一个功能)

于 2013-03-15T17:11:05.290 回答