powered by simpleCommunicator - 2.0.60     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Microsoft SQL Server [игнор отключен] [закрыт для гостей] / Знатокам оптимизации
22 сообщений из 22, страница 1 из 1
Знатокам оптимизации
    #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
Знатокам оптимизации
    #39815660
invm
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
OblomНо почему при этом так резко меняется Number of Rows Read, а соответственно и время выполнения запроса?Либо
- разные запросы
- разные планы
- разные опции итератора Clustered Index Scan
- разные значения @p__linq__0
...
Рейтинг: 0 / 0
Знатокам оптимизации
    #39815670
Фотография komrad
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oblom,

покажите
Код: sql
1.
select @@version
...
Рейтинг: 0 / 0
Знатокам оптимизации
    #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
Знатокам оптимизации
    #39815678
Oblom
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
И "быстрый" план
...
Рейтинг: 0 / 0
Знатокам оптимизации
    #39815679
Oblom
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
OblomИ "быстрый" план
...
Рейтинг: 0 / 0
Знатокам оптимизации
    #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
Знатокам оптимизации
    #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
Знатокам оптимизации
    #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
Знатокам оптимизации
    #39815723
TaPaK
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oblom,
для интереса попробуйте
OPTION(QUERYTRACEON 4138)
...
Рейтинг: 0 / 0
Знатокам оптимизации
    #39815729
Фотография komrad
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oblom[/src]
ситуация не поменялась, видимо фильтрованной ему мало и он опять полную использует
запрос запускали с с option (recompile) ?
...
Рейтинг: 0 / 0
Знатокам оптимизации
    #39815731
felix_ff
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oblom,

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

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

Но хочется понять, почему величина семпла статистики оказывает такое разрушающее воздействие на план и время выполнения запроса. Больше не меняется ничего, только семпл по которому сделана статистика, ну и гистограмма, которая при этом получается. В "быстрой" гистограмме вообще все значения этого столбца NULL, может в этом дело...
...
Рейтинг: 0 / 0
Знатокам оптимизации
    #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
Знатокам оптимизации
    #39816227
invm
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oblom,

Ваши планы скомпилированы для @p__linq__0 = 'SearchEngine'
Т.к. в одном случае данное значение отсутствует в статистике, а в другом присутствует, получаются разные оценки стоимости.
Одна из этих оценок приводит к параллельному плану. И получается эффект, описанный тут - https://www.sql.kiwi/2012/05/parallel-row-goals-gone-rogue.html
...
Рейтинг: 0 / 0
Знатокам оптимизации
    #39816235
TaPaK
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
а кто скажет как получилось 22600? в плане без статистики по полю?
...
Рейтинг: 0 / 0
Знатокам оптимизации
    #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
Знатокам оптимизации
    #39816330
TaPaK
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SomewhereSomehow,

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

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

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

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

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


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