Upload
it-
View
311
Download
10
Embed Size (px)
DESCRIPTION
Алексей Малашкевич - Автор и разработчик Pony ORM / Pony ORM / Россия, Санкт-Петербург Александр Козловский - Автор и разработчик Pony ORM / Pony ORM / Россия, Санкт-Петербург Pony ORM - маппер, который позволяет работать с базой данных с помощью генераторных выражений языка Питон. С помощью такого подхода Pony позволяет формулировать очень компактные и понятные запросы, которые автоматически транслируются в оптимизированный SQL. Pony обладает графическим редактором ER диаграмм - удобным инструментом для создания и редактирования модели данных. В докладе разработчики Pony ORM расскажут про процесс перевода объектно-ориентированного запроса в запрос на языке SQL, о том какие оптимизации Pony применяет на каждом этапе обработки запроса, какие сложности стояли при разработке высокопроизводительного ORM и как Pony ORM облегчает и ускоряет разработку приложений. http://www.it-sobytie.ru/events/2040
Citation preview
Object-Relational Mapper
Что такое Pony ORM?
Что такое Pony ORM?
• Объектно-реляционный маппер
• Реализует паттерн Identity Map
• Транслирует генераторы в SQL
• Включает редактор ER диаграмм
• Автоматически оптимизирует SQL запросы
• Поддерживает Python 2.5 – 2.7
• Скоро появится поддержка Python 3
Демо (15 минут)
• Редактор ER диаграмм
• Создание БД и заполнение данными
• Работа с данными в интерактивном
режиме
Pony ORM:
select(p for p in Person
if p.name.startswith('A') and p.tel is None
or p.dob.year < 2012
)
Способы построения запроса
Способы построения запроса
Django:
Person.objects.filter(
Q(name__startswith('A'), tel__isnull=True)
| Q(dob__year__lt=2012)
)
Способы построения запроса
SQLAlchemy:
session.query(Person).filter(
(Person.name.startswith('A')
& (Person.tel == None))
| (extract('year', Person.dob) < 2012)
)
select(p for p in Person
if p.name.startswith('A') and p.tel is None
or p.dob.year < 2012
)
Person.objects.filter(
Q(name__startswith('A'), tel__isnull=True)
| Q(dob__year__lt=2012)
)
session.query(Person).filter(
(Person.name.startswith('A') & (Person.tel == None))
| (extract('year', Person.dob) < 2012)
)
Преимущества использования
синтаксиса генераторов:
• Несколько проще для запоминания
• Сложные запросы пишутся с меньшим
количеством «наворотов» (типа “Q”)
• Трансляция запросов в SQL
осуществляется гораздо быстрее!
(можно кешировать результат
трансляции и использовать
объект кода генератора как ключ)
Как Pony ORM транслирует
питоновские генераторы в SQL?
Трансляция генератора в SQL
1. Декомпиляция байткода, восстановление абстрактного синтаксического дерева (AST)
2. Трансляция AST в “абстрактный SQL” (представленный в виде списков)
3. Трансляция “абстрактного SQL” в конкретный диалект языка SQL
Трансляция генератора в SQL
1. Декомпиляция байткода, восстановление абстрактного синтаксического дерева (AST)
2. Трансляция AST в “абстрактный SQL” (представленный в виде списков)
3. Трансляция “абстрактного SQL” в конкретный диалект языка SQL
Декомпиляция байткода
• Используем паттерн Visitor
• Методы визитора соответствуют
операциям байткода
• Храним фрагменты восстановленного
AST на стеке
• Каждый метод или помещает на
вершину стека новый фрагмент AST,
или комбинирует имеющиеся
фрагменты
Декомпиляция байткода
(a + b.c) in x.y
LOAD_GLOBAL a
LOAD_FAST b
LOAD_ATTR c
BINARY_ADD
LOAD_FAST x
LOAD_ATTR y
COMPARE_OP in
Декомпиляция байткода
(a + b.c) in x.y
LOAD_GLOBAL a
LOAD_FAST b
LOAD_ATTR c
BINARY_ADD
LOAD_FAST x
LOAD_ATTR y
COMPARE_OP in
Стек
Декомпиляция байткода
(a + b.c) in x.y
> LOAD_GLOBAL a
LOAD_FAST b
LOAD_ATTR c
BINARY_ADD
LOAD_FAST x
LOAD_ATTR y
COMPARE_OP in
Стек
Декомпиляция байткода
(a + b.c) in x.y
> LOAD_GLOBAL a
LOAD_FAST b
LOAD_ATTR c
BINARY_ADD
LOAD_FAST x
LOAD_ATTR y
COMPARE_OP in
Стек
Name('a')
Декомпиляция байткода
(a + b.c) in x.y
LOAD_GLOBAL a
> LOAD_FAST b
LOAD_ATTR c
BINARY_ADD
LOAD_FAST x
LOAD_ATTR y
COMPARE_OP in
Стек
Name('a')
Декомпиляция байткода
(a + b.c) in x.y
LOAD_GLOBAL a
> LOAD_FAST b
LOAD_ATTR c
BINARY_ADD
LOAD_FAST x
LOAD_ATTR y
COMPARE_OP in
Стек
Name('b')
Name('a')
Декомпиляция байткода
(a + b.c) in x.y
LOAD_GLOBAL a
LOAD_FAST b
> LOAD_ATTR c
BINARY_ADD
LOAD_FAST x
LOAD_ATTR y
COMPARE_OP in
Стек
Name('b')
Name('a')
(a + b.c) in x.y
LOAD_GLOBAL a
LOAD_FAST b
> LOAD_ATTR c
BINARY_ADD
LOAD_FAST x
LOAD_ATTR y
COMPARE_OP in
Стек
Getattr(Name('b'), 'c')
Name('a')
Декомпиляция байткода
(a + b.c) in x.y
LOAD_GLOBAL a
LOAD_FAST b
LOAD_ATTR c
> BINARY_ADD
LOAD_FAST x
LOAD_ATTR y
COMPARE_OP in
Стек
Getattr(Name('b'), 'c')
Name('a')
Декомпиляция байткода
(a + b.c) in x.y
LOAD_GLOBAL a
LOAD_FAST b
LOAD_ATTR c
> BINARY_ADD
LOAD_FAST x
LOAD_ATTR y
COMPARE_OP in
Стек
Add(Name('a'),
Getattr(Name('b'), 'c'))
Декомпиляция байткода
Декомпиляция байткода
(a + b.c) in x.y
LOAD_GLOBAL a
LOAD_FAST b
LOAD_ATTR c
BINARY_ADD
> LOAD_FAST x
LOAD_ATTR y
COMPARE_OP in
Стек
Add(Name('a'),
Getattr(Name('b'), 'c'))
Декомпиляция байткода
(a + b.c) in x.y
LOAD_GLOBAL a
LOAD_FAST b
LOAD_ATTR c
BINARY_ADD
> LOAD_FAST x
LOAD_ATTR y
COMPARE_OP in
Стек
Name('x')
Add(Name('a'),
Getattr(Name('b'), 'c'))
Декомпиляция байткода
(a + b.c) in x.y
LOAD_GLOBAL a
LOAD_FAST b
LOAD_ATTR c
BINARY_ADD
LOAD_FAST x
> LOAD_ATTR y
COMPARE_OP in
Стек
Name('x')
Add(Name('a'),
Getattr(Name('b'), 'c'))
Декомпиляция байткода
(a + b.c) in x.y
LOAD_GLOBAL a
LOAD_FAST b
LOAD_ATTR c
BINARY_ADD
LOAD_FAST x
> LOAD_ATTR y
COMPARE_OP in
Стек
Getattr(Name('x'), 'y')
Add(Name('a'),
Getattr(Name('b'), 'c'))
Декомпиляция байткода
(a + b.c) in x.y
LOAD_GLOBAL a
LOAD_FAST b
LOAD_ATTR c
BINARY_ADD
LOAD_FAST x
LOAD_ATTR y
> COMPARE_OP in
Стек
Getattr(Name('x'), 'y')
Add(Name('a'),
Getattr(Name('b'), 'c'))
Декомпиляция байткода
(a + b.c) in x.y
LOAD_GLOBAL a
LOAD_FAST b
LOAD_ATTR c
BINARY_ADD
LOAD_FAST x
LOAD_ATTR y
> COMPARE_OP in
Стек
Compare('in',
Add(…), Getattr(…))
Абстрактное синтаксич. дерево (AST)
a
in
+
.c
b
.y
x
(a + b.c) in x.y
Трансляция генератора в SQL
1. Декомпиляция байткода, восстановление абстрактного синтаксического дерева (AST)
2. Трансляция AST в “абстрактный SQL” (представленный в виде списков)
3. Трансляция “абстрактного SQL” в конкретный диалект языка SQL
Трансляция AST
• Используем паттерн Visitor
• Выполняем обход дерева вглубь
• Обрабатываем каждый узел на выходе
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
a
in
+
.c
b
.y
x
(a + b.c) in x.y
Трансляция AST
a
in
+
.c
b
.y
x
(a + b.c) in x.y
Трансляция AST
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
• В какой SQL должно транслироваться?
• Зависит от типов переменных
• “+” может означать сложение чисел или конкатенацию строк – транслируется в +, CONCAT или ||
• “in” может транслироваться в подзапрос или в LIKE
(a + b.c) in x.y
Трансляция AST
• (? + “b”.“c”) IN (SELECT …)
• CONCAT(?, “b”.“c”) IN (SELECT …)
• “x”.“y” LIKE ‘%’ || ? || “b”.“c” || ‘%’
(a + b.c) in x.y
Трансляция AST
• Трансляция операции, такой как “+”, “in” или взятие атрибута, зависит от смысла подчиненных выражений
• Если класс транслятора сам выполняет весь необходимый анализ, логика транслятора становится чрезмерно сложной
• На помощь приходят монады
(a + b.c) in x.y
Трансляция AST
• Инкапсулируют в себе результат анализа AST
• Умеют генерировать результат трансляции – “абстрактный SQL”
• Умеют комбинировать себя с другими монадами
• Обрабатывая узел AST, транслятор дает команду монадам нижележащих узлов скомбинировать себя и получить новую монаду
Монады
• NumericParamMonad
• StringParamMonad
• ObjectIterMonad
• ObjectParamMonad
• NumericAttrMonad
• StringAttrMonad
• ObjectAttrMonad
• FuncMonad
• и т.д. …
Монады
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада
Трансляция AST
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада
Трансляция AST
монада
in
.y
x
+
a .c
b
(a + b.c) in x.y
Трансляция AST
монада
монада
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада
Трансляция AST
монада
монада
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада монада
Трансляция AST
монада
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада монада
Трансляция AST
монада
монада
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада монада
монада
Трансляция AST
монада
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада монада
Трансляция AST
монада
монада
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада монада монада
Трансляция AST
монада
монада
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада монада монада
Трансляция AST
монада
монада
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада монада монада
монада
Трансляция AST
монада
монада
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада монада монада
монада
Трансляция AST
монада
монада
in
.y
x
+
a .c
b
(a + b.c) in x.y
монада монада монада
монада
монада
Трансляция AST
монада
монада
in
.y
x
+
a .c
b
Aбстрактный SQL
(a + b.c) in x.y
['LIKE', ['COLUMN', 't1', 'y'], ['CONCAT', ['VALUE', '%'], ['PARAM', 'p1'], ['COLUMN', 't2', 'c'], ['VALUE', '%'] ] ] - Позволяет абстрагироваться от специфики конкретного диалекта
Трансляция генератора в SQL
1. Декомпиляция байткода, восстановление абстрактного синтаксического дерева (AST)
2. Трансляция AST в “абстрактный SQL” (представленный в виде списков)
3. Трансляция “абстрактного SQL” в конкретный диалект языка SQL
Другие особенности Pony ORM
Другие особенности Pony ORM
• Автоматическая оптимизация запросов
• Identity Map
• Решение проблемы N+1
• Оптимистические транзакции
Django ORM
s1 = Student.object.get(pk=123)
print s1, s1.group.id
s2 = Student.object.get(pk=456)
print s2, s2.group.id
• Сколько SQL-запросов выполнится?
• Сколько объектов будет создано?
Django ORM
s1 = Student.object.get(pk=123)
print s1, s1.group.id
s2 = Student.object.get(pk=456)
print s2, s2.group.id
Django ORM
s1 = Student.object.get(pk=123)
print s1, s1.group.id
s2 = Student.object.get(pk=456)
print s2, s2.group.id
Student 123
Django ORM
s1 = Student.object.get(pk=123)
print s1, s1.group.id
s2 = Student.object.get(pk=456)
print s2, s2.group.id
Student 123 Group 1
Django ORM
s1 = Student.object.get(pk=123)
print s1, s1.group.id
s2 = Student.object.get(pk=456)
print s2, s2.group.id
Student 123
Student 456
Group 1
Django ORM
s1 = Student.object.get(pk=123)
print s1, s1.group.id
s2 = Student.object.get(pk=456)
print s2, s2.group.id
Student 123
Student 456
Group 1
Group 1
Pony ORM
s1 = Student[123]
print s1, s1.group.id
s2 = Student[456]
print s2, s2.group.id
Pony ORM – сиды, IdentityMap
s1 = Student[123]
print s1, s1.group.id
s2 = Student[456]
print s2, s2.group.id
Student 123
Group 1
Pony ORM – сиды, IdentityMap
s1 = Student[123]
print s1, s1.group.id
s2 = Student[456]
print s2, s2.group.id
Student 123
Group 1
сид (seed)
Pony ORM – сиды, IdentityMap
s1 = Student[123]
print s1, s1.group.id
s2 = Student[456]
print s2, s2.group.id
Student 123
Group 1
сид (seed)
Pony ORM – сиды, IdentityMap
s1 = Student[123]
print s1, s1.group.id
s2 = Student[456]
print s2, s2.group.id
Student 123
Student 456
Group 1
сид (seed)
Pony ORM – сиды, IdentityMap
s1 = Student[123]
print s1, s1.group.id
s2 = Student[456]
print s2, s2.group.id
Student 123
Student 456
Group 1
сид (seed)
Решение проблемы N+1 query
orders = select(o for o in Order
if o.price > 1000)
for o in orders:
print o.total_price, o.customer.name
SELECT o.id, o.total_price, o.customer_id, …
FROM “Order” o
WHERE o.price > 1000
Решение проблемы N+1 query
Order 1
Order 3
Order 4
Order 7
Order 9
Customer 1
Customer 4
Customer 7
Решение проблемы N+1 query
orders = select(o for o in Order if o.price > 1000)
for o in orders:
print o.total_price, o.customer.name
SELECT c.id, c.name, …
FROM “Customer” c
WHERE c.id IN (?, ?, ?)
Решение проблемы N+1 query
Order 1
Order 3
Order 4
Order 7
Order 9
Customer 1
Customer 4
Customer 7
Django ORM - транзакции
def transfer_money(id1, id2, amount):
account1 = Account.objects.get(pk=id1)
if account1.amount < amount:
raise ValueError(‘Not enough money!’)
account2 = Account.object.get(pk=id2)
account1.amount -= amount
account1.save()
account2.amount += amount
account2.save()
Django ORM - транзакции
@transaction.atomic
def transfer_money(id1, id2, amount):
account1 = Account.objects.get(pk=id1)
if account1.amount < amount:
raise ValueError(‘Not enough money!’)
account2 = Account.object.get(pk=id2)
account1.amount -= amount
account1.save()
account2.amount += amount
account2.save()
@transaction.atomic
def transfer_money(id1, id2, amount):
account1 = Account.objects. \
select_for_update.get(pk=id1)
if account1.amount < amount:
raise ValueError(‘Not enough money!’)
account2 = Account.objects. \
select_for_update.get(pk=id2)
account1.amount -= amount
account1.save()
account2.amount += amount
account2.save()
Pony ORM - транзакции
@db_session
def transfer_money(id1, id2, amount):
account1 = Account[id1]
if account1.amount < amount:
raise ValueError(‘Not enough money!’)
account1.amount -= amount
Account[id2].amount += amount
db_session
• Pony автоматически отслеживает какие
объекты были изменены
• В момент выхода из db_session, если не
возникло исключений, происходит
сохранение всех измененных объектов
в рамках единой транзакции
• Пользователь не обязан сам вызывать
save для сохранения объектов
Оптимистические транзакции
• Pony отслеживает какие атрибуты
пользователь читал а какие изменял
• Если объект не был заблокирован в БД
в момент выборки, при сохранении
объекта Pony автоматически добавляет
проверки для оптимистических
блокировок
Оптимистические транзакции
UPDATE Account
SET amount = :new_value
WHERE id = :id
AND amount = :old_value
Pony ORM - транзакции
@db_session
def transfer_money(id1, id2, amount):
account1 = Account.get_for_update(id=id1)
if account1.amount < amount:
raise ValueError(‘Not enough money!’)
account1.amount -= amount
account2 = Account.get_for_update(id=id2)
account2.amount += amount
Особенности Pony ORM
• Использование генераторов и лямбд для формулирования запросов
• Автоматическая оптимизация запросов
• Identity Map
• Решение проблемы N+1
• Оптимистические транзакции
• Графический редактор ER-диаграмм
• В перспективе – поддержка NoSQL
Заключение
• Сайт ponyorm.com
• Редактор editor.ponyorm.com
• Установка pip install pony==0.5-beta
Спасибо за внимание!