MySQL 和 NoSQL:帮我选一个合适的

Posted

技术标签:

【中文标题】MySQL 和 NoSQL:帮我选一个合适的【英文标题】:MySQL and NoSQL: Help me to choose the right one 【发布时间】:2011-05-24 02:24:13 【问题描述】:

有一个大数据库,有 1,000,000,000 行,称为线程(这些线程确实存在,我并不是因为我喜欢它而使事情变得更难)。 Threads 里面只有一些东西,让事情变得更快:(int id, string hash, int replycount, int dateline (timestamp), int forumid, string title)

查询:

select * from thread where forumid = 100 and replycount > 1 order by dateline desc limit 10000, 100

因为有 1G 的记录,所以查询速度很慢。所以我想,让我们将这 1G 的记录拆分到我拥有的论坛(类别)中尽可能多的表中!这几乎是完美的。有很多表,我搜索的记录较少,而且速度真的更快。查询现在变为:

select * from thread_forum_id where replycount > 1 order by dateline desc limit 10000, 100

这对于 99% 的论坛(类别)来说确实更快,因为其中大多数只有少数主题 (100k-1M)。但是,因为有些记录有大约 10M 的记录,所以一些查询仍然很慢(0.1/.2 秒,对我的应用来说太慢了!我已经在使用索引了!强>)。

我不知道如何使用 mysql 来改进这一点。有什么办法吗?

对于这个项目,我将使用 10 台服务器(12GB 内存,软件 raid 10 上的 4x7200rpm 硬盘,四核)

这个想法是在服务器之间简单地拆分数据库,但是上面解释的问题仍然不够。

如果我在这 10 台服务器上安装 cassandra(假设我有时间让它按预期工作),我是否应该假设有性能提升?

我该怎么办?继续使用 MySQL 在多台机器上使用分布式数据库或构建 cassandra 集群?

我被要求发布索引是什么,它们是:

mysql> show index in thread;
PRIMARY id
forumid
dateline
replycount

选择解释:

mysql> explain SELECT * FROM thread WHERE forumid = 655 AND visible = 1 AND open <> 10 ORDER BY dateline ASC LIMIT 268000, 250;
+----+-------------+--------+------+---------------+---------+---------+-------------+--------+-----------------------------+
| id | select_type | table  | type | possible_keys | key     | key_len | ref         | rows   | Extra                       |
+----+-------------+--------+------+---------------+---------+---------+-------------+--------+-----------------------------+
|  1 | SIMPLE      | thread | ref  | forumid       | forumid | 4       | const,const | 221575 | Using where; Using filesort | 
+----+-------------+--------+------+---------------+---------+---------+-------------+--------+-----------------------------+

【问题讨论】:

那么,您使用的是什么索引? EXPLAIN SELECT 对您的查询有何看法? 重新添加的索引信息:显然“了解索引”并不包括实际了解哪些索引以及如何使用它们。见维克多的回答。 @andrew,我忘了提到回复计数索引,但这是表中的索引。 见维克多的回答。另请阅读“使用文件排序”可能意味着什么。此外,您对说明的查询与您第一次显示的查询不同,这可能会完全改变答案。 我看到你已经接受了一个答案,但我还是发布了我的答案,因为你可能会找到使用信息:) 【参考方案1】:

您应该阅读以下内容并了解一些关于精心设计的 innodb 表的优势以及如何最好地使用聚集索引 - 仅适用于 innodb!

http://dev.mysql.com/doc/refman/5.0/en/innodb-index-types.html

http://www.xaprb.com/blog/2006/07/04/how-to-exploit-mysql-index-optimizations/

然后按照以下简化示例设计您的系统:

示例架构(简化)

重要的特点是表使用innodb引擎,thread表的主键不再是单个auto_incrementing键,而是基于forum_id和thread_id组合的复合clustered键。例如

threads - primary key (forum_id, thread_id)

forum_id    thread_id
========    =========
1                   1
1                   2
1                   3
1                 ...
1             2058300  
2                   1
2                   2
2                   3
2                  ...
2              2352141
...

每个论坛行都包含一个名为 next_thread_id (unsigned int) 的计数器,该计数器由触发器维护,并在每次将线程添加到给定论坛时递增。这也意味着如果对 thread_id 使用单个 auto_increment 主键,我们每个论坛可以存储 40 亿个线程,而不是总共 40 亿个线程。

forum_id    title   next_thread_id
========    =====   ==============
1          forum 1        2058300
2          forum 2        2352141
3          forum 3        2482805
4          forum 4        3740957
...
64        forum 64       3243097
65        forum 65      15000000 -- ooh a big one
66        forum 66       5038900
67        forum 67       4449764
...
247      forum 247            0 -- still loading data for half the forums !
248      forum 248            0
249      forum 249            0
250      forum 250            0

使用复合键的缺点是不能再通过单个键值来选择线程,如下:

select * from threads where thread_id = y;

你必须做的:

select * from threads where forum_id = x and thread_id = y;

但是,您的应用程序代码应该知道用户正在浏览哪个论坛,因此实现起来并不困难 - 将当前查看的 forum_id 存储在会话变量或隐藏的表单字段等中......

这是简化的架构:

drop table if exists forums;
create table forums
(
forum_id smallint unsigned not null auto_increment primary key,
title varchar(255) unique not null,
next_thread_id int unsigned not null default 0 -- count of threads in each forum
)engine=innodb;


drop table if exists threads;
create table threads
(
forum_id smallint unsigned not null,
thread_id int unsigned not null default 0,
reply_count int unsigned not null default 0,
hash char(32) not null,
created_date datetime not null,
primary key (forum_id, thread_id, reply_count) -- composite clustered index
)engine=innodb;

delimiter #

create trigger threads_before_ins_trig before insert on threads
for each row
begin
declare v_id int unsigned default 0;

  select next_thread_id + 1 into v_id from forums where forum_id = new.forum_id;
  set new.thread_id = v_id;
  update forums set next_thread_id = v_id where forum_id = new.forum_id;
end#

delimiter ;

您可能已经注意到我将 reply_count 作为主键的一部分包含在内,这有点奇怪,因为 (forum_id, thread_id) 组合本身是唯一的。这只是一个索引优化,它在执行使用 reply_count 的查询时节省了一些 I/O。有关更多信息,请参阅上面的 2 个链接。

示例查询

我仍在将数据加载到我的示例表中,到目前为止我已经加载了大约。 5 亿行(是您系统的一半)。加载过程完成后,我应该期望有大约:

250 forums * 5 million threads = 1250 000 000 (1.2 billion rows)

我故意让一些论坛的帖子数超过 500 万,例如论坛 65 有 1500 万个帖子:

forum_id    title   next_thread_id
========    =====   ==============
65        forum 65      15000000 -- ooh a big one

查询运行时

select sum(next_thread_id) from forums;

sum(next_thread_id)
===================
539,155,433 (500 million threads so far and still growing...)

innodb 下对 next_thread_ids 求和以给出总线程数比平时快得多:

select count(*) from threads;

论坛65有多少个话题:

select next_thread_id from forums where forum_id = 65

next_thread_id
==============
15,000,000 (15 million)

这再次比平常更快:

select count(*) from threads where forum_id = 65

好的,现在我们知道到目前为止我们有大约 5 亿个线程,论坛 65 有 1500 万个线程 - 让我们看看架构如何执行 :)

select forum_id, thread_id from threads where forum_id = 65 and reply_count > 64 order by thread_id desc limit 32;

runtime = 0.022 secs

select forum_id, thread_id from threads where forum_id = 65 and reply_count > 1 order by thread_id desc limit 10000, 100;

runtime = 0.027 secs

对我来说看起来非常高效 - 所以这是一个包含 500+ 百万行(并且还在增长)的单个表,其查询在 0.02 秒内覆盖了 1500 万行(在负载下!)

进一步优化

这些将包括:

按范围分区

分片

投入金钱和硬件

等等……

希望这个答案对您有所帮助:)

【讨论】:

非常感谢 f00!这确实有用!我认为有些答案应该是物有所值的!谢谢! =) 只是对存储引擎和聚集索引的评论 - TokuDB 支持多个用户定义的聚集索引,它是 MySQL 的引擎,它的扩展性比 InnoDB 好得多。很好的答案,我 +1。 @f00 感谢您的设计。我有一个查询。您设计的表“线程”容易产生大量碎片(例如,当所有 250 个论坛中都存在线程时,在论坛 5 中插入一个新线程)会增加内存消耗,即使对于索引也是如此。我知道这张表会有很高的插入操作。有没有什么办法可以改变这个大表来减少碎片? (运行 OPTIMIZE TABLE 不会有太大帮助,因为会导致后续插入时出现页面拆分,指向 15/16 填充率。它对主要用于读取操作的表有更多帮助。 @ethan:是的,碎片和记录链接可能是个问题,但好的 dba 会解决这个问题。作为维护事项,按 PK 顺序导出和重新导入。 不知道还能在哪里问这个问题,但是@Jon Black,你用什么来创建、加载和测试你的示例表/数据库?我一直在寻找如何做到这一点,并没有找到明确的方向。【参考方案2】:

编辑:你的一栏索引是不够的。您至少需要涵盖三个相关的列。

更高级的解决方案:将replycount &gt; 1 替换为hasreplies = 1,方法是在replycount &gt; 1 时创建一个等于1 的新hasreplies 字段。完成此操作后,按以下顺序在三列上创建索引:INDEX(forumid, hasreplies, dateline)。确保它是支持排序的 BTREE 索引。

您的选择基于:

给定的forumid 给定的hasrepliesdateline订购

完成此操作后,您的查询执行将涉及:

向下移动 BTREE 以找到匹配 forumid = X 的子树。这是一个对数运算(持续时间:log(论坛数))。 进一步向下移动 BTREE 以找到匹配 hasreplies = 1 的子树(同时仍然匹配 forumid = X)。这是一个常数时间运算,因为hasreplies 只是 0 或 1。 遍历按日期线排序的子树以获得所需的结果,而无需阅读和重新排序论坛中的整个项目列表。

我之前对replycount 进行索引的建议是不正确的,因为它可能是一个范围查询,因此无法使用dateline 对结果进行排序(因此您会非常快速地选择回复的线程,但是在查找所需的 100 个元素之前,必须对生成的百万行列表进行完全排序)。

重要提示:虽然这在所有情况下都可以提高性能,但您巨大的 OFFSET 值(10000!)会降低性能,因为 MySQL 似乎无法直接跳过BTREE。因此,您的 OFFSET 越大,请求将变得越慢。

恐怕偏移量问题不会通过将计算分散到多个计算中自动解决(无论如何,你如何并行跳过偏移量?)或转移到 NoSQL。所有解决方案(包括 NoSQL 解决方案)都将归结为基于 dateline 模拟 OFFSET(基本上说 dateline &gt; Y LIMIT 100 而不是 LIMIT Z, 100 其中Y 是偏移量Z 处的项目的日期)。这有效,并消除了与偏移量相关的任何性能问题,但会阻止直接进入 200 页中的第 100 页。

【讨论】:

+1 很好的答案。我希望你得到 cedivad 的第一个 +1 投票,并在他的 9 个月内回答“接受”;) 很抱歉,但正如已经解释的那样,不幸的是,索引还不够。我也很抱歉问过并且从未使用过“接受功能,我不知道我可以使用它!(通常会出现“你没有足够的代表来做这个”的弹出窗口)”。 您始终可以为您的问题选择正确的答案。至于索引,请查看 EXPLAIN 中“使用文件排序”的含义... 感谢您惊人的更新回复!我终于开始了解更多关于 MySql 的工作原理! =) 不幸的是,我不理解关于偏移量的最后一部分,这很重要,因为偏移量约为 200k 的查询需要大约 20 秒才能运行。假设我们已经知道 Y 的值(以某种方式缓存?)“dateline > Y LIMIT 100”的东西是否运作良好?向表中添加一个字段“Month, 201012”并添加查询 MONTH = 201012 不必抵消大量数据?因此,如果结果 进行另一个查询 dateline &gt; Y 的工作方式如下:在第一页,Y = 9999-12-31,因此您可以获得最新的元素。您将尝试获取其中的 101 个,显示前 100 个,并记住第 101 行的 dateline 是什么。然后,要获取项目 101-200,您将使用该记住的值。可以通过将其添加到第 2 页的链接 (/page?date=2010-09-28) 来记住该值。这就是处理分页的 NoSQL 解决方案的数量(例如,CouchDB 会:参见guide.couchdb.org/draft/recipes.html 上的“快速分页”)。作为中间立场:如果小于 1000,则使用 OFFSET,否则使用 dateline【参考方案3】:

有部分问题与 NoSQL 或 MySQL 选项有关。实际上,这是隐藏在这里的一件基本事情。 SQL 语言对人类来说很容易编写,而对计算机来说阅读起来有点困难。在大容量数据库中,我建议避免使用 SQL 后端,因为这需要额外的步骤 - 命令解析。我已经进行了广泛的基准测试,并且在某些情况下 SQL 解析器是最慢的点。您对此无能为力。好的,您可以使用预解析语句并访问它们。

顺便说一句,它并不广为人知,但 MySQL 是从 NoSQL 数据库发展而来的。 MySQL David 和 Monty 的作者工作的公司是数据仓库公司,他们经常不得不为不常见的任务编写自定义解决方案。这导致大量自制 C 库用于在 Oracle 和其他性能不佳时手动编写数据库函数。 SQL 在 1996 年被添加到这个有近 20 年历史的动物园中,以供娱乐。你知道之后发生了什么。

实际上,您可以使用 MySQL 避免 SQL 开销。但通常 SQL 解析并不是最慢的部分,只是很高兴知道。例如,要测试解析器开销,您可以只为“SELECT 1”做基准测试;)。

【讨论】:

【参考方案4】:

您不应该尝试使数据库架构适合您计划购买的硬件,而是计划购买硬件以适合您的数据库架构。

一旦您有足够的 RAM 将工作索引集保存在内存中,所有可以使用索引的查询都会很快。确保您的密钥缓冲区设置得足够大以容纳索引。

所以如果 12GB 不够用,不要使用 10 台 12GB 内存的服务器,少用 32GB 或 64GB 的内存。

【讨论】:

你说将索引完全缓存在内存中就可以了?不幸的是,我拥有 10 台服务器 @ 12Gb 比 3 台 @ 64Gb 便宜。我会买一个并进行测试,我想这是唯一的方法。 您无需购买任何东西。启动一个 Amazon EC2 实例,只需为运行它的时间付费以进行测试。最大的实例有 68.4GB 的内存。 这也是真的……我可以使用ec2来测试内存使用和性能!谢谢!【参考方案5】:

索引是必须的 - 但请记住选择正确的索引类型:在 WHERE 子句中使用带有“”的查询时,BTREE 更适合,而当您在一列,您在 WHERE 子句中使用“=”或“”。

延伸阅读http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html

【讨论】:

并不真正相关,因为没有人会在从未持久化到磁盘的 MEMORY 表上运行论坛,并且只有 MEMORY 表支持 HASH 索引。如果他的服务器崩溃,或者 MySQL 出现故障并不得不重新启动,他的所有数据都会丢失。 有几种技术可以实现安全和持久的内存数据库,例如快照、事务日志、复制/故障转移集群。内存数据库系统肯定比任何磁盘优化系统都快,因此值得一试。

以上是关于MySQL 和 NoSQL:帮我选一个合适的的主要内容,如果未能解决你的问题,请参考以下文章

当规模到亿级,MySQL是一个更好的NoSQL!

MYSql 社区版是不是支持 Nosql

Mongodb Mysql NoSQL的区别和联系

MySQL 作为新的 NoSQL 解决方案: 轻松应对亿级数据

NoSQL概述

MySql vs NoSql - 社交网络评论和通知数据结构和实现