Поговорим о морских хищниках? Все, конечно, знают об акулах, но на самом деле морская жизнь настолько разнообразна, что можно провести целые дни, изучая различные виды хищников. Один из самых удивительных и опасных хищников — рак-богомол.
Эти животные — совершенные хищники. Способность рака-богомола взмахивать своими «битами» настолько сильна, что вода от удара буквально закипает и схлопывается с характерным щелчком, а рыбы и моллюски, попавшие под удар, могут быть оглушены или убиты мгновенно. Некоторые виды рака-богомола могут прыгать на расстояние в несколько своих тел, чтобы схватить добычу.
Кроме того, рак-богомол обладает удивительным зрением. Глаза у него могут двигаться независимо друг от друга, что позволяет ему воспринимать окружающую среду с большой точностью. Также у этого животного есть способность воспринимать невероятное количество цветов и даже различать поляризацию света.
Это животное настолько удивительно, что есть даже комикс, посвященный ему — «Подводный кошмар«. Настоятельно рекомендую к прочтению.
Не секрет, что один и тот же запрос можно написать с помощью SQL множеством разных способов. В идеальном мире с безупречным оптимизатором все эти способы должны работать за одинаковое время. Но, к счастью, наш мир не идеален, и оптимизаторы тоже довольно далеки от совершенства. Именно поэтому у программистов до сих пор есть работа.
Обычно проблемы с производительностью решаются с помощью индексов. В принципе это правильный подход, но всегда нужно помнить, что индексы требуют дополнительное дисковое пространство и негативно влияют на скорость операций изменения данных. Сегодня я предлагаю рассмотреть простой запрос, и попытаться ускорить его не используя индексы, а просто меняя его формулировку на SQL.
Нам предстоит работать с таблицей person из примерно 300 тысяч строк следующего вида:
id
first_name
middle_name
last_name
mother_id
father_id
Уникальный идентификатор человека
Имя
Отчество
Фамилия
Ссылка на id матери в этой же таблице (person)
Ссылка на id отца в этой же таблице (person)
Задача довольно тривиальная — необходимо подсчитать количество детей у каждого человека. То есть на выходе требуется таблица вида:
id
first_name
middle_name
last_name
child_cnt
Уникальный идентификатор человека
Имя
Отчество
Фамилия
Количество детей у указанного человека
Самый тривиальный вариант, который обычно пишут новички в SQL содержит подзапрос в списке столбцов:
SELECT p.id,
p.last_name,
p.first_name,
p.middle_name,
(
SELECT COUNT(*) AS child_cnt
FROM dbo.person pp
WHERE pp.father_id = p.id
OR pp.mother_id = p.id
) AS child_cnt
FROM dbo.person p
ORDER BY p.id;
Надеюсь, вы понимаете в чём проблема этого запроса. Для каждой(!) строки результирующей выборки (а их 300 тысяч) будет выполнен подзапрос с подсчётом количества детей. Суммарная стоимость этого варианта составляет 178230. Что касается времени выполнения, то я так и не дождался результата, поэтому условно будем считать час (на самом деле намного больше).
Казалось бы очевидным улучшением является подсчёт количества детей с помощью соединения таблицы с собой:
SELECT p.id,
COUNT(p2.father_id)+COUNT(p2.mother_id) AS child_cnt
FROM dbo.person p
LEFT JOIN dbo.person p2
ON p2.father_id = p.id
OR p2.mother_id = p.id
GROUP BY p.id;
Но оптимизатор так не считает. Оценка этого варианта 455435, а в плане виден ненавистный спулинг.
Тем не менее если мы заменим left join на inner join, мы получим список только тех людей, у кого есть хотя бы один ребёнок, но зато с оценкой в ~64.
Путём небольшой доработки, мы сможем добавить информацию о людях без детей и получим запрос, который вернёт нам ровно ту информацию, которая требуется:
SELECT p.id,
p.last_name,
p.first_name,
p.middle_name,
COALESCE(r.child_cnt, 0) AS child_cnt
FROM dbo.person p
LEFT JOIN
(
SELECT p.id,
COUNT(*) AS child_cnt
FROM dbo.person p
INNER JOIN dbo.person p2
ON p2.father_id = p.id
OR p2.mother_id = p.id
GROUP BY p.id
) r
ON p.id = r.id;
Время выполнения этого запроса девять секунд. В принципе, на этом можно было бы остановиться, но ради спортивного интереса продолжим.
Очевидно, что идея подсчитать количество детей для тех, у кого они есть, а потом дополнить этот список оставшимися людьми довольно эффективная. Так как «обёртка»-дополнение во всех случаях будет одинаковой , сосредоточимся на подсчёте детей у тех, у кого они есть.
SELECT p.id,
COUNT(*) AS child_cnt
FROM dbo.person p
INNER JOIN dbo.person p2
ON p2.father_id = p.id
GROUP BY p.id
UNION ALL
SELECT p.id,
COUNT(*) AS child_cnt
FROM dbo.person p
INNER JOIN dbo.person p2
ON p2.mother_id = p.id
GROUP BY p.id;
В этом варианте запроса с оценкой ~15, мы избавляемся от сложного оператора OR в join. Вместо этого мы подсчитываем у скольких людей конкретная персона является отцом, аналогично считаем у скольких людей она является матерью, а результаты просто объединяем.
Но на самом деле можно поступить ещё проще. Нам нет необходимости проверять каждую персону на наличие детей. Мы можем просто посчитать сколько раз каждый человек появлялся в столбцах mother_id / father_id и таким образом получить искомое количество детей.
SELECT r.parent_id,
COUNT(*) AS child_cnt
FROM
(
SELECT mother_id AS parent_id
FROM dbo.person
WHERE (1 = 1)
AND (mother_id IS NOT NULL)
UNION ALL
SELECT father_id
FROM dbo.person
WHERE (1 = 1)
AND (father_id IS NOT NULL)
) r
GROUP BY r.parent_id;
С оценкой ~5 мы считаем требуемые данные всего за одну секунду.
Разумеется, как я сказал ещё в самом начале, гораздо проще было бы ускорить этот запрос, просто добавив индекс. Но разве не здорово просто переформулировав то, что ты хочешь получить от сервера, добиться ускорения в сотни раз?
—
Уже после публикации этой записи мне пришла в голову ещё одна идея. Очевидно, что использование OR в подзапросе существенно замедляет его. Что если оставить подзапрос в SELECT, но при этом разбить его на две части?
SELECT p.id,
p.last_name,
p.first_name,
p.middle_name,
(
SELECT COUNT(*) AS child_cnt
FROM dbo.person pp
WHERE pp.father_id = p.id
) +
(
SELECT COUNT(*) AS child_cnt
FROM dbo.person pp
WHERE pp.mother_id = p.id
) AS child_cnt
FROM dbo.person p
ORDER BY p.id;
Мы видим, что при таком запросе оптимизатор догадался сначала сгруппировать данные по матерям и отцам, а потом собрать итоговую таблицу используя быстрый MERGE JOIN.
Даже такой оптимизации уже было бы достаточно и вместо долгого ожидания мы бы получили результат всего за несколько секунд.
Дендронефтия — это красивый коралл, но очень сложный в содержании. Я бы не рекомендовал его никому без опыта содержания нефотосинтетических кораллов. Начните лучше с простых NPS вроде Tubastrea.
Дендронефтия требует частого, почти постоянного, кормления в первую очередь фитопланктоном, слабого освещения, сильного турбулентного течения, постоянного уровня йода в морской воде и, судя по всему, определённой доли удачи.
Если вам повезёт и вы сумеете обеспечить для неё хорошие условия, Dendronephthya порадует вас своими раскрытыми полипами и неземными цветами.
Чалисы — это группа жёстких кораллов, которые обычно объединяют под общим неофициальным названием. В эту группу входят такие роды, как Pectinia, Echinophyllia, Oxypora, Echinopora, Mycedium и Echinomorpha.
Все чалисы отличаются своей красотой, формой роста и требованиями к условиям содержания, таким как слабое освещение и умеренное течение. Аквариумисты почти никогда не знают точное видовое название своих чалисов. Многие из них известны под торговыми названиями, такими как Toxic pie, Desert sunset, Mummy eye и другие. На фотографии показан коралл с названием Gold Meister, который, скорее всего, относится к роду Echinophyllia. Если вы решите завести такой коралл, помните, что он будет активно расти и может проявлять агрессию к соседям.
Содержать снежную мурену несложно, но для этого потребуется плотная крышка, чтобы предотвратить её побег из аквариума. В качестве пищи они предпочитают мясные продукты, такие как креветки, кальмары и осьминоги. Не рекомендуется кормить их пресноводными рыбками, так как это может привести к заболеванию печени.
Совместимость мурен с рифовыми аквариумами является условной. Они не наносят прямого вреда кораллам, но могут препятствовать жизни мелких рыб и креветок. Я лично видел, как мурена в магазине съела апогона. Кроме того, из-за особенностей пищеварения мурены могут вызывать быстрое накопление нитратов и фосфатов в аквариуме, что может негативно сказаться на кораллах, если система фильтрации не будет справляться.
Рикордеи относятся к отряду Corallimorpharia и образуют отдельное семейство Ricordeidae с единственным родом Ricordea.
В рамках этого рода выделяются три вида: R. fungiforme, R. florida и R. yuma. Однако первый вид признан не всеми учёными, так как его первоначальное описание нечёткое и неполное. Два последних вида отличаются расположением щупалец: у R. yuma рот находится в щупальцах, а у R. florida его нет. Кроме того, R. florida образует многоротые колонии, что является уникальным признаком в рамках рода.