Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / Microsoft SQL Server [игнор отключен] [закрыт для гостей] / Знатокам оптимизации / 22 сообщений из 22, страница 1 из 1
21.05.2019, 09:56
    #39815648
Oblom
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
Дано:
Таблица:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
CREATE TABLE [dbo].[Customer](
	[Id] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
	[CustomerGuid] [uniqueidentifier] NOT NULL,
	[Username] [nvarchar](1000) NULL,
	[Email] [nvarchar](1000) NULL,
	[Password] [nvarchar](max) NULL,
	[PasswordFormatId] [int] NOT NULL,
	[PasswordSalt] [nvarchar](max) NULL,
	[AdminComment] [nvarchar](max) NULL,
	[IsTaxExempt] [bit] NOT NULL,
	[AffiliateId] [int] NOT NULL,
	[VendorId] [int] NOT NULL,
	[HasShoppingCartItems] [bit] NOT NULL,
	[Active] [bit] NOT NULL,
	[Deleted] [bit] NOT NULL,
	[IsSystemAccount] [bit] NOT NULL,
	[SystemName] [nvarchar](max) NULL,
	[LastIpAddress] [nvarchar](max) NULL,
	[CreatedOnUtc] [datetime] NOT NULL,
	[LastLoginDateUtc] [datetime] NULL,
	[LastActivityDateUtc] [datetime] NOT NULL,
	[BillingAddress_Id] [int] NULL,
	[ShippingAddress_Id] [int] NULL,
PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO


Запрос:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
declare @p__linq__0 nvarchar(4000)=N'SearchEngine'
SELECT TOP (1) 
    [Project1].[Id] AS [Id], 
    [Project1].[CustomerGuid] AS [CustomerGuid], 
    [Project1].[Username] AS [Username], 
    [Project1].[Email] AS [Email], 
    [Project1].[Password] AS [Password], 
    [Project1].[PasswordFormatId] AS [PasswordFormatId], 
    [Project1].[PasswordSalt] AS [PasswordSalt], 
    [Project1].[AdminComment] AS [AdminComment], 
    [Project1].[IsTaxExempt] AS [IsTaxExempt], 
    [Project1].[AffiliateId] AS [AffiliateId], 
    [Project1].[VendorId] AS [VendorId], 
    [Project1].[HasShoppingCartItems] AS [HasShoppingCartItems], 
    [Project1].[Active] AS [Active], 
    [Project1].[Deleted] AS [Deleted], 
    [Project1].[IsSystemAccount] AS [IsSystemAccount], 
    [Project1].[SystemName] AS [SystemName], 
    [Project1].[LastIpAddress] AS [LastIpAddress], 
    [Project1].[CreatedOnUtc] AS [CreatedOnUtc], 
    [Project1].[LastLoginDateUtc] AS [LastLoginDateUtc], 
    [Project1].[LastActivityDateUtc] AS [LastActivityDateUtc], 
    [Project1].[BillingAddress_Id] AS [BillingAddress_Id], 
    [Project1].[ShippingAddress_Id] AS [ShippingAddress_Id]
    FROM ( SELECT 
        [Extent1].[Id] AS [Id], 
        [Extent1].[CustomerGuid] AS [CustomerGuid], 
        [Extent1].[Username] AS [Username], 
        [Extent1].[Email] AS [Email], 
        [Extent1].[Password] AS [Password], 
        [Extent1].[PasswordFormatId] AS [PasswordFormatId], 
        [Extent1].[PasswordSalt] AS [PasswordSalt], 
        [Extent1].[AdminComment] AS [AdminComment], 
        [Extent1].[IsTaxExempt] AS [IsTaxExempt], 
        [Extent1].[AffiliateId] AS [AffiliateId], 
        [Extent1].[VendorId] AS [VendorId], 
        [Extent1].[HasShoppingCartItems] AS [HasShoppingCartItems], 
        [Extent1].[Active] AS [Active], 
        [Extent1].[Deleted] AS [Deleted], 
        [Extent1].[IsSystemAccount] AS [IsSystemAccount], 
        [Extent1].[SystemName] AS [SystemName], 
        [Extent1].[LastIpAddress] AS [LastIpAddress], 
        [Extent1].[CreatedOnUtc] AS [CreatedOnUtc], 
        [Extent1].[LastLoginDateUtc] AS [LastLoginDateUtc], 
        [Extent1].[LastActivityDateUtc] AS [LastActivityDateUtc], 
        [Extent1].[BillingAddress_Id] AS [BillingAddress_Id], 
        [Extent1].[ShippingAddress_Id] AS [ShippingAddress_Id]
        FROM [dbo].[Customer] AS [Extent1] 
        WHERE ([Extent1].[SystemName] = @p__linq__0) OR (([Extent1].[SystemName] IS NULL) AND (@p__linq__0 IS NULL))
    )  AS [Project1]
    ORDER BY [Project1].[Id] ASC


Распределение данных в таблице:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
select SystemName, count(*) cnt
from Customer
group by SystemName
order by 1

SystemName	cnt
NULL	5603609
ArrHealthyCheck	1
BackgroundTask	1
SearchEngine	1
Warmup	1


По факту выборка снаружи идет только по полям где SystemName IS NOT NULL и всегда возвращается одна строка, но явно в запросе этого нет.
Индекса по SystemName нет (и не может быть из-за типа поля), но есть статистика.

Собственно проблема:
Запускаю
Код: sql
1.
EXEC sp_updatestat


, в плане Clustered Index Scan, Number of Rows Read ~ 22600, запрос работает быстро (~ 100 ms).
Статистика выглядит вот так
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
DBCC SHOW_STATISTICS ('Customer', [_WA_Sys_00000010_36B12243]); 
Name                        Updated              Rows       Rows Sampled  Steps  Density   Average key length  String Index  Filter Expression  Unfiltered Rows
--------------------------- ---- ------------ 	----------- ------------- ------ --------- ------------------- ------------- ------------------ --------------------------
_WA_Sys_00000010_36B12243   May 20 2019  4:24PM  5584561    116167        1      0         0                   YES           NULL               5584561

All density   Average Length Columns
------------- -------------- -----------
1             0              SystemName

RANGE_HI_KEY RANGE_ROWS    EQ_ROWS       DISTINCT_RANGE_ROWS  AVG_RANGE_ROWS
------------ ------------- ------------- -------------------- --------------
NULL         0             5584561       0                    1  


Запускаю
Код: sql
1.
update statistics Customer [_WA_Sys_00000010_36B12243] with fullscan


, в плане тоже Clustered Index Scan, но Numbers of Rows Read ~ 5 600 000, запрос резко замедляется (~ 2000 ms).
Статистика становится такой
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
DBCC SHOW_STATISTICS ('Customer', [_WA_Sys_00000010_36B12243]); 
Name                         Updated              Rows     Rows Sampled  Steps  Density  Average key length String Index Filter Expression  Unfiltered Rows
---------------------------- -------------------- -------- ------------- ------ -------- ------------------ ------------ ------------------ --------------------
_WA_Sys_00000010_36B12243    May 21 2019  1:40AM  5604477  5604477       5      0        1.67723E-05        YES          NULL               5604477

All density   Average Length Columns
------------- -------------- ---------------
0.2           1.67723E-05    SystemName

RANGE_HI_KEY     RANGE_ROWS    EQ_ROWS       DISTINCT_RANGE_ROWS  AVG_RANGE_ROWS
---------------- ------------- ------------- -------------------- --------------
NULL             0             5604473       0                    1
ArrHealthyCheck  0             1             0                    1
BackgroundTask   0             1             0                    1
SearchEngine     0             1             0                    1
Warmup           0             1             0                    1



Вопрос: почему так? Я понимаю, что sp_updatestats делает маленький семпл (в данном случае 116167), а update statics with fullscan делает полный семпл.
Но почему при этом так резко меняется Number of Rows Read, а соответственно и время выполнения запроса? Я как-то думал, что скан - он и есть скан, то есть в любом случае прочитает всё.
А так получается, что с лучшей статистикой запрос работает в разы медленнее, парадокс...
...
Рейтинг: 0 / 0
21.05.2019, 10:24
    #39815660
invm
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
OblomНо почему при этом так резко меняется Number of Rows Read, а соответственно и время выполнения запроса?Либо
- разные запросы
- разные планы
- разные опции итератора Clustered Index Scan
- разные значения @p__linq__0
...
Рейтинг: 0 / 0
21.05.2019, 10:38
    #39815670
komrad
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
Oblom,

покажите
Код: sql
1.
select @@version
...
Рейтинг: 0 / 0
21.05.2019, 10:47
    #39815677
Oblom
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
invmOblomНо почему при этом так резко меняется Number of Rows Read, а соответственно и время выполнения запроса?Либо
- разные запросы
- разные планы
- разные опции итератора Clustered Index Scan
- разные значения @p__linq__0

Да, планы разные, в быстром скане паралеллизм не включается, потому как Estimated Number Of Rows = 2366, а в медленном паралеллизм включается, поскольку Estimated Number Of Rows = 5600990

Запросы и значения параметров одни и те же, картина выглядит просто:
update statistics with fullscan - CPU = 100%
exec sp_updatestats - CPU = 10%

Собственно проблема и появилась после регламентного запуска update statistics with fullscan
Вопрос: почему разный семпл в статистике так сильно влияет на Estimated Number Of Rows, а главное на время выполнения запроса.

На всякий случай приложу оба плана, может кому-то будет интересно посмотреть.
...
Рейтинг: 0 / 0
21.05.2019, 10:47
    #39815678
Oblom
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
И "быстрый" план
...
Рейтинг: 0 / 0
21.05.2019, 10:48
    #39815679
Oblom
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
OblomИ "быстрый" план
...
Рейтинг: 0 / 0
21.05.2019, 10:48
    #39815680
Oblom
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
komradOblom,

покажите
Код: sql
1.
select @@version



Microsoft SQL Server 2014 (SP3-CU1) (KB4470220) - 12.0.6205.1 (X64)
Nov 30 2018 02:59:03
Copyright (c) Microsoft Corporation
Enterprise Edition (64-bit) on Windows NT 6.3 <X64> (Build 14393: )
...
Рейтинг: 0 / 0
21.05.2019, 11:06
    #39815688
komrad
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
OblomПо факту выборка снаружи идет только по полям где SystemName IS NOT NULL и всегда возвращается одна строка, но явно в запросе этого нет.
Индекса по SystemName нет (и не может быть из-за типа поля), но есть статистика.

попробуйте создать фильтрованную статистику по этому полю (is not null)
https://blog.sqlauthority.com/2015/04/07/sql-server-what-is-filtered-statistics/
...
Рейтинг: 0 / 0
21.05.2019, 11:34
    #39815700
Oblom
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
komradOblomПо факту выборка снаружи идет только по полям где SystemName IS NOT NULL и всегда возвращается одна строка, но явно в запросе этого нет.
Индекса по SystemName нет (и не может быть из-за типа поля), но есть статистика.

попробуйте создать фильтрованную статистику по этому полю (is not null)
https://blog.sqlauthority.com/2015/04/07/sql-server-what-is-filtered-statistics/

Создал:
Код: sql
1.
create statistics _WA_Sys_00000010_36B12243_Filtered ON Customer(SystemName) WHERE SystemName IS NOT NULL


ситуация не поменялась, видимо фильтрованной ему мало и он опять полную использует
...
Рейтинг: 0 / 0
21.05.2019, 12:04
    #39815723
TaPaK
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
Oblom,
для интереса попробуйте
OPTION(QUERYTRACEON 4138)
...
Рейтинг: 0 / 0
21.05.2019, 12:07
    #39815729
komrad
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
Oblom[/src]
ситуация не поменялась, видимо фильтрованной ему мало и он опять полную использует
запрос запускали с с option (recompile) ?
...
Рейтинг: 0 / 0
21.05.2019, 12:08
    #39815731
felix_ff
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
Oblom,

Раз используете переменную в предикате попробуйте option (recompile) или optimize for @variable= value
...
Рейтинг: 0 / 0
21.05.2019, 12:39
    #39815751
Oblom
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
спасибо всем за подсказки с изменением запроса.

Однако поменять запрос нетривиальная задача, там коробочное решение и внутрь залезть непросто, плюс сделано всё на LINQ, и по уму надо переписывать на SP или view, чтобы иметь возможность хинтовать.
А совсем уж по уму надо переделывать логику приложения, которая сотню раз в секунду долбится в таблицу клиентов, чтобы получить одну из 4 записей с заполненным полем в таблице с 5.5 млн. строк. И просто для этих 4 пользователей сделать отдельную таблицу и кешировать её вызов минут на 5, а то и на час.
Я нашел другой выход, сменил тип столбца на nvarchar(50) и повесил туда покрывающий индекс.

Но хочется понять, почему величина семпла статистики оказывает такое разрушающее воздействие на план и время выполнения запроса. Больше не меняется ничего, только семпл по которому сделана статистика, ну и гистограмма, которая при этом получается. В "быстрой" гистограмме вообще все значения этого столбца NULL, может в этом дело...
...
Рейтинг: 0 / 0
21.05.2019, 13:22
    #39815775
felix_ff
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
Oblom,

кстати да, посмотрел план у вас там собственно в игру вступает row goal.
флаг предложенный товарищем TaPaK должен по сути менять вашу ситуацию с быстрым планом.

Код: plaintext
1.
<RelOp NodeId="2" PhysicalOp="Clustered Index Scan" LogicalOp="Clustered Index Scan" EstimateRows= "2366.81"  EstimateRowsWithoutRowGoal= "5.60178e+006"  EstimateIO="102.576" EstimateCPU="6.16211" AvgRowSize="22210" EstimatedTotalSubtreeCost="0.0492055" TableCardinality="5.60178e+006" Parallel="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row">
...
Рейтинг: 0 / 0
22.05.2019, 10:27
    #39816227
invm
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
Oblom,

Ваши планы скомпилированы для @p__linq__0 = 'SearchEngine'
Т.к. в одном случае данное значение отсутствует в статистике, а в другом присутствует, получаются разные оценки стоимости.
Одна из этих оценок приводит к параллельному плану. И получается эффект, описанный тут - https://www.sql.kiwi/2012/05/parallel-row-goals-gone-rogue.html
...
Рейтинг: 0 / 0
22.05.2019, 10:33
    #39816235
TaPaK
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
а кто скажет как получилось 22600? в плане без статистики по полю?
...
Рейтинг: 0 / 0
22.05.2019, 12:10
    #39816325
SomewhereSomehow
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
До 2014 если значение выходило за границы гистограммы то вычисление селективности останавливалось, начиная с 2014, когда появилась новая модель оценки, которая используется у ТС, сервер пытается что-то с этим сделать. В частности распознает эту ситуацию, как оценка по возрастающему ключу и пытается моделировать исходя из этого предположения (можно выполнить запрос с недокументированным флагом 9489, который отключает AscendingKeyFilter, чтобы убедиться в этом).

Увидеть процесс можно, включив флаг трассировки 2363 (не документированный), либо событие xEvent query_optimizer_estimate_cardinality.

На примере тестовой таблицы с распределением как у ТС:
TF:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
Plan for computation:
  CSelCalcColumnInInterval
      Column: QCOL: [Extent1].SystemName

Loaded histogram for column QCOL: [Extent1].SystemName from stats with id 2

Calculator failed. Replanning.

Plan for computation:
 CSelCalcAscendingKeyFilter(avg. freq., QCOL: [Extent1].SystemName)

Selectivity: 0.000422441


xEvent:
Код: xml
1.
2.
3.
4.
<CalculatorList>
  <FilterCalculator CalculatorName="CSelCalcColumnInInterval" Selectivity="-1.000" CalculatorFailed="true" TableName="[Extent1]" ColumnName="SystemName" />
  <FilterCalculator CalculatorName="CSelCalcAscendingKeyFilter" Selectivity="0.000" TableName="[Extent1]" ColumnName="SystemName" UseAverageFrequency="true" StatId="2" />
</CalculatorList>


Пытается моделировать по средней частоте, но т.к. средняя частота 1 (т.е. все значения одинаковые), то вместо этого, считает квадратный корень из числа строк (допущение модели).
Код: sql
1.
select sqrt(5601780)--2366,80797700194


То, что мы видим в плане после округления.
...
Рейтинг: 0 / 0
22.05.2019, 12:16
    #39816330
TaPaK
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
SomewhereSomehow,

спасибо!
...
Рейтинг: 0 / 0
22.05.2019, 12:48
    #39816378
Oblom
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
Спасибо всем откликнувшимся! буду курить ссылки и пытаться осознать...
...
Рейтинг: 0 / 0
22.05.2019, 12:57
    #39816391
Oblom
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
SomewhereSomehow,

И последний вопрос, почему так время двух запросов так резко разнится? И там и там скан в запросе, и там и там по идее он должен просканировать все строки и занять это должно примерно одинаковое время...
...
Рейтинг: 0 / 0
22.05.2019, 13:20
    #39816427
TaPaK
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
OblomSomewhereSomehow,

И последний вопрос, почему так время двух запросов так резко разнится? И там и там скан в запросе, и там и там по идее он должен просканировать все строки и занять это должно примерно одинаковое время...
про параллелизм и row goal ссылку уже дали
...
Рейтинг: 0 / 0
22.05.2019, 13:24
    #39816433
Oblom
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Знатокам оптимизации
TaPaKOblomSomewhereSomehow,

И последний вопрос, почему так время двух запросов так резко разнится? И там и там скан в запросе, и там и там по идее он должен просканировать все строки и занять это должно примерно одинаковое время...
про параллелизм и row goal ссылку уже дали

Всё, понял, спасибо, про параллелизм забыл.
...
Рейтинг: 0 / 0
Форумы / Microsoft SQL Server [игнор отключен] [закрыт для гостей] / Знатокам оптимизации / 22 сообщений из 22, страница 1 из 1
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]