我们正在使用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)我们将使用相同的表,并将根据日期范围创建分区
更新:域名确定的“客户”?答案是肯定的
谢谢
最佳答案 首先,如果数据类型过大则批评:
`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)中.
对于国家/地区,请使用CHAR(2)CHARACTER SET ascii – 仅2个字节.
国家地区城市(也许)latlng – 将其标准化为“位置”.
所有这些变化可能会将磁盘占用空间减少一半.较小 – >更多可缓存 – >少I / O – >快点.
其他问题…
为了大大加快你的sid计数器,改变
KEY `domain_statstime` (`domain`,`stats_time`),
至
KEY dss (domain_id,`stats_time`, sid),
那将是一个“覆盖指数”,因此不必在指数和数据之间反弹2713729次 – 弹跳是13分钟的成本. (domain_id将在下面讨论.)
这与上面的索引是多余的,DROP它:
KEY domain_index(域名)
是由域名决定的“客户”吗?
每个InnoDB表必须有一个PRIMARY KEY.获得PK的方法有3种;你选择了“最差”的 – 由引擎制造的隐藏的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的情况下工作.
“每个客户的独立桌子”?不,这是一个常见的问题;响亮的答案是否定的.我不会重复没有100K表的所有原因.
拆分
“分片”是在多台机器上分割数据.
要进行分片,您需要在某处查看域并确定哪个服务器将处理查询,然后将其移交.当您有写入缩放问题时,建议进行分片.你没有提到这一点,所以不清楚分片是否可取.
在对域(或domain_id)等进行分片时,可以使用(1)哈希来选择服务器,(2)字典查找(100K行),或(3)混合.
我喜欢混合 – 散列到比如1024个值,然后查找1024行表以查看哪台机器有数据.由于添加新的分片并将用户迁移到不同的分片是主要的工作,我觉得混合是一种合理的妥协.需要将查找表分发给将操作重定向到分片的所有客户端.
如果你的“写作”已经失去动力,请参阅high speed ingestion以了解加快这种情况的可能方法.
分区
PARTITIONing是跨多个“子表”分割数据.
只有limited number of use cases,分区可以为您带来任何性能.您没有表明任何适用于您的用例.阅读该博客,看看您是否认为分区可能有用.
你提到了“按日期范围划分”.大多数查询是否包含日期范围?如果是这样,那么这种分区可能是可取的. (请参阅上面的链接以获取最佳做法.)还会想到其他一些选项:
计划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.