Блог

    Caché (Кашэ́) — промышленная высокопроизводительная, объектная система управления базами данных, интегрированная с технологией разработки веб-приложений. Единая архитектура данных Caché позволяет разработчикам использовать одновременно объектный, реляционный (SQL) и прямой (NoSQL) доступ к одним и тем же данным, хранение которых обеспечивается ориентированным на транзакции многомерным ядром СУБД.

    http://www.intersystems.ru/cache/

Последние записи


Теги

Информация

Ускорение выборки из производных таблиц с учётом всей цепочки наследования

добавлено: 25 сен 18
понравилось:0
просмотров: 801
комментов: 0

теги:

Автор: servit

Недавно возникла интересная задача: 21665297 и 21666325.

Итак, дано:
  • три хранимых класса "a","b" и "c", где "с" наследуется от "b", а "b" в свою очередь - от "a";
  • данные всех трёх классов хранятся в одном глобале и соответственно нумерация ID у всех трёх - сквозная;
  • необходимо как-то ускорить следующие запросы, чтобы избежать полного сканирования
    select * from a
    select * from b
    select * from с
  • также должна учитываться вся цепочка наследования при работе с указанными таблицами.
Неоптимизированный код:
Class test.a Extends %Persistent
{

/// do ##class(test.a).Test()
ClassMethod
Test()
{
set sqlB=1,
sqlB(1)="select * from test.b",
sqlC=1,
sqlC(1)="select * from test.c"

kill ^test.aI,^test.aD

for i=1:1:2 {
&sql(
insert into test.a default values)
&sql(
insert into test.b default values)
&sql(
insert into test.c default values)
}

zwrite ^test.aI,^test.aD

do $system.SQL.TuneSchema("test",$$$YES)
,
$system.OBJ.Compile("test.*","cu-d")

,
$system.SQL.ShowPlan(.sqlB,$$$NO)
,
$system.SQL.ShowPlan(.sqlC,$$$NO)
}

}


Class test.b Extends test.a
{

}


Class test.c Extends test.b
{

}
Результат:
USER>do ##class(test.a).Test()
^test.aD=6
^test.aD(1)=$lb("")
^test.aD(2)=$lb("~test.b~")
^test.aD(3)=$lb("~test.b~test.c~")
^test.aD(4)=$lb("")
^test.aD(5)=$lb("~test.b~")
^test.aD(6)=$lb("~test.b~test.c~")

<plan>
<
sql
select * from test.b
sql>
<
cost value="1016"/>
Read master map test.b.IDKEY, looping on ID.
For each row:
Output the row.
</
plan>
<
plan>
<
sql
select * from test.с
sql>
<
cost value="1008"/>
Read master map test.c.IDKEY, looping on ID.
For each row:
Output the row.
</
plan>
select * from test.a
ID
1
2
3
4
5
6

select * from test.b
ID
2
3
5
6

select * from test.c
ID
3
6

Как видим, согласно планам запросов в обоих случаях происходит полный перебор с проверкой значения %%CLASSNAME, хранящимся по умолчанию в первом элементе списка.

Так как же решить проблему и в принципе, возможно ли это сделать минимальными усилиями?

Но сперва рассмотрим недостатки уже предложенных решений.

Решение №1: первичный ключ по [classname + number]


ТС изначально хотел хранить данные в виде
^data("classB",number1)=...
^data("classB",number2)=...
^data("classB",numbern)=...

^data("classС",number1)=...
^data("classС",number2)=...
^data("classС",numbern)=...
, где каждый класс имеет свою нумерацию, для чего планировалось создать первичный индекс по [classname + number].

Но беда в том, что запрос вида

select * from b

должен возвращать данные относящиеся как к "b", так и к "c", "d" и т.д., т.е всю цепочку наследования, что порождает новые проблемы с хранением/нумерацией.
Кроме того sql-движок всё равно не будет должным образом его использовать без дополнительного where, что сводит на нет все усилия.

Решение №2: хранить данные каждого класса отдельно


Здесь та же проблема с хранением/получением данных для всей цепочки наследования.

Решение №3: пользовательская схема хранения


Без комментариев.

Решение №4: использовать индекс Extent


Bingo! И это - почти правильное решение, но.. не совсем.

Добавим в класс test.a индекс:

Index iExt [ Extent ];
Результат:
USER>do ##class(test.a).Test()
^test.aI("iExt",1)=""
^test.aI("iExt",2)="~test.b~"
^test.aI("iExt",3)="~test.b~test.c~"
^test.aI("iExt",4)=""
^test.aI("iExt",5)="~test.b~"
^test.aI("iExt",6)="~test.b~test.c~"
^test.aD=6
^test.aD(1)=$lb("")
^test.aD(2)=$lb("~test.b~")
^test.aD(3)=$lb("~test.b~test.c~")
^test.aD(4)=$lb("")
^test.aD(5)=$lb("~test.b~")
^test.aD(6)=$lb("~test.b~test.c~")

<plan>
<
sql
select * from test.b
sql>
<
cost value="1016"/>
Read master map test.b.IDKEY, looping on ID.
For each row:
Output the row.
</
plan>
<
plan>
<
sql
select * from test.с
sql>
<
cost value="1008"/>
Read master map test.c.IDKEY, looping on ID.
For each row:
Output the row.
</
plan>

И снова мимо. Как же так? Есть нюанс, о котором в дискуссиях не было упомянуто, а именно...

Правильное решение: использовать индекс Bitmap Extent


Модифицируем наш индекс:

Index "$a" [ Extent, Type = bitmap ];
Результат:
USER>do ##class(test.a).Test()
^test.aI("$a",1)=$zwc(409,1,0)/*$bit(2..7)*/
^test.aI("$b",1)=$zwc(409,1,0,1,4)/*$bit(3,4,6,7)*/
^test.aI("$c",1)=$zwc(401,1,3,6)/*$bit(4,7)*/
^test.aD=6
^test.aD(1)=$lb("")
^test.aD(2)=$lb("~test.b~")
^test.aD(3)=$lb("~test.b~test.c~")
^test.aD(4)=$lb("")
^test.aD(5)=$lb("~test.b~")
^test.aD(6)=$lb("~test.b~test.c~")

<plan>
<
sql
select * from test.b
sql>
<
cost value="1016"/>
Read extent bitmap test.b.$b, looping on ID.
For each row:
Output the row.
</
plan>
<
plan>
<
sql
select * from test.с
sql>
<
cost value="1008"/>
Read extent bitmap test.c.$c, looping on ID.
For each row:
Output the row.
</
plan>

Теперь стало заметно быстрее (здесь не показано, но можно проверить в SMP), так как sql-движок задействовал соответствующие индексы, в которых компактно хранятся идентификаторы всей цепочки наследования, что исключает полный перебор.

Комментарии




Необходимо войти на сайт, чтобы оставлять комментарии