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 > 1
替换为hasreplies = 1
,方法是在replycount > 1
时创建一个等于1 的新hasreplies
字段。完成此操作后,按以下顺序在三列上创建索引:INDEX(forumid, hasreplies, dateline)
。确保它是支持排序的 BTREE 索引。
您的选择基于:
给定的forumid
给定的hasreplies
由dateline
订购
完成此操作后,您的查询执行将涉及:
向下移动 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 > 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 > 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:帮我选一个合适的的主要内容,如果未能解决你的问题,请参考以下文章