<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>kocherov.net &#187; optimization</title>
	<atom:link href="https://kocherov.net/tag/optimization/feed/" rel="self" type="application/rss+xml" />
	<link>https://kocherov.net</link>
	<description>создание и поддержка парсеров, систем сбора и анализа информации</description>
	<lastBuildDate>Thu, 19 Feb 2026 13:03:25 +0000</lastBuildDate>
	<language>ru-RU</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.6.1</generator>
		<item>
		<title>SQL optimization. Join против In и Exists. Что использовать?</title>
		<link>https://kocherov.net/sql-optimization-join-protiv-in-and-exists-chto-ispolzovat/</link>
		<comments>https://kocherov.net/sql-optimization-join-protiv-in-and-exists-chto-ispolzovat/#comments</comments>
		<pubDate>Sun, 09 Dec 2012 14:29:15 +0000</pubDate>
		<dc:creator>Pavel Kocherov</dc:creator>
				<category><![CDATA[Работа]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://kocherov.net/?p=144</guid>
		<description><![CDATA[&#171;Раньше было проще&#187; &#8212; Подумал я, садясь за оптимизацию очередного запроса в SQL management studio. Когда я писал под MySQL, реально все было проще &#8212; или работает, или нет. Или тормозит или нет. Explain решал все мои проблемы, больше ничего не требовалось. Сейчас у меня есть мощная среда разработки, отладки и оптимизации запросов и процедур/функций, [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>&#171;Раньше было проще&#187; &#8212; Подумал я, садясь за оптимизацию очередного запроса в SQL management studio. Когда я писал под MySQL, реально все было проще &#8212; или работает, или нет. Или тормозит или нет. Explain решал все мои проблемы, больше ничего не требовалось. Сейчас у меня есть мощная среда разработки, отладки и оптимизации запросов и процедур/функций, и все это нагромождение создает по-моему только больше проблем. А все почему? Потому что встроенный оптимизатор запросов &#8212; зло. Если в MySQL и PostgreSQL я напишу</p>
<pre class="brush: sql; title: ; notranslate">select * from a, b, c where a.id = b.id, b.id = c.id</pre>
<p>и в каждой из табличек будет хотя бы по 5к строк &#8212; все зависнет. И слава богу! Потому что иначе в разработчике, в лучшем случае, вырабатывается ленность писать правильно, а в худшем он вообще не понимает что делает! Ведь этот же запрос в MSSQL пройдет аналогично</p>
<pre class="brush: sql; title: ; notranslate">select * from a join b on a.id = b.id join c on b.id = c.id</pre>
<p>Встроенный оптимизатор причешет быдлозапрос и все будет окей.</p>
<p>Он так же сам решит, что лучше делать &#8212; exist или join и еще много чего. И все будет работать максимально оптимально.</p>
<p>Только есть одно НО. В один прекрасный момент оптимизатор споткнется о сложный запрос и спасует, и тогда вы получите большущую проблему. И получите вы ее, возможно, не сразу, а когда вес таблиц достигнет критической массы.</p>
<p>Так вот к сути статьи. exists и in &#8212; очень тяжелые операции. Фактически это отдельный подзапрос <strong>для каждой</strong> строчки результата. А если еще и присутствует вложенность, то это вообще туши свет. Все будет окей, когда возвращается 1, 10, 50 строк. Вы не почувствуете разницы, а возможно join  будет даже медленнее. Но когда вытаскивается 500 &#8212; начнутся проблемы. 500 подзапросов в рамках одного запроса &#8212; это серьезно.</p>
<p>Пусть с точки зрения человеческого понимания in и exists лучше, но с точки зрения временных затрат для запросов, возвращающих 50+ строк &#8212; они не допустимы.</p>
<p>Нужно оговориться, что естественно, если где-то убывает &#8212; где-то должно прибывать. Да, join более ресурсоемок по памяти, ведь держать единовременно всю таблицу значений и оперировать ею &#8212; накладнее, чем дергать подзапросы для каждой строки, быстро освобождая память. Нужно смотреть конкретно по запросу и замерять &#8212; критично ли будет использование лишней памяти в угоду времени или нет.</p>
<p>Приведу примеры полных аналогий. Вообще говоря, я не встречал еще запросов такой степени сложности, которые не могли бы быть раскручены в каскад join&#8217;ов. Пусть на это уйдет день, но все можно раскрыть.</p>
<pre class="brush: sql; title: ; notranslate">

select * from a where a.id in (select id from b)

select * from a where exists (select top 1 1 from b where b.id = a.id)

select * from a join b on a.id = b.id

</pre>
<pre class="brush: sql; title: ; notranslate">

select * from a where a.id not in (select id from b)

select * from a where not exists (select top 1 1 from b where b.id = a.id)

select * from a left join b on a.id = b.id where b.id is null

</pre>
<p>Повторюсь &#8212; данные примеры MSSQL оптимизатор оптимизирует под максимальную производительность и на таких простейших запросах тупняков не будет никогда.</p>
<p>Рассмотрим теперь пример реального запроса, который пришлось переписывать из-за того что на некоторых выборках он просто намертво зависал (структура очень упрощена и понятия заменены, не нужно пугаться некоей не оптимальности структуры бд).</p>
<p>Нужно вытащить все дубликаты &#171;продуктов&#187; в разных аккаунтах, ориентируясь на параметры продукта, его группы, и группы-родителя, если таковая есть.</p>
<pre class="brush: sql; title: ; notranslate">

select d.PRODUCT_ID
from PRODUCT s, PRODUCT_GROUP sg
left join M_PG_DEPENDENCY sd on (sg.PRODUCT_GROUP_ID = sd.M_PG_DEPENDENCY_CHILD_ID),
PRODUCT d, PRODUCT_GROUP dg
left join M_PG_DEPENDENCY dd on (dg.PRODUCT_GROUP_ID = dd.M_PG_DEPENDENCY_CHILD_ID)
where s.PRODUCT_GROUP_ID=sg.PRODUCT_GROUP_ID
and d.PRODUCT_GROUP_ID=dg.PRODUCT_GROUP_ID
and sg.PRODUCT_GROUP_PERSPEC=dg.PRODUCT_GROUP_PERSPEC
and sg.PRODUCT_GROUP_NAME=dg.PRODUCT_GROUP_NAME
and s.PRODUCT_NAME=d.PRODUCT_NAME
and s.PRODUCT_TYPE=d.PRODUCT_TYPE
and s.PRODUCT_IS_SECURE=d.PRODUCT_IS_SECURE
and s.PRODUCT_MULTISELECT=d.PRODUCT_MULTISELECT
and dg.PRODUCT_GROUP_IS_TMPL=0
and (
(
	    sd.M_PG_DEPENDENCY_CHILD_ID is null
	    and
	    dd.M_PG_DEPENDENCY_CHILD_ID is null
	  )
	  or exists
	  (
		select 1 from PRODUCT_GROUP sg1, PRODUCT_GROUP dg1
		 where sd.M_PG_DEPENDENCY_PARENT_ID = sg1.PRODUCT_GROUP_ID and
		       dd.M_PG_DEPENDENCY_PARENT_ID = dg1.PRODUCT_GROUP_ID and
		       sg1.PRODUCT_GROUP_PERSPEC=dg1.PRODUCT_GROUP_PERSPEC and
		       sg1.PRODUCT_GROUP_NAME=dg1.PRODUCT_GROUP_NAME and
	  )
	)

</pre>
<p>Так вот это тот случай, когда оптимизатор спасовал. И для каждой строчки выполнялся тяжеленный exists, что убивало базу.</p>
<pre class="brush: sql; title: ; notranslate">

select d.PRODUCT_ID
from PRODUCT s
join PRODUCT d on
    s.PRODUCT_TYPE=d.PRODUCT_TYPE
    and s.PRODUCT_NAME=d.PRODUCT_NAME
    and s.PRODUCT_IS_SECURE=d.PRODUCT_IS_SECURE
    and s.PRODUCT_MULTISELECT=d.PRODUCT_MULTISELECT
join PRODUCT_GROUP sg on s.PRODUCT_GROUP_ID=sg.PRODUCT_GROUP_ID
join PRODUCT_GROUP dg on d.PRODUCT_GROUP_ID=dg.PRODUCT_GROUP_ID
    and sg.PRODUCT_GROUP_NAME=dg.PRODUCT_GROUP_NAME
    and sg.PRODUCT_GROUP_PERSPEC=dg.PRODUCT_GROUP_PERSPEC
left join M_PG_DEPENDENCY sd on sg.PRODUCT_GROUP_ID = sd.M_PG_DEPENDENCY_CHILD_ID
left join M_PG_DEPENDENCY dd on dg.PRODUCT_GROUP_ID = dd.M_PG_DEPENDENCY_CHILD_ID
left join PRODUCT_GROUP sgp on sgp.PRODUCT_GROUP_ID = sd.M_PG_DEPENDENCY_PARENT_ID
left join PRODUCT_GROUP dgp on
    dgp.PRODUCT_GROUP_ID = dd.M_PG_DEPENDENCY_PARENT_ID
    and sgp.PRODUCT_GROUP_NAME = dgp.PRODUCT_GROUP_NAME
    and isnull(sgp.PRODUCT_GROUP_IS_TMPL, 0) = isnull(dgp.PRODUCT_GROUP_IS_TMPL, 0)
where
	  (
		sd.M_PG_DEPENDENCY_CHILD_ID is null
		and
		dd.M_PG_DEPENDENCY_CHILD_ID is null
	  )
	  or
	  (
		sgp.PRODUCT_GROUP_NAME is not null
		and
		dgp.PRODUCT_GROUP_NAME is not null
	  )
go

</pre>
<p>После данных преобразований производительность вьюхи увеличилась экспоненциально количеству найденных продуктов. Вернее сказать, время поиска оставалось практически независимым от числа совпадений и было всегда очень маленьким. Как и должно быть.</p>
<p><strong>Это наглядный пример того, как доверие MSSQL оптимизатору может сыграть злую шутку. Не доверяйте ему, не ленитесь, join&#8217;те ручками, каждый раз думайте что лучше в данной ситуации &#8212; exists, in или join.</strong></p>
]]></content:encoded>
			<wfw:commentRss>https://kocherov.net/sql-optimization-join-protiv-in-and-exists-chto-ispolzovat/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
