У меня есть база данных, в которой хранятся результаты шутера. Я положил их в 3NF, чтобы разрешить расширения системы. Так это выглядит так:
Player
-------------------
GameId integer
PlayerId integer
TeamId integer
Hits
-------------------
GameId integer
FromId integer
ToId integer
Hits integer
Таким образом, в основном для каждой игры есть идентификатор, и у каждого игрока и команды есть свой идентификатор (их имена хранятся в других базах данных).
Теперь я хочу подсчитать очки для каждого игрока. Мне нужны очки за каждую игру, но, что более важно, общее количество на игрока. Очки в основном: 3 очка за каждое попадание по противнику, -2 очка за каждое попадание члена команды и -2 очка за каждое нанесенное попадание.
В одиночку для подсчета количества попаданий команды требуется соединение с 3 таблицами, и я боюсь за производительность в производственной среде. (В каждой игре ~ 8 игроков-> PlayerDB-Size равен 8n, а HitsDB-Size равен (8-1) ^ 2 * n)
И в конце: мне нужно рассчитать очки на игрока для каждой игры и суммировать их, потому что минимальное количество очков за игру должно быть равно нулю. И, наконец, получить ранг для каждого игрока (игрок х имеет 2-е место по количеству очков и т. Д.)
Я чувствую, что теряюсь в слишком сложных запросах, которые в какой-то момент снижают производительность базы данных.
Может ли кто-нибудь судить о дизайне и, возможно, дать мне несколько советов, с чего начать искать дальше? Я думал о том, чтобы хранить TeamHits и очки за игру в базе данных игроков (очки за их суммирование, teamHits для статистических целей), но это, конечно, нарушило бы нормализацию.
PS: я работаю с PHP 5 и MYSQL. Я также думал о том, чтобы получить каждую игру из базы данных, вычислить баллы в PHP (что я уже делаю, когда я показываю игру) и записать это обратно (оптимально при добавлении игры в БД, а также когда параметры для очки меняются)
Изменить: Идея, чтобы избежать подвыборов будет:
SELECT p.*, SUM(h.Hits) AS TeamHits, SUM(h2.Hits) as Hits
FROM player p
LEFT JOIN
(hits h
INNER JOIN player p2
ON h.GameId=p2.GameId AND h.ToId=p2.PlayerId
)
ON p.GameId=p2.GameId AND h.FromId=p.PlayerId AND p.TeamId=p2.TeamId
GROUP BY p.PlayerId, p.GameId
LEFT JOIN hits h2
ON h2.GameId=p.GameId AND h2.FromId=p.PlayerId
Но, конечно, это не работает. Можно ли даже объединить группировки с объединениями или мне придется использовать подзапросы?
Лучшее, что у меня есть:
SELECT p.PlayerId, SUM((-2-3)*IFNULL(th.TeamHits, 0) + (3)*IFNULL(h.Hits, 0) + (-2)*IFNULL(ht.HitsTaken, 0)) AS Points
FROM player p
LEFT JOIN
(SELECT p.GameId, p.PlayerId, SUM(h.Hits) AS TeamHits
FROM player p
INNER JOIN hits h
ON h.GameId=p.GameId AND p.PlayerId=h.FromId
INNER JOIN player p2
ON p.GameId=p2.GameId AND p2.PlayerId=h.ToId AND p.TeamId=p2.TeamId
GROUP BY p.PlayerId, p.GameId) th
ON p.GameId=th.GameId AND p.PlayerId=th.PlayerId
LEFT JOIN
(SELECT p.GameId, p.PlayerId, SUM(h.Hits) AS Hits
FROM player p
INNER JOIN hits h
ON h.GameId=p.GameId AND p.PlayerId=h.FromId
GROUP BY p.PlayerId, p.GameId) h
ON p.GameId=h.GameId AND p.PlayerId=h.PlayerId
LEFT JOIN
(SELECT p.GameId, p.PlayerId, SUM(h.Hits) AS HitsTaken
FROM player p
INNER JOIN hits h
ON h.GameId=p.GameId AND p.PlayerId=h.ToId
INNER JOIN player p2
ON p.GameId=p2.GameId AND p2.PlayerId=h.FromId AND p.TeamId!=p2.TeamId
GROUP BY p.PlayerId, p.GameId) ht
ON p.GameId=ht.GameId AND p.PlayerId=ht.PlayerId
GROUP BY p.PlayerId
Скрипки: http://sqlfiddle.com/#!9/dc0cb/4
Текущая проблема: для базы данных с около 10000 игр подсчет очков для всех игроков занимает около 18 секунд. Это непригодно, поэтому мне нужно улучшить это …
Присоединения не так уж дороги, подзапросы. до тех пор, пока вы можете избегать подзапросов, вы не наносите слишком большой удар.
Помните, что для этих вещей в наши дни создана база данных.
Просто убедитесь, что у вас есть правильные индексы на правильных полях, так что это оптимизировано. Как teamID и GameID, так и playerID должны быть индексами.
Просто запустите его в phpmyadmin и посмотрите, сколько миллисекунд потребуется для его выполнения. если он требует более 50, это тяжелый запрос, но обычно его довольно сложно решить … Мне однажды удалось сделать очень тяжелый запрос, который объединял более 100 000 строк из разных таблиц и представлений, и все же делал это за 5 мс .. ,
О каком количестве запросов в час мы говорим? 200 игроков в день? 200.000 игроков в день? Как часто происходят запросы? 10 в секунду на игрока? раз в минуту? Насколько загружена ваша база данных?
Я думаю, что все эти параметры низкие, поэтому вам пока не стоит беспокоиться об этой оптимизации.
Запустите вашу игру и начните работу, очистите php-код, где можно получить реальные выгоды, и держитесь подальше от сложных подзапросов или представлений.
Пока ваш стол присоединяется и объединяется, это чертовски быстро. и если вы должны выполнить подзапрос, посмотрите, нет ли альтернативного способа, используя таблицу связывания, чтобы связать определенные результаты с некоторыми другими таблицами, чтобы вы могли выполнить объединение вместо подзапроса.
Других решений пока нет …