Ale*_*kov 5 mysql sql sql-optimization
希望你做得很好.
我需要一点这个数据库的帮助:

这是一个存储投票的数据库.用户选择他们喜欢的音轨,然后投票给他们.他们可以"投票"或"投票"投票.非常简单.但是,当谈到计算统计数据时,它变得毛茸茸.
它是一个键值样式表,存储最常用的统计信息(只是排序缓存):
mysql> SELECT * FROM Meta;
+-------------+-------+
| Key | Value |
+-------------+-------+
| TRACK_COUNT | 2620 |
| VOTE_COUNT | 3821 |
| USER_COUNT | 371 |
+-------------+-------+
Run Code Online (Sandbox Code Playgroud)
投票表持有投票本身.这里唯一有趣的领域是Type,其价值意味着:
0 - 应用程序投票,用户使用UI投票选择了赛道1 - 导入投票(来自外部服务)2 - 合并投票.实际上与导入的投票相同,但它实际上已经注意到,该用户已经使用外部服务投票支持此曲目,现在他正在重复使用该应用程序.该轨道保留了自己的总统计数据.喜欢,不喜欢外来服务(LikesRP),不喜欢外部服务(DislikesRP),喜欢/不喜欢调整的数量.
该应用程序需要获得投票:
Vote.Type = 1)要获得100个最常用的投票曲目,我使用此查询:
SELECT
T.Hash,
T.Title,
T.Artist,
COALESCE(X.VotesTotal, 0) + T.LikesAdjust as VotesAdjusted
FROM (
SELECT
V.TrackHash,
SUM(V.Vote) AS VotesTotal
FROM
Vote V
WHERE
V.CreatedAt > NOW() - INTERVAL 1 MONTH AND V.Vote = 'up'
GROUP BY
V.TrackHash
ORDER BY
VotesTotal DESC
) X
RIGHT JOIN Track T
ON T.Hash = X.TrackHash
ORDER BY
VotesAdjusted DESC
LIMIT 0, 100;
Run Code Online (Sandbox Code Playgroud)
此查询工作正常,它符合调整(客户端希望调整列表中的跟踪位置).几乎相同的查询用于获得5个最多/最多投票的曲目.对任务#3的查询是这样的:
SELECT
T.Hash,
T.Title,
T.Artist,
COALESCE(X.VotesTotal, 1) as VotesTotal
FROM (
SELECT
V.TrackHash,
SUM(V.Vote) AS VotesTotal
FROM
Vote V
WHERE
V.Type = '1' AND
V.CreatedAt > NOW() - INTERVAL 1 WEEK AND
V.Vote = 'up'
GROUP BY
V.TrackHash
ORDER BY
VotesTotal DESC
) X
RIGHT JOIN Track T
ON T.Hash = X.TrackHash
ORDER BY
VotesTotal DESC
LIMIT 0, 5;
Run Code Online (Sandbox Code Playgroud)
问题是第一个查询需要大约2秒才能执行,我们的投票少于4k.到年底,这个数字将是大约20万票,这很可能会杀死这个数据库.所以我正在弄清楚如何解决这个难题.
现在我回答这些问题:
我做的第一件事就是缓存.但是,好的,这大大解决了这个问题.但我对SQL相关的解决方案感到好奇(总是倾向于完美).
我有一个想法的第二件事是将这些计算值放到Meta表中并在投票过程中更改它们.但是我的时间很短,只是试一试.顺便说一下,这值得吗?或者,企业级应用程序如何解决这些问题?
谢谢.
我不敢相信我忘了包含指数.他们来了:
mysql> SHOW INDEXES IN Vote;
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Vote | 0 | UNIQUE_UserId_TrackHash | 1 | UserId | A | 890 | NULL | NULL | | BTREE | |
| Vote | 0 | UNIQUE_UserId_TrackHash | 2 | TrackHash | A | 4450 | NULL | NULL | | BTREE | |
| Vote | 1 | INDEX_TrackHash | 1 | TrackHash | A | 4450 | NULL | NULL | | BTREE | |
| Vote | 1 | INDEX_CreatedAt | 1 | CreatedAt | A | 1483 | NULL | NULL | | BTREE | |
| Vote | 1 | UserId | 1 | UserId | A | 1483 | NULL | NULL | | BTREE | |
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
mysql> SHOW INDEXES IN Track;
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Track | 0 | PRIMARY | 1 | Hash | A | 2678 | NULL | NULL | | BTREE | |
| Track | 1 | INDEX_Likes | 1 | Likes | A | 66 | NULL | NULL | | BTREE | |
| Track | 1 | INDEX_Dislikes | 1 | Dislikes | A | 27 | NULL | NULL | | BTREE | |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
Run Code Online (Sandbox Code Playgroud)
这是一个非常主观的问题,因为它很大程度上取决于您的具体要求以及性能测试,而这里没有人可以对您的数据进行测试。但我可以回答您的问题并添加一些可能适合您的通用解决方案:
我的数据库设计有误吗?我的意思是,还能更好吗?
不,这是 OLTP 的理想设计。
难道是我查询错误了?
否(尽管ORDER BY子查询中的 是多余的)。查询的性能很大程度上取决于表上的索引Vote,因为查询的主要列将在这部分中:
SELECT V.TrackHash, SUM(V.Vote) AS VotesTotal
FROM Vote V
WHERE V.CreatedAt > NOW() - INTERVAL 1 MONTH AND V.Vote = 'up'
GROUP BY V.TrackHash
Run Code Online (Sandbox Code Playgroud)
我建议使用 2 个索引,一个 on TrackHash,一个 on CreatedAt,VoteAND Type(这可能作为 3 个单独的索引表现更好,值得测试这两种方式)。200k 行并不是那么多数据,因此使用正确的索引,查询上个月的数据不会花费太长时间。
还有什么我可以改进的吗?
这在很大程度上是一种平衡行为,它实际上取决于您对最佳处理方式的具体要求。您可以通过 3 种主要方法来解决该问题。
1.你目前的做法(每次查询投票表)
正如之前提到的,我认为这种方法应该可以针对您的应用程序进行扩展。优点是它不需要任何维护,并且发送到应用程序的所有数据都是最新且准确的。缺点是性能,插入数据(由于更新索引)和选择数据可能需要更长的时间。这将是我的首选方法。
2.OLAP方法
这将涉及维护一个汇总表,例如:
CREATE TABLE VoteArchive
( TrackHash CHAR(40) NOT NULL,
CreatedDate DATE NOT NULL,
AppMadeUpVotes INT NOT NULL,
AppMadeDownVotes INT NOT NULL,
ImportedUpVotes INT NOT NULL,
ImportedDownVotes INT NOT NULL,
MergedUpVotes INT NOT NULL,
MergedDownVotes INT NOT NULL,
PRIMARY KEY (CreatedDate, TrackHash)
);
Run Code Online (Sandbox Code Playgroud)
这可以通过运行一个简单的查询每晚填充
INSERT VoteArchive
SELECT TrackHash,
DATE(CreatedAt),
COUNT(CASE WHEN Vote = 'Up' AND Type = 0 THEN 1 END),
COUNT(CASE WHEN Vote = 'Down' AND Type = 0 THEN 1 END),
COUNT(CASE WHEN Vote = 'Up' AND Type = 1 THEN 1 END),
COUNT(CASE WHEN Vote = 'Down' AND Type = 1 THEN 1 END),
COUNT(CASE WHEN Vote = 'Up' AND Type = 2 THEN 1 END),
COUNT(CASE WHEN Vote = 'Down' AND Type = 2 THEN 1 END)
FROM Votes
WHERE CreatedAt > DATE(CURRENT_TIMESTAMP)
GROUP BY TrackHash, DATE(CreatedAt);
Run Code Online (Sandbox Code Playgroud)
然后,您可以使用该表代替实时数据。它的优点是日期是聚集索引的一部分,因此任何受日期限制的查询都应该非常快。这样做的缺点是,如果您查询该表,您只能获得上次填充之前的准确统计信息,但查询速度会快得多。维护查询也是额外的工作。然而,如果我不能查询实时数据,这将是我的第二选择。
3.投票期间更新统计数据
我将其包含在内是为了完整性,但恳请您不要使用此方法。您可以在应用程序层或通过触发器来实现这一点,尽管它确实允许查询最新数据而无需查询“生产”表,但它容易出错,而且我从未遇到过任何真正倡导这一点的人这种方法。对于每次投票,您都需要执行插入/更新逻辑,这应该将非常快的插入查询变成更长的过程,具体取决于您如何进行维护,有机会(尽管并发问题很小)。
4.以上的组合
您始终可以有 2 个与投票表格式相同的表,以及解决方案 2 中列出的一个表,有一个投票表仅用于存储今天的投票,一个用于存储历史投票,并且仍然维护一个汇总表,您可以然后将今天的数据与汇总表结合起来即可获得最新结果,而无需查询大量数据。同样,这是额外的维护,并且更有可能出现问题。
| 归档时间: |
|
| 查看次数: |
275 次 |
| 最近记录: |