|
Оптимизатор SQL не справляется с запросом из RLS | ☑ | ||
---|---|---|---|---|
0
raytan
04.11.19
✎
14:50
|
Сильно переделанная УПП 1.3 на 8.2, MySQL 2005. Используется RLS. Столкнулся с такой проблемой, что в зависимости от того какие есть "Группы доступа" у пользователя очень сильно различается скорость чтения данных. Стал изучать проблему. Для этого переделал шаблон из RLS в обычный запрос и проверяю время выполнения. Разница между пользователями огромна: 0.14 с. и 40 с. время выполнения.
Запрос стандартный: ВЫБРАТЬ ПЕРВЫЕ 50 //Ограничил ПЕРВЫЕ 50 для ускорения выполнения ТекущаяТаблица.Ссылка, ТекущаяТаблица.Реквизит1 //и т.д. ИЗ Документ.ТаблицаККоторойНеобходимоПолучитьДоступ КАК ТекущаяТаблица ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ РАЗЛИЧНЫЕ СоставГруппы.Ссылка КАК ГруппаПользователей ИЗ Справочник.ГруппыПользователей.ПользователиГруппы КАК СоставГруппы ГДЕ СоставГруппы.Пользователь = &ТекущийПользователь) КАК ГруппыПользователей ПО (ИСТИНА) ГДЕ НЕ ГруппыПользователей.ГруппаПользователей ЕСТЬ NULL И НЕ 1 В (ВЫБРАТЬ ПЕРВЫЕ 1 1 ИЗ РегистрСведений.НазначениеВидовОбъектовДоступа КАК НазначениеВидовОбъектовДоступа ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НастройкиПравДоступаПользователей КАК НастройкиПравДоступаПользователей ПО (НастройкиПравДоступаПользователей.ОбъектДоступа = ВЫБОР КОГДА НазначениеВидовОбъектовДоступа.ВидОбъектаДоступа = ЗНАЧЕНИЕ(Перечисление.ВидыОбъектовДоступа.Организации) ТОГДА ТекущаяТаблица.Организация КОГДА НазначениеВидовОбъектовДоступа.ВидОбъектаДоступа = ЗНАЧЕНИЕ(Перечисление.ВидыОбъектовДоступа.Подразделения) ТОГДА ТекущаяТаблица.Подразделение КОГДА НазначениеВидовОбъектовДоступа.ВидОбъектаДоступа = ЗНАЧЕНИЕ(Перечисление.ВидыОбъектовДоступа.СтатьиДДС) ТОГДА ТекущаяТаблица.СтатьяДвиженияДенежныхСредств КОНЕЦ ) И НастройкиПравДоступаПользователей.ВидОбъектаДоступа = НазначениеВидовОбъектовДоступа.ВидОбъектаДоступа И НастройкиПравДоступаПользователей.ОбластьДанных = ЗНАЧЕНИЕ(Перечисление.ОбластиДанныхОбъектовДоступа.ПустаяСсылка) И НастройкиПравДоступаПользователей.Пользователь = ГруппыПользователей.ГруппаПользователей ГДЕ НазначениеВидовОбъектовДоступа.ГруппаПользователей = ГруппыПользователей.ГруппаПользователей И НазначениеВидовОбъектовДоступа.ВидОбъектаДоступа В ( ЗНАЧЕНИЕ(Перечисление.ВидыОбъектовДоступа.ПустаяСсылка), ЗНАЧЕНИЕ(Перечисление.ВидыОбъектовДоступа.Организации), ЗНАЧЕНИЕ(Перечисление.ВидыОбъектовДоступа.Подразделения), ЗНАЧЕНИЕ(Перечисление.ВидыОбъектовДоступа.СтатьиДДС) ) И НастройкиПравДоступаПользователей.ОбъектДоступа ЕСТЬ NULL ) Посмотрел план запроса в SQL Profilere. Он одинаковый для "быстрого" и "медленного" пользователей. За единственным исключением: у "медленного" пользователя громадное значение "Actual number jf rows", равное количеству строк в нашей таблице или для некоторых операций равное произведению итераций на количество строк таблицы. "Estimeted number of rows" равен 1. Для "быстрого" пользователя данные этих параметров вполне нормальные. К примеру: "A-number" = 250, "E-number" = 55. Из различий между двумя пользователями можно выделить: "быстрый" много работает с этой таблицей, в его "группах доступа" указаны те элементы, которые часто используются. У "медленного" -- наоборот, всё редко. Как я понимаю, проблема в статистике. Пробовал обновить статистику для конкретной таблицы с помощью "Update statistic with fullscan". Кроме того ночью обновляется статистика всей базы. Ни то, ни другое не помогло. Есть мысль, что "медленного" пользователя очень нерепрезентативная выборка и данных мало, поэтому оптимизатор не пользуется статистикой. Собственно вопрос: кто с чем то подобным сталкивался ? Что вообще можно сделать в такой ситуации ? Как возможна разница во времени выполнения одного и того же запроса в 280 раз ?.. |
|||
1
КнОпка
04.11.19
✎
14:56
|
А где Разрешенные ?
ВЫБРАТЬ разрешенные ПЕРВЫЕ 50 |
|||
2
raytan
04.11.19
✎
15:03
|
(1) Думаете это повлияет на результат замеров времени выполнения ? Проблема же в том, что один и тот же запрос оптимизатор по разному трактует..
|
|||
3
raytan
04.11.19
✎
15:04
|
(1) Тем более я же тестирую под полными правами. Какая разница есть флаг разрешенные или нет ?
|
|||
4
fisher
04.11.19
✎
15:05
|
Ну дык группы доступа же разные. Т.е. разные входные данные. А статистика для всех одна.
Что делать? Побуду совой-стратегом: не использовать типовые RLS на больших объемах данных. |
|||
5
Sapiens_bru
04.11.19
✎
15:05
|
(0) Одинаковые планы, выбирающие разное количество строк? Где-то тут нестыковочка. Может они всё же не одинаковые? Например и там и там CLUSTERED INDEX SEEK , но предикаты поиска разные.
|
|||
6
raytan
04.11.19
✎
15:17
|
(5) Согласен как то странно звучит. Но я максимально внимательно прошелся по плану и увидел различие только в "Actual number of rows". А так конечно вы правы, что наличие или отсутствие предиката в Индекс сике все меняет. Но у меня например есть и индекс скан, в котором все одинаково кроме числа строк..
|
|||
7
raytan
04.11.19
✎
15:18
|
(4) 150 тысяч записей в таблице -- это не так уж и много
|
|||
8
mistеr
04.11.19
✎
15:25
|
(4) Другой вариант — апгрейдить древний скуль на современный, гда оптимизатор умнее и умеет учитывать "разные входные данные".
|
|||
9
mistеr
04.11.19
✎
15:26
|
(6) Выложил бы планы (в тексте).
|
|||
10
raytan
04.11.19
✎
15:29
|
Rows Executes StmtText StmtId NodeId Parent PhysicalOp LogicalOp Argument DefinedValues EstimateRows EstimateIO EstimateCPU AvgRowSize TotalSubtreeCost OutputList Warnings Type Parallel EstimateExecutions
---- -------- -------- ------ ------ ------ ---------- --------- -------- ------------- ------------ ---------- ----------- ---------- ---------------- ---------- -------- ---- -------- ------------------ 3 1 Top(TOP EXPRESSION:((50))) 0 0 Top Top TOP EXPRESSION:((50)) 50 0 5E-006 177 0,164991 [T1].[_IDRRef], [T1].[_Fld13011RRef], [T1].[_Fld13013RRef], [T1].[_Fld13020], [T1].[_Fld13021RRef], [T1].[_Fld13040RRef], [T1].[_Fld13041RRef], [T1].[_Fld13042RRef], [T1].[_Fld13061RRef], [T1].[_Fld13082RRef], [T3].[_Reference77_IDRRef] PLAN_ROW 0 1 3 1 |--Nested Loops(Left Anti Semi Join, OUTER REFERENCES:([T1].[_Fld13011RRef], [T1].[_Fld13042RRef], [T1].[_Fld13061RRef], [T3].[_Reference77_IDRRef])) 0 1 0 Nested Loops Left Anti Semi Join OUTER REFERENCES:([T1].[_Fld13011RRef], [T1].[_Fld13042RRef], [T1].[_Fld13061RRef], [T3].[_Reference77_IDRRef]) 50 0 3,77511 177 0,164986 [T1].[_IDRRef], [T1].[_Fld13011RRef], [T1].[_Fld13013RRef], [T1].[_Fld13020], [T1].[_Fld13021RRef], [T1].[_Fld13040RRef], [T1].[_Fld13041RRef], [T1].[_Fld13042RRef], [T1].[_Fld13061RRef], [T1].[_Fld13082RRef], [T3].[_Reference77_IDRRef] PLAN_ROW 0 1 902310 1 |--Nested Loops(Inner Join) 0 2 1 Nested Loops Inner Join 55,5556 0 3,77511 177 0,00931134 [T1].[_IDRRef], [T1].[_Fld13011RRef], [T1].[_Fld13013RRef], [T1].[_Fld13020], [T1].[_Fld13021RRef], [T1].[_Fld13040RRef], [T1].[_Fld13041RRef], [T1].[_Fld13042RRef], [T1].[_Fld13061RRef], [T1].[_Fld13082RRef], [T3].[_Reference77_IDRRef] NO JOIN PREDICATE PLAN_ROW 0 1 150385 1 | |--Clustered Index Scan(OBJECT:([LocalBase].[dbo].[_Document517].[PK___Document517NG__2E5FFDE1] AS [T1])) 0 3 2 Clustered Index Scan Clustered Index Scan OBJECT:([LocalBase].[dbo].[_Document517].[PK___Document517NG__2E5FFDE1] AS [T1]) [T1].[_IDRRef], [T1].[_Fld13011RRef], [T1].[_Fld13013RRef], [T1].[_Fld13020], [T1].[_Fld13021RRef], [T1].[_Fld13040RRef], [T1].[_Fld13041RRef], [T1].[_Fld13042RRef], [T1].[_Fld13061RRef], [T1].[_Fld13082RRef] 9,25078 15,9142 0,16558 161 0,00416513 [T1].[_IDRRef], [T1].[_Fld13011RRef], [T1].[_Fld13013RRef], [T1].[_Fld13020], [T1].[_Fld13021RRef], [T1].[_Fld13040RRef], [T1].[_Fld13041RRef], [T1].[_Fld13042RRef], [T1].[_Fld13061RRef], [T1].[_Fld13082RRef] PLAN_ROW 0 1 902310 150385 | |--Stream Aggregate(GROUP BY:([T3].[_Reference77_IDRRef])) 0 4 2 Stream Aggregate Aggregate GROUP BY:([T3].[_Reference77_IDRRef]) 6,0055 0 6,0055E-006 23 0,00486364 [T3].[_Reference77_IDRRef] PLAN_ROW 0 10,2507 902310 150385 | |--Index Seek(OBJECT:([LocalBase].[dbo].[_Reference77_VT2072].[_Referenc77_VT2072_ByField2075_RR] AS [T3]), SEEK:([T3].[_Fld2074RRef]=[@P1]) ORDERED FORWARD) 0 5 4 Index Seek Index Seek OBJECT:([LocalBase].[dbo].[_Reference77_VT2072].[_Referenc77_VT2072_ByField2075_RR] AS [T3]), SEEK:([T3].[_Fld2074RRef]=[@P1]) ORDERED FORWARD [T3].[_Reference77_IDRRef] 6,0055 0,003125 0,000163606 23 0,00480208 [T3].[_Reference77_IDRRef] PLAN_ROW 0 10,2507 902307 902310 |--Filter(WHERE:([LocalBase].[dbo].[_InfoRg20970].[_Fld20971_TYPE] as [T5].[_Fld20971_TYPE] IS NULL AND [LocalBase].[dbo].[_InfoRg20970].[_Fld20971_RTRef] as [T5].[_Fld20971_RTRef] IS NULL AND [LocalBase].[dbo].[_InfoRg20970].[_Fld20971_RRRef] as [T5].[_Fld20971_RRRef] IS NULL)) 0 7 1 Filter Filter WHERE:([LocalBase].[dbo].[_InfoRg20970].[_Fld20971_TYPE] as [T5].[_Fld20971_TYPE] IS NULL AND [LocalBase].[dbo].[_InfoRg20970].[_Fld20971_RTRef] as [T5].[_Fld20971_RTRef] IS NULL AND [LocalBase].[dbo].[_InfoRg20970].[_Fld20971_RRRef] as [T5].[_Fld20971_RRRef] IS NULL) 1 0 3,6917E-006 9 0,164991 PLAN_ROW 0 101,002 914162 902310 |--Nested Loops(Left Outer Join, OUTER REFERENCES:([T4].[_Fld20899RRef], [Expr1014])) 0 8 7 Nested Loops Left Outer Join OUTER REFERENCES:([T4].[ |
|||
11
raytan
04.11.19
✎
15:30
|
(9) Как то так не очень выкладывается :((( -- это "медленный"
|
|||
12
mistеr
04.11.19
✎
15:37
|
(11) Для этого есть pastebin
|
|||
13
mistеr
04.11.19
✎
15:39
|
(10) Неполный
|
|||
14
raytan
04.11.19
✎
15:41
|
||||
15
raytan
04.11.19
✎
15:42
|
https://my-files.ru/a6uc4q -- быстрый
|
|||
16
fisher
04.11.19
✎
15:44
|
(7) Это если не всобачивать в каждый запрос вот такие портянки. Причем эта считается еще простенькой. А ведь по сути что она делает? Вычисляет список доступных пользователю ключевых объектов. Условно постоянный список (и обычно довольно короткий по итогу) - в каждом запросе, Карл!
Когда я от этого избавился, то мои RLS начали ускорять работу, а не замедлять. Как по-хорошему и должно быть. |
|||
17
raytan
04.11.19
✎
15:49
|
(16) На данном этапе переделывать все шаблоны в RLS -- это нереально. Кроме того, что просто еще непонятно на что переделать. Так еще это пока что тот кусок работы, который мы не потянем физически.
|
|||
18
raytan
04.11.19
✎
15:53
|
||||
19
H A D G E H O G s
04.11.19
✎
15:55
|
В этих РЛС есть кусок кода, который отвечает за динамическое применение изменения групп доступа. Без перезахода пользователей. Его можно убрать.
|
|||
20
H A D G E H O G s
04.11.19
✎
15:57
|
(18) Откройте для себя XML форму планов запросов
|
|||
21
fisher
04.11.19
✎
15:58
|
(17) Чаще всего на практике используется всего несколько простых разделителей. Ну по типу как здесь - Организации, Подразделения, СтатьиДДС.
Переписывается это относительно несложно. Просто для каждого разделителя заводишь параметр сеанса вида ФиксированныйМассив и при старте программы заполняешь их по той же логике, по которой они сейчас вычисляются в запросе. Все. Для признака полных прав по разделителю я пихал в массив пустое значение. После этого (это можно делать постепенно) меняешь типовые RLS на предельно простые, с банальными условиями на вхождение в массив. |
|||
22
mistеr
04.11.19
✎
15:59
|
(15) Для начала, запросы и планы намного разные.
|
|||
23
H A D G E H O G s
04.11.19
✎
15:59
|
(18) И sentry one plan explorer
|
|||
24
H A D G E H O G s
04.11.19
✎
16:01
|
(21) +100500
|
|||
25
mistеr
04.11.19
✎
16:01
|
(23) Штатный профайлер все кажет.
|
|||
26
H A D G E H O G s
04.11.19
✎
16:01
|
(25) Этот - удобнее
|
|||
27
mistеr
04.11.19
✎
16:02
|
(22) *немного*
|
|||
28
raytan
04.11.19
✎
16:02
|
(23) Спасибо, попробую завтра разобраться с этой программой
(21) Подумаем над этим вариантом. Но повторюсь, это большой кусок работы. |
|||
29
raytan
04.11.19
✎
16:05
|
(22) Может быть. Я проводил много экспериментов и возможно прислал несколько отличающиеся запросы. Но не смотря на это результат один: "медленный" пользователь работает долго, "быстрый" -- быстро..
|
|||
30
mistеr
04.11.19
✎
16:08
|
По-моему, классический случай "data skew". Оптимизатор решил, что кардинальность будет низкая, выбрал nested loops. Строк для этого пользователя оказалось много, nested loops стал неэффективен.
|
|||
31
mistеr
04.11.19
✎
16:09
|
Либо статистика не дала нужной информации, либо оптимизатор не смог ее использовать.
|
|||
32
raytan
04.11.19
✎
16:10
|
(30) (31) как это побороть ?
|
|||
33
raytan
04.11.19
✎
16:10
|
(32) обновление статистики не помогло ((
|
|||
34
mistеr
04.11.19
✎
16:16
|
(32) Либо звать спеца по 2005 скулю, либо (8)
|
|||
35
fisher
04.11.19
✎
16:21
|
(28) Если это большой кусок работы, то я завидую вашей загрузке :)
Реализовать первую часть - два часа с отладкой и перекурами. Переписать сабжевое RLS = скорость набирания текста. |
|||
36
raytan
04.11.19
✎
16:37
|
(35) мне видется, что это не все так просто. Но спасибо за идею, подумаем над ее реализацией
|
|||
37
Провинциальный 1сник
04.11.19
✎
16:37
|
Помнится, тоже наткнулся на такое с sql2000, дико тормозило РЛС на ЗУП. Переход на 2008r2 проблему решил. Там оптимизатор поумнее.
|
|||
38
fisher
04.11.19
✎
16:38
|
А вообще, попробуй запустить sp_updatestats на текущей базе.
По-идее, это тот же UPDATE STATISTICS, вид сбоку. Но мне как-то реально помогло на "залипшем" неоптимальном плане выполнения. Фиг его, знает, может и поможет. |
|||
39
fisher
04.11.19
✎
16:40
|
Ну, т.е. оно обновляет статистику для всех таблиц и сбрасывает кэши планов выполнения.
|
|||
40
raytan
04.11.19
✎
16:40
|
(37) У нас лицензионный скл и просто поменять мы не можем :(
(38) читал про sp_updatestats -- возможно это и выход, но попробовать смогу наверное только на выходных |
|||
41
raytan
04.11.19
✎
16:42
|
(39) выполнял отдельно:
DBCC DROPCLEANBUFFERS DBCC FREEPROCCACHE По моему в комплекте с update это тоже самое |
|||
42
fisher
04.11.19
✎
16:42
|
(40) Это разностное обновление. Т.е. если ты статистику считаешь регулярно, то шибко времени занять не должно и на боевом продакшене вполне можно запускать - беды никакой не должно причинить.
(41) А ты все же попробуй. |
|||
43
raytan
04.11.19
✎
16:44
|
(42) В любом случае буду делать. Так как с проблемой надо что то решать.
|
|||
44
fisher
04.11.19
✎
16:47
|
(42) Просто если с выборочным обновлением/сбросом можно напортачить, то эта хрень гарантированно кэши сбросит.
|
|||
45
H A D G E H O G s
04.11.19
✎
17:12
|
Если таблица большая - статистика может тупо не собираться.
|
|||
46
H A D G E H O G s
04.11.19
✎
17:13
|
||||
47
H A D G E H O G s
04.11.19
✎
17:15
|
||||
48
Sapiens_bru
04.11.19
✎
17:23
|
(0) Попробуй вытащить вот это
" (ВЫБРАТЬ РАЗЛИЧНЫЕ СоставГруппы.Ссылка КАК ГруппаПользователей ИЗ Справочник.ГруппыПользователей.ПользователиГруппы КАК СоставГруппы ГДЕ СоставГруппы.Пользователь = &ТекущийПользователь) " Во временную таблицу. |
|||
49
raytan
05.11.19
✎
08:49
|
(48) Это же переделанный запрос из RLS -- там нет временных таблиц
|
|||
50
raytan
05.11.19
✎
08:55
|
(47) таблица относительно маленькая -- 150 тысяч строк
|
|||
51
rsv
05.11.19
✎
15:16
|
(0) а когда оптимизатор не справляется ему обычно ....
подсказывают. Ради интереса в -скульный аналог вкрячить хинт какой нить Но если аналог из портянок ##tt1 и прочее то мимо |
|||
52
pechkin
05.11.19
✎
15:19
|
нужно переходить на новый вид РЛС
|
|||
53
raytan
06.11.19
✎
10:07
|
(52) а что конкретно вы имеете ввиду ? В более новых решениях шаблоны реализованы по другому ? А как ? Может есть пример ?
|
|||
54
raytan
06.11.19
✎
10:07
|
(38) выполнил sp_updatestats -- не помогло
|
|||
55
fisher
06.11.19
✎
10:59
|
(54) Значит, дело не в статистике и не в процедурном кэше. Ваш кэп.
|
|||
56
Провинциальный 1сник
06.11.19
✎
11:03
|
(51) В голову приходит dll-инжекция с перехватом обращений к sql-серверу и подстановкой хинтов, вместо "join" подставить "hash join", и так далее)
|
Форум | Правила | Описание | Объявления | Секции | Поиск | Книга знаний | Вики-миста |