Bookmark Lookup, etc..

Merle
Уже с Приветом
Posts: 109
Joined: 26 Sep 2002 12:24

Bookmark Lookup, etc..

Post by Merle »

Волею судеб это долгожданное чудо, по имени Юкон, попало мне в руки.. И вот, по мимо всего прочего, случилось наткнуться на некий любопытный эффект, в связи с которым очень интересно ощественное мнение, особенно Tengiz'а :wink:

Берем старый добрый MSSQL 2000 и прогоняем вот такой запрос по базе Northwind:

Code: Select all

declare @City varchar(100)
set @City = 'Sao Paulo'
select distinct emp.EmployeeID,emp.LastName,emp.FirstName,emp.Title,emp.HireDate
from Employees emp
inner join Orders o on o.EmployeeID = emp.EmployeeID
inner join Customers c on c.CustomerID = o.CustomerID and c.City = @City
inner join [Order Details] od on od.OrderID = o.OrderID
and od.Discount > (select avg(Discount) from [Order Details])

и в плане присутствует совершенно законный bookmark lookup. По моим представлениям ситуация следующая: Сначала идет выборка Customer'ов по City, потом отбираются все заказы, в которых присутствуе нужный CustomerID, а затем надо отобрать всех Employeer'ов по EmployeeID совпадающим с таким же полем в уже отобранных заказах. Но к этому моменту EmployeeID в отобранных заказах не известен, за ним и устраивается bookmark lookup.
Спасает от bookmark lookup, естественно, построение дополнительного составного индекса по Orders(CustomerID, EmployeeID), но вопрос не в этом.

Когда я прогнал этот запрос в Юконе, то никакгого bookmark lookup'а я не обнаружил. А именно план запроса примерно таков:

Code: Select all

 |--Stream Aggregate(GROUP BY:([emp].[EmployeeID]) DEFINE:([emp].[LastName]=ANY([Northwind].[dbo].[Employees].[LastName] as [emp].[LastName]), [emp].[FirstName]=ANY([Northwind].[dbo].[Employees].[FirstName] as [emp].[FirstName]), [emp].[Title]=ANY([Northw
       |--Nested Loops(Inner Join, OUTER REFERENCES:([o].[OrderID], [Expr1011]))
            |--Nested Loops(Inner Join, OUTER REFERENCES:([o].[EmployeeID]))
            |    |--Sort(ORDER BY:([o].[EmployeeID] ASC))
            |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([o].[OrderID]) WITH PREFETCH)
            |    |         |--Nested Loops(Inner Join, OUTER REFERENCES:([c].[CustomerID]))
            |    |         |    |--Nested Loops(Inner Join)
            |    |         |    |    |--Compute Scalar(DEFINE:([Expr1011]=CASE WHEN [Expr1019]=(0) THEN NULL ELSE [Expr1020]/CONVERT_IMPLICIT(float(53),[Expr1019],0) END))
            |    |         |    |    |    |--Stream Aggregate(DEFINE:([Expr1019]=Count(*), [Expr1020]=SUM([Northwind].[dbo].[Order Details].[Discount])))
            |    |         |    |    |         |--Clustered Index Scan(OBJECT:([Northwind].[dbo].[Order Details].[PK_Order_Details]))
            |    |         |    |    |--Index Seek(OBJECT:([Northwind].[dbo].[Customers].[City] AS [c]), SEEK:([c].[City]=CONVERT_IMPLICIT(nvarchar(100),[@City],0)) ORDERED FORWARD)
            |    |         |    |--Index Seek(OBJECT:([Northwind].[dbo].[Orders].[CustomerID] AS [o]), SEEK:([o].[CustomerID]=[Northwind].[dbo].[Customers].[CustomerID] as [c].[CustomerID]) ORDERED FORWARD)
            |    |         |--Compute Scalar(DEFINE:([o].[EmployeeID]=[Northwind].[dbo].[Orders].[EmployeeID] as [o].[EmployeeID]))
            |    |              |--Clustered Index Seek(OBJECT:([Northwind].[dbo].[Orders].[PK_Orders] AS [o]), SEEK:([o].[OrderID]=[Northwind].[dbo].[Orders].[OrderID] as [o].[OrderID]) ORDERED FORWARD)
            |    |--Clustered Index Seek(OBJECT:([Northwind].[dbo].[Employees].[PK_Employees] AS [emp]), SEEK:([emp].[EmployeeID]=[Northwind].[dbo].[Orders].[EmployeeID] as [o].[EmployeeID]) ORDERED FORWARD)
            |--Clustered Index Seek(OBJECT:([Northwind].[dbo].[Order Details].[PK_Order_Details] AS [od]), SEEK:([od].[OrderID]=[Northwind].[dbo].[Orders].[OrderID] as [o].[OrderID]),  WHERE:(CONVERT_IMPLICIT(float(53),[Northwind].[dbo].[Order Details].[Di


Вот я и не могу понять, что в данном случае делает оптимизатор и что означает Compute Scalar(DEFINE:([o].[EmployeeID]=[Northwind].[dbo].[Orders].[EmployeeID] as [o].[EmployeeID]))

Я конечно не знаю в тех ли попугаях меряется стоимость запроса, но этот план гораздо эффективнее чем в восьмерке...

Все здорово, но вот этот запрос:

Code: Select all

declare @region varchar(100)
set @region = 'Eastern'

declare @OrderEnd datetime,@OrderBeg datetime
set @OrderBeg = '1996-03-19 00:00:00.000'
set @OrderEnd = '1998-05-19 00:00:00.000'

select distinct emp.EmployeeID,emp.LastName,emp.FirstName,emp.Title,emp.HireDate
from Employees emp
inner join EmployeeTerritories et on emp.EmployeeID = et.EmployeeID
inner join Territories t on t.TerritoryID = et.TerritoryID
inner join Region r on r.RegionID = t.RegionID and r.RegionDescription = @region
inner join Orders o on o.EmployeeID = emp.EmployeeID and o.OrderDate between @OrderBeg and @OrderEnd

Порождает план уже хуже чем в восьмерке, а именно, присутствуют два table scan, которых восьмерка успешно избегала. Ну и стоимость естественно больше.
Причем похоже тут Юкон руководствуется теми же принципами, что и в предыдущем случае..
Это из-за того, что альфа?

P. S.
На самом деле еще много вопросов по Юкону, Tengiz, Вас можно терзать или это еще совсем закрытая информация?
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Post by tengiz »

В случае 1 оптимизатор вместо Bookmark Lookup + Nested Loop сделал Nested Loop + Sort + Nested Loop. Когда на вход Bookmark Lookup приходит много строк, то что сделано в Yukon может быть эффективнее по очевидным причинам: многократный Bookmark Lookup может быть очень дорогим.

В случае 2 оптимизатор решил, что построение временных хеш-индексов (откуда и берутся сканы) и последующие hash match с участием таблиц Region и Territories будет дешевле, чем план восьмёрки, где есть 2 bookmark lookups.

Так как это бета, то разумеется новые планы могут быть не самыми оптимальными из-за возможных ошибок в оценке стоимости. Производительность не являлась первоочередным целью этой конктретной беты. Главное - посмотреть насколько интересным для пользователей будет новая функциональность сервера. В том числе и новые варианты планов, рассматриваемх оптимизатором.

А у Вас результаты для второго запроса действительно хуже получились?

P.S. По поводу Yukon B1 разумеется можно задавать вопросы - джина выпустили из бутылки вот уже почти как месяц назад на PDC. Поэтому чего тут в конспирацию играть. Был закрытый период первой беты после выдачи её MVP и кое-каким особо интересным и крупным пользователям, а теперь уже всё. Так что задавайте. Но имейте пожалуйста в виду, что моя специализация - Storage Engine, куда из напрямую видимой пользователю функциональности входят подсистема хранения и подсистема обрабоки транзакций. Поэтому на хитрые вопросы на тему оптимизации я могу и не знать ответов.
Cheers
User avatar
Dmitry67
Уже с Приветом
Posts: 28294
Joined: 29 Aug 2000 09:01
Location: SPB --> Gloucester, MA, US --> SPB --> Paris

Post by Dmitry67 »

Слушайте
А Yukon можно как вторую инстанцию поставить ?
А том не на работе SQL 2000 очень нужен для РАБОТЫ
Зарегистрированный нацпредатель, удостоверение N 19719876044787 от 22.09.2014
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Post by tengiz »

Dmitry67 wrote:Слушайте
А Yukon можно как вторую инстанцию поставить ?
А том не на работе SQL 2000 очень нужен для РАБОТЫ

Если речь идёт о первой бете, то можно, но не стоит. Мозги у машины съедут капитально - нужно будет переустанавливать ОС даже вне зависимости от преодолимых проблем с параллельно установленным SQL 2000.
Cheers
Merle
Уже с Приветом
Posts: 109
Joined: 26 Sep 2002 12:24

Post by Merle »

tengiz wrote:В случае 1 оптимизатор вместо Bookmark Lookup + Nested Loop сделал Nested Loop + Sort + Nested Loop. Когда на вход Bookmark Lookup приходит много строк, то что сделано в Yukon может быть эффективнее по очевидным причинам: многократный Bookmark Lookup может быть очень дорогим.

План действительно в юконе дешевле раза в два, если конечно юкон меряет в тех же попугаях,. что и восьмерка.. =)
Но вот отличие в плане заключается в том, что в восьмерке делается Sort + Bookmark Lookup + Nested Loop
А в Юконе Sort + Nested Loop + Compute Scalar, если я ничего не пропустил. Во всем остальном планы идентичны. Вот это-то и непонятно...

tengiz wrote:В случае 2 оптимизатор решил, что построение временных хеш-индексов (откуда и берутся сканы) и последующие hash match с участием таблиц Region и Territories будет дешевле, чем план восьмёрки, где есть 2 bookmark lookups.

Тоесть в этой бэтте просто повысили стоимость bookmark lookup'а, что заставляет оптимизатор пытаться найти более оптимальный план? Или все-таки были внесены более серьезные изменения в оптимизатор?

tengiz wrote:Главное - посмотреть насколько интересным для пользователей будет новая функциональность сервера. В том числе и новые варианты планов, рассматриваемх оптимизатором.

Очень интересно.. =) Самое веселое, что этот запрос показали в форуме и задали вопрос про bookmark lookup. Прежде чем отвечать, я решил прогнать запрос, а поскольку с MSDE база Northwind не поставляется, то я сделал это в юконе и очень удивился не обнаружив искомого bookmark lookup'а.. =)

tengiz wrote:А у Вас результаты для второго запроса действительно хуже получились?

Да, во втором запросе у юкона план хуже чем в восьмерке, правда совсем на чуть-чуть, чуть ли не на сотые в стоимости, опять-таки если они меряются в одинаковых единицах.
Судя по всему оптимальный план лежит где-то по середине, между тем, что выдает восьмерка и юкон. Восьмерка везде делает loop join'ы и, естественно bookmark lookups, юкон почти все переделывает на hash, но не там, где надо.
Самый оптимальный план, судя по всему, в полтора раза лучше, если везде, где юкон пытается подставить hash, оставить loop, но сделать hash в объединении с Orders, тоесть вот так:

Code: Select all

declare @region varchar(100)
set @region = 'Eastern'

declare @OrderEnd datetime,@OrderBeg datetime
set @OrderBeg = '1996-03-19 00:00:00.000'
set @OrderEnd = '1998-05-19 00:00:00.000'

select distinct emp.EmployeeID,emp.LastName,emp.FirstName,emp.Title,emp.HireDate
from Employees emp
inner join EmployeeTerritories et on emp.EmployeeID = et.EmployeeID
inner join Territories t on t.TerritoryID = et.TerritoryID
inner loop join Region r on r.RegionID = t.RegionID and r.RegionDescription = @region
inner hash join Orders o on o.EmployeeID = emp.EmployeeID and o.OrderDate between @OrderBeg and @OrderEnd



tengiz wrote: Так что задавайте. Но имейте пожалуйста в виду, что моя специализация - Storage Engine, куда из напрямую видимой пользователю функциональности входят подсистема хранения и подсистема обрабоки транзакций.

Спасибо... Тогда вопросы по серьезнее постараюсь сформулировать чуть позже, а пока первое на что наткнулся.
В юконе ввели varchar(max) и т.д., с которым можно работать как с обычным varchar'ом, а text, ntext и image теперь obsolete, как я понимаю...
А вопрос такой, насколько сильно изменился в связи с этим формат хранения данных и изменился ли вообще?
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Post by tengiz »

Merle wrote:Но вот отличие в плане заключается в том, что в восьмерке делается Sort + Bookmark Lookup + Nested Loop. А в Юконе Sort + Nested Loop + Compute Scalar, если я ничего не пропустил. Во всем остальном планы идентичны. Вот это-то и непонятно...Тоесть в этой бэтте просто повысили стоимость bookmark lookup'а, что заставляет оптимизатор пытаться найти более оптимальный план? Или все-таки были внесены более серьезные изменения в оптимизатор?

Compute Scalar в этом конкретном случае - это чистый no-op, поэтому его стоимость 0. Что касается стоимости bookmark/index lookup - я так полагаю разница в стоимости планов вылезает из-за разницы в том, что конкретно составляет собственно статистику. Слегка по-другому разложились данные и индексы по страницам (из-за небольших изменений в формате хранения как данных, так и индексов, из-за изменений в коде, который ищет и выделяет страницы и коде расщепления страниц) и уже этой разницы хватает, чтобы оптимальным был выбран другой план просто благодаря другой стоимости сканирования слегка другого количества данных. Посмотрите на конкретные статистики по этим таблицам на SQL Server 2000 и на Yukon - я думаю что Вы увидите разницу, не говоря уже о разнице, которую должен показать sp_spaceused. Я не знаю, включена ли по умолчанию версионность в Northwind, поставляемом с первой бетой (у меня уже нет машин с этой версией сервера) - это тоже даёт разницу в том, как точно разложатся данные по страницам/строкам.

В юконе ввели varchar(max) и т.д., с которым можно работать как с обычным varchar'ом, а text, ntext и image теперь obsolete, как я понимаю...А вопрос такой, насколько сильно изменился в связи с этим формат хранения данных и изменился ли вообще?

Да, text/ntext/image можно забыть и нужно пользоваться (max) типами. В общем, это те же самые LOBs, только с ними работать значительно удобнее - по ним можно строить индексы, можно пользоваться строковыми функциями типа substring, можно заводить переменные этих типов, писать функции возвращающие эти типы и пр. Они действительно хранятся по-другому, например, каждая колонка имеет отдельное хранилище (которое используется если фактические данные в колонке не умещается в строке основной таблицы), в отличие от единой свалки для всех LOB колонок одной талбицы, как это было до Yukon. Но дальнейшими подробностями подетиться не могу, sorry.
Cheers
Merle
Уже с Приветом
Posts: 109
Joined: 26 Sep 2002 12:24

Post by Merle »

tengiz wrote:Да, text/ntext/image можно забыть и нужно пользоваться (max) типами. В общем, это те же самые LOBs, только с ними работать значительно удобнее - по ним можно строить индексы.

Хм... А вот индекс-то построить у меня и не получилось... Да и в BOL написано, что по (max) и xml полям индекс строить низя...
Или Вы имели ввиду, что можно использовать столбцы этого типа в качестве не ключевых, а дополнительных? (index include..)
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Post by tengiz »

Merle wrote:Или Вы имели ввиду, что можно использовать столбцы этого типа в качестве не ключевых, а дополнительных? (index include..)

Нет, Вы меня поняли правильно. Но я подтверждаю, что я действительно наврал, sorry. Незадолго перед выходом B1 возможность создания индексов по (max) типам было решено убрать - факт, который я прозевал. А index include для них, тем не мене, должен нормально работать.
Cheers
Merle
Уже с Приветом
Posts: 109
Joined: 26 Sep 2002 12:24

Post by Merle »

А не могли бы Вы рассказать, по каким причинам эту возможность убрали?
Если, конечно, это не закрытая информация...
И по какому принципу работало индексирование таких полей, ведь ограничение на размер ключа индекса, как я понимаю, никто не отменял...

Return to “Вопросы и новости IT”