MySql - 处理表大小和性能

时间:2016-01-11 09:17:26

标签: mysql database query-performance

我们正在使用Google Analytics产品。对于我们的每个客户,我们提供一个JavaScript代码,他们将其放在他们的网站上。如果用户访问我们的客户站点,则java脚本代码会点击我们的服务器,以便我们代表此客户存储此页面访问。每个客户都包含唯一的域名。

我们将此页面访问存储在MySql表中。

以下是表格架构。

CREATE TABLE `page_visits` (
  `domain` varchar(50) DEFAULT NULL,
  `guid` varchar(100) DEFAULT NULL,
  `sid` varchar(100) DEFAULT NULL,
  `url` varchar(2500) DEFAULT NULL,
  `ip` varchar(20) DEFAULT NULL,
  `is_new` varchar(20) DEFAULT NULL,
  `ref` varchar(2500) DEFAULT NULL,
  `user_agent` varchar(255) DEFAULT NULL,
  `stats_time` datetime DEFAULT NULL,
  `country` varchar(50) DEFAULT NULL,
  `region` varchar(50) DEFAULT NULL,
  `city` varchar(50) DEFAULT NULL,
  `city_lat_long` varchar(50) DEFAULT NULL,
  `email` varchar(100) DEFAULT NULL,
  KEY `sid_index` (`sid`) USING BTREE,
  KEY `domain_index` (`domain`),
  KEY `email_index` (`email`),
  KEY `stats_time_index` (`stats_time`),
  KEY `domain_statstime` (`domain`,`stats_time`),
  KEY `domain_email` (`domain`,`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |

我们没有此表的主键。

MySql服务器详细信息

这是Google云MySql(版本为5.6),存储容量为10TB。

截至目前,我们的表中有3.5亿行,表大小为300 GB。即使一个客户与另一个客户之间没有关系,我们也会将所有客户详细信息存储在同一个表中。

问题1 :对于我们在桌面上拥有大量行数的客户而言,针对这些客户的查询效果非常慢。

示例查询1:

SELECT count(DISTINCT sid) AS count,count(sid) AS total FROM page_views WHERE domain = 'aaa' AND stats_time BETWEEN CONVERT_TZ('2015-02-05 00:00:00','+05:30','+00:00') AND CONVERT_TZ('2016-01-01 23:59:59','+05:30','+00:00');
+---------+---------+
| count   | total   |
+---------+---------+
| 1056546 | 2713729 |
+---------+---------+
1 row in set (13 min 19.71 sec)

我会在这里更新更多查询。我们需要在5-10秒内得到结果,是否可能?

问题2 :表格大小正在迅速增加,我们可能会在今年年底达到5 TB的表格大小,因此我们想要对表格进行分类。我们希望将所有与一个客户相关的记录保存在一台机器中。这种分片的最佳实践是什么。

我们正在考虑针对上述问题采取以下方法,请向我们提出克服这些问题的最佳做法。

为每个客户创建单独的表

1)如果我们为每个客户创建单独的表,有什么优点和缺点。到目前为止,我们有30,000个客户,到今年年底我们可能达到100k,这意味着数据库中有100k表。我们同时访问所有表以进行读写。

2)我们将使用相同的表格,并将根据日期范围创建分区

更新:是否由域名确定“客户”? 答案是肯定的

由于

2 个答案:

答案 0 :(得分:1)

首先,批评过大的数据类型

  `domain` varchar(50) DEFAULT NULL,  -- normalize to MEDIUMINT UNSIGNED (3 bytes)
  `guid` varchar(100) DEFAULT NULL,  -- what is this for?
  `sid` varchar(100) DEFAULT NULL,  -- varchar?
  `url` varchar(2500) DEFAULT NULL,
  `ip` varchar(20) DEFAULT NULL,  -- too big for IPv4, too small for IPv6; see below
  `is_new` varchar(20) DEFAULT NULL,  -- flag?  Consider `TINYINT` or `ENUM`
  `ref` varchar(2500) DEFAULT NULL,
  `user_agent` varchar(255) DEFAULT NULL,  -- normalize! (add new rows as new agents are created)
  `stats_time` datetime DEFAULT NULL,
  `country` varchar(50) DEFAULT NULL,  -- use standard 2-letter code (see below)
  `region` varchar(50) DEFAULT NULL,  -- see below
  `city` varchar(50) DEFAULT NULL,  -- see below
  `city_lat_long` varchar(50) DEFAULT NULL,  -- unusable in current format; toss?
  `email` varchar(100) DEFAULT NULL,

对于IP地址,请使用inet6_aton(),然后存储在BINARY(16)

对于country,请使用CHAR(2) CHARACTER SET ascii - 仅2个字节。

country + region + city +(也许)latlng - 将其标准化为" location"。

所有这些变化可能会将磁盘占用空间减少一半。较小 - >更多可缓存 - >少I / O - >更快。

其他问题 ...

要大大加快sid计数器的速度,请更改

KEY `domain_statstime` (`domain`,`stats_time`),

KEY dss (domain_id,`stats_time`, sid),

这将是"覆盖索引"因此不会在索引和数据之间反弹2713729次 - 弹跳是13分钟的成本。 (domain_id将在下面讨论。)

对于上面的索引DROP,这是多余的:   KEY domain_indexdomain

是"客户"由domain确定?

每个InnoDB表必须有PRIMARY KEY。获得PK的方法有3种;你挑选了最差的' one - 由引擎制造的隐藏的6字节整数。我认为没有自然的' PK可从某些列组合中获得?然后,要求显式BIGINT UNSIGNED。 (是的,这将是8个字节,但各种形式的维护需要显式 PK。)

如果大多数查询包含WHERE domain = '...',那么我建议如下。 (这将大大改善所有此类查询。)

id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
domain_id MEDIUMINT UNSIGNED NOT NULL,   -- normalized to `Domains`
PRIMARY KEY(domain_id, id),  -- clustering on customer gives you the speedup
INDEX(id)  -- this keeps AUTO_INCREMENT happy

建议您查看pt-online-schema-change进行所有这些更改。但是,我不知道它是否可以在没有明确PRIMARY KEY的情况下工作。

"每个客户的单独表格#34;? 没有。这是一个常见的问题;响亮的答案是否定的。我不会重复没有100K表的所有理由。

<强>拆分

&#34;拆分&#34;将数据拆分到多个计算机

要进行分片,您需要在某处查看domain的代码,并决定哪个服务器将处理该查询,然后将其移交。当您有写缩放问题时,建议进行分片。你没有提到这一点,所以不清楚是否可以进行分片。

domain(或domain_id)之类的内容进行分片时,可以使用(1)哈希来选择服务器,(2)字典查找(100K行)或(3)混合动力车。

我喜欢混合 - 散列到比如1024个值,然后查找1024行表以查看哪台机器有数据。由于添加新的分片并将用户迁移到不同的分片是主要的工作,我觉得混​​合是一种合理的妥协。需要将查找表分发给将操作重定向到分片的所有客户端。

如果您正在撰写&#39;正在失去动力,请参阅high speed ingestion了解加快速度的方法。

<强>分区

PARTITIONing将数据拆分为多个&#34;子表&#34;。

只有limited number of use cases分区可以为您带来任何性能。您没有表明任何适用于您的用例。阅读该博客,看看您是否认为分区可能有用。

你提到&#34;按日期范围划分&#34;。大多数查询是否包含日期范围?如果是这样,那么这样的分区可能是。 (请参阅上面的链接以获取最佳实践。)其他一些选项会浮现在脑海中:

计划A:PRIMARY KEY(domain_id, stats_time, id)但这很笨重,每个二级索引需要更多的开销。 (每个辅助索引都默默地包含PK的所有列。)

计划B:让stats_time包含微秒,然后调整值以避免重复。然后使用stats_time代替id。但这需要一些额外的复杂性,特别是如果有多个客户端插入数据。 (如果需要,我可以详细说明。)

计划C:有一个将stats_time值映射到id的表。在进行实际查询之前查找id范围,然后同时使用WHERE id BETWEEN ... AND stats_time ...。 (再次,凌乱的代码。)

汇总表

在日期范围内计算事物的形式的许多查询是什么?建议根据每小时计算摘要表。 More discussion

COUNT(DISTINCT sid)特别难以折叠到汇总表中。例如,不能将每小时的唯一计数加在一起以获得当天的唯一计数。但我也有technique

答案 1 :(得分:0)

如果我是你,我不会这样做。首先想到的是,在收到网页浏览消息时,我将消息发送到队列,以便工作人员可以稍后拾取并插入数据库(可能是批量);我也在redis中增加siteid:date的计数器(例如)。在sql中执行count对于这种情况来说只是一个坏主意。