8. Понедельник
Я сделаю все так,
чтобы это было
интересно и этим
можно было
гордиться!
У нас новый ПАЦИЕНТ
9. Наш пациент : предмет оптимизации
Diagnosis Search Page
Patient:
Disease:
Doctor:
Search
House
DB
Doctor
Disease
Dr. House
Lumpus
Ivanov Petr
Loading 5
Records … / Pages 3
UI
Patient
Loading
123 …
Petrov Ivan
Dr. House
Lumpus
select * from (
select patient_name, doctor_name, disease_name,
row_number() over(order by patient_name)
as row_index
from list_diagnosis()
where doctor_name = 'House‘ ) d
where row_index between 1 and 2
Запрос для
постраничного
вывода данных
select count(*)
from list_diagnosis()
where doctor_name = 'House'
Запрос общего
количества
строк
10. Уровни тестирования
1) Чем ниже уровень
тестирования, тем оно
Тестирование
через UI
a) меньше слоев вовлекает;
b) более направленное;
c) быстрее может выполняться.
UI
Приложение
2) Автоматическое
Тестирование
тестирование дает
через API
возможность перезапускать
наши тесты, производя тем
самым регрессионное
База данных
Тестирование
тестирование.
через DB
12. Требования к решению
1) Нашим тестом должен быть запрос. Это
позволит тестировать на уровне базы.
2) Нужен запрос, который сравнит
результаты функции до оптимизации с
результатами этой же функции, но уже
после оптимизации.
3) При этом сделает это для различных
входных параметров.
4) И выведет все расхождения, если они есть.
5) Плюс будет достаточно быстрым.
16. Сравнение двух множеств
множество мужчин
из сериала (A)
Форман
Чейз
Хаус
множество
умных врачей (B)
Форман
Чейз
Кэмерон
Хаус
A except B
B except A
пустое
множество
Кэмерон
Тогда чтобы сравнить эти два множества мы можем
использовать следующую последовательность операций.
(A except B) union (B except A)
В результате пустое множество означает, что все элементы
А есть во множестве B и все элементы B есть в А. Значит эти
два множества состоят из одинаковых элементов.
17. Операции со множествами в SQL
with source as
(
select patient_name,
desease_name, doctor_name
from list_diagnosis_old()
),
target as
(
Исходная функция
Оптимизированная
функция
select patient_name,
desease_name, doctor_name
from list_diagnosis_new()
)
(select * from source except select * from target)
union all
(select * from target except select * from source)
Операция сравнения
18. Функция с параметрами
Наша функция зависит от параметра
list_diagnosis_new(@hospital_id)
Тогда нам нужно получить множество параметров, на котором
мы будем тестировать нашу функцию.
Например, таким образом
select hospital_id
from HOSPITAL
Если параметров несколько
select hospital_id, doctor_id
from HOSPITAL
cross join DOCTOR
Или самим сформировать множество параметров
select 1 as doctor_id
union all select 3
union all select 4
union all select 6
Таким образом, мы может написать
совершенно любой запрос для
формирования множества параметров.
19. Функция с параметрами
После этого нам нужно научиться вызывать нашу функцию для
интересующего нас множества параметров.
select p.hospital_id, patient_name,
desease_name, doctor_name
Выходное тестируемое
from
множество, включая
(
параметры
select hospital_id
from HOSPITAL
Множество
) p
cross apply
list_diagnosis(p.hospital_id)
тестируемых
параметров
Тестируемая
функция
20. Итоговый запрос
with source as
Множества параметров
(
select hospital_id, patient_name, desease_name, f.doctor_name
from HOSPITAL h
cross apply list_diagnosis_old(hospital_id) f
),
target as
Тестируемые функции
(
select hospital_id, patient_name, desease_name, f.doctor_name
from HOSPITAL h
cross apply list_diagnosis_new(hospital_id) f
)
(select * from source except select * from target)
union all
(select * from target except select * from source)
Операция сравнения
26. Скорость и покрытие
1) Чем больше объем данных.
2) Чем больше тестируемое множество параметров.
3) Чем медленнее функция.
Тем, очевидно, дольше будет работать сравнение.
Поэтому не стоит запускать сравнение сразу на
полном объеме данных и с полным множеством
параметров.
Наращивайте скоуп тестирования постепенно.
27. Начинаем тестировать
with source as
(
select hospital_id, patient_name, desease_name, f.doctor_name
from (select top 1 hospital_id from HOSPITAL order by hospital_id)
cross apply list_diagnosis_old(hospital_id) f
),
target as
(
select hospital_id, patient_name, desease_name, f.doctor_name
from (select top 1 hospital_id from HOSPITAL order by hospital_id)
cross apply list_diagnosis_new(hospital_id) f
)
(select * from source except select * from target)
union all
(select * from target except select * from source)
28. Увеличиваем скоуп
with source as
(
select hospital_id, patient_name, desease_name, f.doctor_name
from (select top 100 hospital_id from HOSPITAL order by hospital_id
cross apply list_diagnosis_old(hospital_id) f
),
target as
(
select hospital_id, patient_name, desease_name, f.doctor_name
from (select top 100 hospital_id from HOSPITAL order by hospital_id
cross apply list_diagnosis_new(hospital_id) f
)
(select * from source except select * from target)
union all
(select * from target except select * from source)
29. Сравниваем на всем объеме
with source as
(
select hospital_id, patient_name, desease_name, f.doctor_name
from HOSPITAL h
cross apply list_diagnosis_old(h.hospital_id) f
),
target as
(
select hospital_id, patient_name, desease_name, f.doctor_name
from HOSPITAL h
cross apply list_diagnosis_new(h.hospital_id) f
)
(select * from source except select * from target)
union all
(select * from target except select * from source)
30. Тестирование на всем объеме
Даже, если мы протестировали на всем объеме имеющихся
данных. Дает ли нам это гарантию, что мы действительно
протестировали все и ошибок нет?
НЕТ
Потому что, тестирование происходит на уже
существующих данных. И база может не содержать
некоторых важных случаев.
НО
Если система существует продолжительное время. То
именно для оптимизации запросов, это позволяет
получить очень большую долю уверенности, что все
хорошо.
34. Описание бага
BUG
Количество строк в гриде не совпадает с подсчитанным в футоре.
Diagnosis Search Page
Patient:
UI
Disease:
Doctor:
Search
House
Doctor
Disease
Dr. House
Lumpus
Ivanov Petr
Records 5 / Pages 3
DB
Patient
123
Petrov Ivan
Dr. House
Lumpus
select * from (
select patient_name, doctor_name, disease_name,
row_number() over(order by patient_name) as row_index
from list_diagnosis(@hospital_id)
where doctor_name = 'House‘ ) d
where row_index between 1 and 2
select count(*)
from list_diagnosis(@hospital_id)
where doctor_name = 'House'
35. Еще об операциях над множествами
Результат запроса – это не множество. Понятие множества
подразумевает, что в нем нет одинаковых элементов.
Результат
запроса(A)
Форман
Форман
Чейз
Результат
запроса (B)
A except B
B except A
Форман
Чейз
пустое
множество
пустое
множество
SQL операция except работает со множеством, т.е.
удалит все дубликаты.
По аналогии с union и union all в стандарте есть
операция except all, но она не реализована в SQL
Server-e.
36. except all – своими руками
A
Форман
Форман
Чейз
A’
1, Форман
2, Форман
1, Чейз
B
A except B
B except A
Форман
Чейз
пустое
множество
пустое
множество
B’
A’ except B’
B’ except A’
2, Форман
пустое
множество
1, Форман
1, Чейз
Таким образом мы добавили специальный идентификатор,
который будет наращиваться только для одинаковых строк.
37. except all – своими руками
select hospital_id, patient_name,
desease_name, doctor_name,
row_number() over
(
partition by
hospital_id,
patient_name,
desease_name,
f.doctor_name
order by
hospital_id,
patient_name,
desease_name,
f.doctor_name
) number
from list_diagnosis_old(hospital_id)
Функция row_number()
нумерует строки исходя
из сортировки и
разбиения
partition by отвечает за
разбиения на
подмножества
order by отвечает за
сортировку в
подмножестве
38. Наш итоговый запрос
with source as
(
select hospital_id, patient_name,
desease_name, doctor_name,
row_number() over
Исходная функция
(
partition by hospital_id, patient_name,
desease_name, f.doctor_name
order by hospital_id, patient_name,
desease_name, f.doctor_name
) number
from HOSPITAL h
cross apply list_diagnosis_old(hospital_id)
),
target as
Тестируемая функция, с
(
...
такими же изменениями
)
(select * from source except select * from target)
union all
(select * from target except select * from source)
41. Описание бага: еще разок
BUG
Количество строк в гриде не совпадает с подсчитанным в футоре.
Т.е. отображается одна строка, а count(*) показывает, что их там
должно быть пять.
select * from
(
select patient_name, doctor_name, disease_name,
row_number() over(order by patient_name) as row_index
from list_diagnosis(@hospital_id)
where doctor_name = 'House‘
)d
where row_index between 1 and 2
DB
select count(*)
from list_diagnosis(@hospital_id)
where doctor_name = 'House'
Т.е. одна и та же функция с одинаковыми фильтрами, но в
разных контекстах возвращает разное количество строк.
45. Дифференциальный анализ
Симптомы:
1) На продакшене, результаты
функции count(*) не
совпадают с реальным
количеством строк.
2) На локальных окружениях
это проблема не
воспроизводиться.
Диагноз:
1) Возможно на это влияют
какие-то настройки SQL
Server-а?
2) Или в самой функции есть
какие-то логические
ошибки, которые почему-то
проявляются только на
продакшене.
47. Анализ функции
select p.patient_name, desease_name, doctor_name
from PATIENT p
Достаем
cross apply
последний диагноз
(
пациента.
select top 1 desease_name, doctor_name
from DIAGNOSIS g
А если, по какой-то
inner join DOCTOR d
on g.doctor_id = d.doctor_id
причине, есть 2
where g.patient_id = p.patient_id
диагноза с одним
order by g.create_time
временем.
) d
1) В этом случае SQL Server не гарантирует какую строчку он
в действительности достанет.
2) Это зависит от плана выполнения запроса, который,
очевидно, был разным для двух использований функции.
3) Поэтому функция становиться недетерминированной, т.е.
при одинаковых входных данных возвращать разные
результаты.
48. Исправляем логическую ошибку
select p.patient_name, desease_name, doctor_name
from PATIENT p
cross apply
(
select top 1 desease_name, doctor_name
from DIAGNOSIS g
inner join DOCTOR d
on g.doctor_id = d.doctor_id
where g.patient_id = p.patient_id
order by g.create_time
) d
Очевидно, нужно заменить недетерминированный order by,
например так:
order by g.create_time, g.diagnosis_id
или
order by g.diagnosis_id
51. Анализ данного подхода
1) Тестирование производится на 1) Полное тестирование не
уровне базы данных.
всегда возможно.
2) Есть возможность
2) Даже при тестировании на
протестировать изменения на
всех данных – это не гарант
очень большом объеме
того, что ошибок нет. Так как:
данных.
• данных в базе может быть
3) Подход может использоваться:
недостаточно для
a) для функций, для запросов, для
хорошего сравнения;
сравнения целых баз данных
• даже в старой функции
b) в оптимизации, в ETL, для любых
могут быть логические
сравнений, где есть эталон.
ошибки;
Но в целом, если изменения производились только на уровне
базы данных, то такой подход целесообразнее.
52. Подводим итоги недели
1) Разобрались с операциями над
множествами, такими как except и union.
2) Реализовали except all – своими руками.
3) Разработали метод функционального
тестирования запросов.
4) Функционально протестировали
оптимизацию запроса на уровне базы
данных.
5) Нашли очень замысловатую логическую
ошибку и исправили старый
функциональный баг.
53. Подход Доктора Хауса в тестировании
Подводим итоги недели
оптимизации запросов
1) Разобрались с операциями над
1) множествами, такими как except и
Все врут, а значит
всё нужно
union. И даже реализовали except all.
перепроверять.
2) Мы разработали метод
2) Разбираться в
функционального тестирования
проблеме до конца.
3) запросов. резать по
Не боятся
3) Нашли оченьвсегда
живому, но замысловатую
логическую ошибку и исправили
контролировать.
4) старый функциональный баг.
Не сдаваться!!!