Иной раз казалось бы, вполне себе невинный и очень даже простой метод count() может стать серьёзной проблемой для производительности.
Предположим, что у нас есть два доменных класса: Игрок и Предмет. Имеем реляцию: Игрок hasMany Предмет. Классы будут называться: Player, Item. Поле с коллекцией предметов — items. Коллекция предметов нужна нечасто, поэтому для её подгрузки используется lazy-loading.
В случае, если нам необходимо просто подсчитать количество предметов, мы могли бы использовать такой код:
player.items.count()
Но гораздо быстрее будет работать код
Item.countByPlayer(player)
Попробуем разобраться, что же происходит?
В чём разница? Ну, разница в выполняемых запросах. В первому случае, мы выполняем запрос вида: SELECT * FROM Item WHERE Item.Player = Player, затем сохраняем результаты запроса в память, затем считаем их и выдаём результат. Во втором случае, выполнится запрос вида: SELECT COUNT(*) FROM Item WHERE Item.Player = Player GROUP BY (Player). Начнём с того, что данные из таблицы не загружаются в память: чем больше предметов в таблице Item, тем выше будет эффективность метода countByPlayer. Не самый значительный, но отчасти влияющий фактор — размер пакетов, при коммуникации клиента и сервера базы данных. Очевидно, что в случае с countByPlayer мы скачиваем лишь только одно число, в то время как в первом случае — целую таблицу.
Таким образом, если нам действительно необходим lazy-loading (т.е. предметы из коллекции items используются нечасто), целесообразно использовать для подсчётов именно метод countBy…. Если же предметы постоянно подгружаются в память, от lazy loading имеет смысл отказаться. Если размер датасета будет не слишком большой, возможно попробовать считывать данные из базы не в 2 запроса, а в 1: используя lazy loading через JOIN. С MySQL такое вряд ли будет оправданным, а например с тем же PostgreSQL — уже есть смысл подумать.
Не далее, чем два дня назад — подобная оптимизация в одну строчку в одном из наших проектов, значительно повысила производительность всей системы в целом.