Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
2007 г.

Интеграция языков программирования с базами данных: в чем состоит проблема?

Вильям Кук (William R. Cook), Али Ибрагим (Ali H. Ibrahim)
Перевод: Сергей Кузнецов
Оригинал: Integrating Programming Languages & Databases: What's the Problem?

Предисловие переводчика

Проблема «потери соответствия» (impedance mismatch) между языками программирования и системами баз данных обсуждается в сообществе баз данных на протяжении всей моей профессиональной жизни. Попытки решения этой проблемы породили направления языков программирования баз данных, объектно-ориентированных и, отчасти, объектно-реляционных баз данных.

В статье Кука и Ибрагима предлагается взгляд на проблему потери соответствия со стороны членов сообщества объектно-ориентированного программирования. Всегда интересно увидеть проблему чужими глазами. Некоторые мысли авторов, например, по поводу оптимизации доступа к постоянно хранимым данным кажутся мне, как человеку из сообщества баз данных, достаточно неожиданными. Но именно это и интересно.

Авторы статьи не являются специалистами в области баз данных, и поэтому иногда в статье используется не очень корректная терминология. Я не стал править авторский текст для устранения этих небольших дефектов. К статье прилагается хороший список литературы. К сожалению, не все тексты статей публично доступны в Internet, но я постарался подобрать ссылки на доступные статьи. Надеюсь, что кому-нибудь это принесет пользу.

Аннотация

Проблема интеграции баз данных и языков программирования остается открытой на протяжении почти 45 лет. В течение этого времени достигнут большой прогресс в исследованиях специализированных языков программирования баз данных, ортогональной персистентности, объектно-ориентированных баз данных, моделей транзакций, библиотек доступа к данным, встраиваемых запросов и объектно-реляционного отображения. Хотя решения предлагаются каждый год, ни одно из них не оказалось полностью удовлетворительным. Одним из объяснений этой ситуации является то, что сама проблема не является достаточно четко определенной, и поэтому постоянно предлагаемые частные решения оцениваются на основе неполных метрик, что затрудняет направленный прогресс. В этой статье предпринимается попытка прояснить проблему, а не предложить какое-либо ее новое решение. Анализируются вопросы, возникающие на границе между языками программирования и базами данных, включая типизацию, оптимизацию и повторное использование. Разрабатываются конкретные критерии оценки решений, и эти критерии применяются к упомянутым выше решениям. Анализ показывает, что прогресс действительно достигнут, хотя открытой остается проблема одновременного соответствия всем критериям.

Обновлено 10/12/2005

 

Все очень просто. Давайте договоримся: каждый будет в своем углу. Вы здесь, вы там, а я тут. И давайте молчать: ни слова, ладно? Это не так уж сложно. У каждого из нас есть свои мысли.

Жан-Поль Сартр. За закрытыми дверями
Перевод Л. Каменской.

1. Введение

Программы, использующие базы данных, являются важной частью нашей информационной инфраструктуры. В этих системах языки программирования используются для вычислений общего назначения, а базы данных – для надежного и безопасного управления параллельным доступом к данным, поиска в больших объемах данных и обновления данных. Такие системы во все возрастающем количестве разрабатываются с использованием процедурных объектно-ориентированных языков и реляционных баз данных. Для обеспечения масштабируемости и надежности несколько серверов приложений обычно взаимодействует с совместно используемым, реплицируемым сервером баз данных.

Процедурные языки и языки запросов баз данных основываются на разных семантике и стратегиях оптимизации. Эти различия неформально называются «потерей соответствия» (impedance mismatch) [32]: между императивными программами и декларативными запросами; алгоритмами и структурами данных, с одной стороны, и отношениями и индексами, с другой стороны; потоками управления и транзакциями; пустыми указателями и неопределенными значениями в смысле отсутствия данных. Различаются также подходы к модульности и сокрытию информации. Поскольку базы данных и языки программирования могут выполнять много одинаковых задач, разработчикам приходится принимать архитектурные решения по поводу функциональной организации и разделения системы. Для распределенного исполнения также требуются эффективная структуризация и управление специализированными паттернами коммуникаций. В результате приложения, нуждающиеся в доступе к базам данных, трудно проектировать и разрабатывать. Языки программирования не облегчают эффективное использование баз данных, и для достижения хорошей производительности обычно требуется тщательная оптимизация, основанная на экспертных знаниях, что затрудняет сопровождение и развитие программ.

Эта статья позволяет лучше понять суть потери соответствия, или проблемы интеграции баз данных и языков программирования. Аспекты, влияющие на пограничный слой между языками программирования и базами данных, исследуются для создания списка критериев оценки решений. Эти критерии разбиваются на три основных категории: типизация, оптимизация и повторное использование. При выборе критериев мы полагаемся на свой опыт разработки коммерческих приложений, ориентированных на данные, и на применение теории языков программирования и баз данных. Процесс выбора критериев является, по сути, субъективным, но пригодность критериев определяется их возможностью выявления полезных различий между разными подходами к решению проблемы.

Мы применяем свои критерии к ряду конкретных решений проблемы потери соответствия, включая объектно-ориентированные базы данных, объектно-реляционные преобразователи, API для доступа к данным, языки программирования с ортогональной персистентностью и встраиваемые языки запросов. Рассматриваются подходы, включающие модификации либо языков программирования, либо интерфейсов баз данных. Однако наши критерии позволяют оценивать аспекты и языков программирования, и баз данных, так что решение, обеспечивающее понятную программную модель, но не поддерживающее оптимизацию в стиле баз данных, не будет считаться удачным решением проблемы потери соответствия.

В ходе своих рассуждений мы выделяем области, в которых удалось достичь существенного прогресса, а также те области, где требуются дополнительные исследования. Предлагаемые критерии обеспечивают полезный базис для понимания решений, принимаемых архитекторами при выборе подхода к интеграции языков программирования и баз данных, а также ориентиры для дальнейших исследований. Мы полагаем, что основной причиной сложности проблемы потери соответствия является затруднительность соответствия всем критериям одновременно.

2. Родственные исследования

В этом разделе анализируются статьи, посвященные разъяснению проблем, которые встречаются при интеграции языков и баз данных. Конкретные интеграционные решения обсуждаются в основных разделах статьи.

В 1987 г. Аткинсон и Бьюнман опубликовали статью [6], в которой анализировались ранние исследования в области интеграции языков программирования и баз данных; в своей статье они сосредотачиваются на создании четкой, единой программной модели персистентных данных, обеспечивающей каркас для дальнейших исследований. Дэвид Майер в [32] сформулировал ключевое требование для решений проблемы потери соответствия: «Какой бы ни была модель программирования баз данных, она должна допускать вынесение из программ сложных операций над большими объемами данных и их выполнение менеджером памяти, а не принуждать программистов к использованию интерфейса уровня записей». Блюм и Здоник в [12] выявили культурные и технические различия между языками программирования и базами данных, включая управление параллелизмом, триггеры, оптимизацию и масштабирование данных. Манифест систем объектно-ориентированных баз данных [4] не включал какие-либо требования к интерфейсу ООБД с языками программирования, и производительность не включалась в состав требований верхнего уровня. Десять лет спустя Кэри и Девитт предсказали кончину персистентных языков программирования и объектно-ориентированных баз данных, а также итоговый успех объектно-реляционных баз данных [14]. Они также идентифицировали в качестве одной из ключевых исследовательских проблем интеграцию баз данных и языков программирования, которую они назвали клиентской интеграцией. Аткинсон в [5] анализировал сложность экспериментальной проверки нового подхода к персистентности.

Йордан в [29] сравнивает персистентные среды для платформы Java. В [15] Йордан характеризует реализацию эталонного тестового набора OO7 как Java-программу, манипулирующую Java-объектами, расположенными в основной памяти. Затем этот тестовый набор используется в качестве стандарта для количественного и качественного сравнения. К сожалению, тестовый набор OO7 моделирует только единственного пользователя, так что не затрагивается важный аспект параллельности. Йордан также предполагает, что все данные могут поместиться в основной памяти. Наконец, тестовый набор OO7 не представляет наиболее распространенные операции в типичных транзакционных/корпоративных приложениях, поскольку фокусируется на обходе иерархических структур. OO7 создавался для тестирования той разновидности специализированных приложений, для которой разрабатывались объектно-ориентированные базы данных. Йордан приводит числовые показатели производительности, но не обобщает свой количественный анализ. В этом обзоре мы не приводим числовые показатели производительности. Вместо этого мы предполагаем, что языки программирования должны давать возможность доступа к оптимизациям базы данных, и приводим качественный анализ того, насколько эффективными они являются при обеспечении этого доступа.

3. Типизация

Сложности согласования типов между языками программирования и базами данных традиционно считаются ключевой причиной потери соответствия.

И в языках программирования, и в базах данных имеется поддержка примитивных типов и структур данных. Хотя детали отображения разных представлений данных могут вызывать неприятные проблемы, на концептуальном уровне модели данных в языке программирования и в базе данных являются совместимыми. Это не является удивительным при имеющейся универсальности методов структуризации данных. Хотя данные и типы совместимы, имеются существенные проблемы при статической типизации запросов и составных программ.

class Employee {		class Department {
    String  name;		    String name;
    float salary; 		    Set<Employee> employees;
    Department department;	    Employee manager;
}				}

Рис. 1. Пример схемы базы данных, определяемой на основе классов

3.1 Отображение данных (T1)

Примитивные типы в языках программирования обычно не соответствуют типам в базе данных, и примитивные типы в разных базах данных обычно различаются. Например, в SQL-92 не определяется абсолютная точность многих числовых типов. Операции также могут быть не согласованными; распространенным примером является сравнение интернациональных строк.

Методы отображения классов в реляционные базы данных являются предметом расширенных исследований и разработок [1]. В общих словах, наиболее распространенный подход заключается в определении отображений между моделью «сущность-связь» (entity/relationship, ER-моделью) и объектно-ориентированной моделью классов. ER-модель обеспечивает логическое представление структуры реляционной базы данных. В ER-модели атрибуты представляют примитивные значения данных, такие как строки символов и целые числа. Они отображаются в члены экземпляра объекта в объектно-ориентированной модели. Связи ER-модели отображаются в ссылки между объектами. Многозначная связь является коллекцией ссылок. В ER-модели может быть также представлена подтипизация, имеющаяся в объектно-ориентированной модели. В некоторых случаях имеется несколько способов выполнения отображения, и результирующие проектные решения обычно основываются на производительности или других факторах.

Например, на рис. 1 определена простая модель служащих и отделов в виде двух Java-классов. В терминах баз данных поля department и employees представляют связь один-ко-многим между Departments и Employees.

Персистентность методов. При рассмотрении отображения между объектами и базами данных некоторые исследователи предлагают персистентно сохранять методы объекта, а не только состояние объекта [7]. Иногда предлагается разрешить даже персистентность потоков управления, элементов управления пользовательских интерфейсов и сетевых соединений [30]. Учитывая, что интеграция состояния и поведения является одной из ключевых концепций объектно-ориентированного программирования, можно утверждать, что персистентное отображение, в котором не сохраняются поведение/методы, нарушает базовые принципы объектно-ориентированного программирования. С другой стороны, разделение данных и поведения оказалось достаточно полезным при разработке и развитии приложений, требующих переработки большого объема данных. Этот вопрос остается без ответа, и в данном обзоре не предлагается какой-либо критерий для оценки полезности персистентности методов.

3.2 Интеграция null-значений (T2)

Поведение null-значений в SQL отличается от поведения null-значений в большинстве процедурных объектно-ориентированных языков. В SQL null представляет «неизвестное» значение, так что примитивные операции, такие как сложение или конъюнкция, возвращают null-значение, если хотя бы один их операнд является null-значением. Например, x == null всегда возвращает null, даже если x является null-значением. С другой стороны, в агрегатных функциях SQL, таких как sum, null-значения игнорируются. В объектно-ориентированных языках программирования обычно допускаются null-значения объектных ссылок, но для примитивных типов, таких как целые числа, null-значения невозможны. В реляционных соединениях null-значения также обрабатываются как «неизвестные» значения, но разыменование null-указателя в объектно-ориентированном языке обычно приводит к возникновению исключительной ситуации.

В некоторых языках, таких как C++ и C#, допускается определение пользовательских типов данных, которые соответствуют семантике баз данных, но могут использоваться вместо встроенных типов языка программирования. Такая возможность бесшовной интеграции внешних типов поддерживается не во всех языках программирования.

4. Статическая типизация (T*)

Статическая типизация является распространенным средством, используемым для повышения надежности и производительности как в языках программирования, так и в базах данных. В языках программирования статическая типизация используется для проверки программ до их запуска – чтобы убедиться в том, что во время выполнения к данным будут применяться только допустимые операции. Статическая типизация может улучшать производительность, поскольку эти проверки можно не производить во время выполнения. Она также способствует модульным разработкам, поскольку клиенты и серверы могут писаться и проверяться на основе строго определенных интерфейсов. В базе данных обычно выполняется проверка запросов на предмет отсутствия ошибок типов до компиляции запросов.

Критерий статической типизации отличается от критериев отображения данных и интерпретации null-значений. Это связано с тем, что статическая типизация не является свойством данных; это свойство системы, управляющей данными, и способ интерпретации ею программ или запросов. Таким образом, статическая типизация является мета-аспектом, применимым к другим критериям. Например, отображение данных может производиться во время выполнения, а может статически проверяться. В нашей оценке статическая типизация является дополнительным измерением оценки по другим критериям, а не отдельным критерием.

5. Стили интерфейсов

Пространство решений интеграции языков программирования и баз данных можно охарактеризовать двумя экстремумами: ортогональная персистентность и явное выполнение запросов. В конкретных решениях, анализируемых в разд. 9, используется некоторая комбинация этих двух подходов. Ортогональная персистентность – это чистый подход к персистентности, при котором механизмы персистентности или даже существование нижележащей базы данных в значительной степени скрываются от программистов. Явное выполнение запросов – это прагматический подход, позволяющий существующим языкам явно вызывать операции баз данных.

void printInfo(String prefix) {
for (Employee e in db.allEmployees() )
    if ( e.name.startsWith(prefix) && e.salary > e.manager.salary ) {
	print( e.name );
	print( e.salary );
	print( e.department.name );
    }
}

Рис. 2. Печать информации о служащих

5.1 Ортогональная персистентность (S1)

Ортогональная персистентность является естественным расширением традиционного понятия времени жизни переменной, позволяя объектам или значениям продолжать существовать после завершения выполнения какой-либо одиночной программы [6]. В наиболее чистом виде персистентные значения существуют до тех пор, пока на них ссылается (транзитивно) персистентный корень, хотя исследовались и способы поддержки персистентности с использованием явных операций, таких как удаление. Персистентность ортогональна, поскольку свойство персистентности значения является независимым от каких бы то ни было других факторов, включая тип значения или обстоятельства его создания.

Программы, манипулирующие персистентными данными, выглядят подобно обычным программам. В предположении, что db является корнем персистентности, содержащим коллекцию служащих, в примере на рис. 2 находятся все служащие, фамилии которых начинаются с заданного префикса, и зарплата которых превышает зарплату их менеджера. Затем печатаются фамилия служащего, его заработная плата и название отдела.

К числу примеров систем с ортогональной персистентностью относятся PJama [7], Thor [31] и OPJ [33]. В системах с чистой ортогональной персистентностью часто реализуется собственный менеджер хранения, а не используется существующая технология баз данных. Ортогональность полезнее представлять не как бинарное свойство, а в виде спектра. При таком представлении ортогональность является показателем уровня единообразия при работе с персистентными и не персистентными данными. Это представление является разумным также и по той причине, что некоторые операции, в частности, те, которые связаны с транзакциями, осмысленны только для персистентных данных, так что требуется некоторая степень неортогональности [11].

В большинстве объектно-ориентированных баз данных (ООБД) реализуется некоторый уровень ортогональной персистентности, хотя часто допускается персистентность только тех значений, которые являются объектами [16]. ООБД редко бывают чисто ортогональными, поскольку для пересистентных данных обеспечиваются специальные операции для их запросов. Некоторый уровень ортогональности обеспечивают и средства объектно-реляционного отображения. В число примеров входят TopLink, JDO, EJB и Hibernate [20, 26, 37, 34].

5.2 Явное выполнение запросов (S2)

Основной альтернативой ортогональной персистемности и ее историческим предшественником является выполнение запросов, написанных на специализированном языке запросов. Основным преимуществом явного выполнения запросов является то, что программист имеет возможность непосредственного взаимодействия с сервером базы данных.

string empQuery = “SELECT e.name, e.salary, d.name as deptName”
    + “FROM (Employee e INNER JOIN Department d ON d.ID = e.department)”
    + “INNER JOIN Employee m ON m.ID = e.manager”
    + “WHERE e.name LIKE ? AND e.salary > m.salary”

Connection conn = DriverManager.getConnection(...);
PreparedStatement stmt = con.prepareStatement(empQuery);
stmt.setString(1, prefix + “%”);
ResultSet rs = stmt.executeQuery(empQuery);
while ( rs.next() ) {
    print( rs.getString(“name”) );
    print( rs.getDecimal(“salary”) );
    print( rs.getString(“deptName”) );
}

Рис. 3. Явное выполнение запроса с использованием JDBC

Встраиваемые запросы. Явные запросы могут встраиваться в язык программирования или обрабатываться препроцессором [27]. К числу примеров относится SQLJ [8]. Встраиваемый SQL обеспечивает статически типизированный подход к явному выполнению запросов. Как обсуждается в разд. 7, одним из существенных недостатков встраивания является отсутствие поддержки динамических запросов. Другой проблемой является то, что изменение синтаксиса языка программирования обычно приводит к нарушению работы средств рефакторинга, интегрированных сред разработки (Integrated Development Environment, IDE) и CASE-средств.

Интерфейсы уровня вызова. Доминирующим механизмом явного выполнения запросов является интерфейс уровня вызовов (call level interface, CLI) [28], обеспечивающий языку программирования доступ к серверу баз данных на основе стандартизованного API [28, 24]. Ключевой характеристикой CLI является возможность выполнения запросов и команд базы данных, представленных в виде строк или других структур данных времени выполнения [26]. На рис. 3 показано, как запрос с рис. 2 может быть выполнен с использованием JDBC [24].

В большинстве систем с ортогональной персистентностью явные запросы не поддерживаются. В некоторых, хотя и не во всех объектно-ориентированных базах данных поддержка явных запросов имеется. В большинстве средств объектно-реляционного отображения явные запросы поддерживаются в качестве дополнения к ортогональной персистентности. Иногда явные запросы добавляются для решения проблем производительности; например, в EJB 1.0 поддержка запросов отсутствовала, а в EJB 2.0 – появилась. В других системах, таких как TopLink, JDO и Hibernate, имеются сложные языки запросов. Запросы могут представляться не только символьными строками, но и структурами данных времени выполнения. В Hibernate допускается представление запросов в виде критериальных объектов. Имена полей в этих системах по-прежнему представляются в виде строк символов.

Интерфейсам уровня вызовов свойственен ряд существенных проблем. Синтаксис и типы программ баз данных не проверяются статически, так что ошибки не выявляются до времени выполнения. Для конструирования и повторного использования запросов во время выполнения требуются сложные и чреватые ошибками манипуляции. Результаты запросов представляются как динамически типизируемые объекты, доступ к которым производится по именам строк. В некоторых ситуациях для встраиваемых запросов возможна проверка типов. Гулд, Су и Деванбу [23] применяют статический анализ для проверки программ, использующих интерфейсы уровня вызовов. Их анализ в настоящее время не покрывает параметры запросов и типы результатов и может приводить к неверным результатам при раздельной компиляции компонентов. Основным достоинством подхода является его применимость к существующим программам.

Несмотря на наличие этих проблем, во многих проектах по разработке коммерческого программного обеспечения используются интерфейсы уровня вызова для эффективного использования оптимизаторов запросов баз данных и сокращения коммуникационных задержек с целью повышения общей производительности системы.

6. Оптимизация

Для приложений, работающих с большими объемами данных, важно оптимизировать доступ к данным. Адекватная стратегия выполнения запроса часто может оказаться на несколько порядков быстрее прямолинейной стратегии [41].

В примерах этого раздела представлены распространенные оптимизации запросов, но с точки зрения языка программирования. Для обеспечения более глубокого анализа решений мы разделяем понятия поиска и навигации. Это приблизительно соответствует разделам WHERE и SELECT в языке SQL: поиск относится к выбору подмножества интересующих пользователя объектов, а навигация используется для обработки результатов поиска. Это различие является важным, поскольку во многих решениях одно из этих понятий поддерживается лучше другого.

6.1 Оптимизация поиска

Первой проблемой является оптимизация поиска. В простой программе, показанной на рис. 1, тратится время, пропорциональной числу служащих, хотя лишь для немногих из них фамилия будет начинаться с заданного префикса. Для улучшения этого алгоритма могут применяться традиционные методы оптимизации баз данных, основанные на использовании индекса. Это может существенно сократить время выполнение, которое станет пропорциональным числу служащих с фамилиями, соответствующими первому условию.

Явные индексы (P1). Распространенным методом является изменение порядка подопераций, таких как итерирование и проверка условия. Так, оптимизатор запросов может использовать индекс для вычисления множества идентификаторов записей, удовлетворяющих условию, а затем найти соответствующие данные путем поиска этих идентификаторов во втором индексе. Этот аспект плана иллюстрируется на рис. 4, где эта оптимизация применяется к исходному Java-коду.

Проверка префикса реализуется путем поиска по индексу: метод match возвращает итератор над множеством элементов индекса, соответствующих первому условию. Затем для нахождения данных о служащих используется эффективный индекс, отображающий идентификаторы записей в значения записей. Если большинство фамилий не начинается с заданного префикса, этот метод будет гораздо эффективнее линейного поиска. Явное программирование с использованием индексов поддерживается в Exodus [13] и Ontos [40].

void printInfo(String prefix) {
    for (IndexItem l in employeeNameIndex.match(“S”)) {
	Employee e = employeeID_Index.lookup(l.ID);
	if (e.salary > managerID_Index.lookup(e.managerID).salary) {
	    print( e.name );
	    print( e.salary );
	    Department d = departmentID_Index.lookup(e.deptID);
	    print( d.name );
	}
    }
}

Рис. 4. Оптимизированная выдача на печать информации о служащих

Если такие оптимизации должны выполняться вручную, производительность программиста существенно понижается, поскольку даже при незначительном изменении исходного неоптимизированного кода, например, при добавлении в оператор if дополнительного условия, может потребоваться значительное переписывание оптимизированного кода.

Пересылка критериев. В большинстве баз данных обеспечивается автоматическое управление индексами, и выполняется оптимизация запросов на основе детального знания структуры, содержания и расположения данных [21]. Оптимизатор запросов базы данных производит план для всей операции, которую требуется выполнить. При оптимизации запроса учитываются детали выполняемой операции и контекст, в котором она будет выполняться. Контекст включает статистические свойства реальных хранимых данных, объем доступной памяти, загрузку процессора, частоту поступления различных видов запросов и т.д. Для оптимизаций этого вида требуется специальное знание данных и их связей – но эта информация обычно недоступна для компиляторов языков программирования общего назначения.

Явное выполнение запросов является прагматическим подходом к оптимизации поиска: сокращается число взаимодействий с базой данных и, кроме того, оптимизатору запросов обеспечивается больший простор для оптимизации. Конечно, от программистов требуется вручную создавать соответствующие запросы.

Оказывается также возможным определить программные конструкции, которые позволяют задавать критерии поиска с использованием стандартного синтаксиса булевских выражений, но выполнять поиска в виде запроса к базе данных. В расширении Linq языка C# этот метод используется для сборки запросов, посылаемых в базу данных [18, 10]. Добавляется новая конструкция итерации, похожая на оператор выборки языка SQL, для указания критериев, которые должны обрабатываться в удаленном режиме.

Стандартные конструкции итерации в AppleScript позволяют специфицировать критерии поиска в соответствии с объектной моделью удаленного приложения [2]. Результирующие критерии поиска передаются для эффективной обработки в удаленное приложение.

Существующий синтаксис может использоваться и для выражения запросов. В Safe Query Objects для определения запроса используется обычный булевский метод [17]. Запросы не выполняются в виде стандартного байт-кода, а преобразуются в запросы к базе данных и соответствующий интерфейсный код для вызова CLI базы данных. Поскольку до преобразования запросы подвергаются проверке типов, вызовы CLI не вызывают ошибки типов.

6.2 Оптимизация навигации

В дополнение к оптимизации поиска объектов важно оптимизировать и навигацию к родственным объектам. Проблема заключается в том, что обычно имеется существенная латентность при загрузке объектов из их долговременного хранилища.

Упреждающее чтение родственных объектов (P3). В исходном коде на рис. 2 для печати названия отдела, в котором работает служащий, производится переход по связи department. В персистентной объектной системе этот переход приведет к загрузке соответствующего объекта-отдела, если он уже не загружен. Если персистентность поддерживается на основе реляционной базы данных, каждый объект-отдел может загружаться с использованием отдельного запроса, что существенно снижает производительность.

В большинстве персистентных объектных подсистем поддержки времени выполнения навигация не оптимизируется, хотя изучались методы повышения производительности за счет использования упреждающего чтения [9]. Этот вопрос и его взаимосвязь с модульностью, будет еще раз обсуждаться в разд. 7.

При использовании интерфейса уровня вызова от программиста требуется спецификация данных, производимых запросом, следовательно, программист отвечает за оптимизацию навигации. В средствах объектно-реляционного отображения поддерживается ограниченная оптимизация навигации. В EJB и JDO можно специфицировать автоматическую загрузку родственных объектов, но в настоящее время это свойство является глобальным, не специализированным для запросов. В Toplink и Hibernate 3 имеется более гибкая поддержка оптимизации навигации, но добавление соответствующих подсказок для загрузки достаточно обременительно, и механизмы не являются полностью универсальными. Например, в Toplink поддерживается загрузка только одного уровня многозначных податрибутов. Оптимизация навигации должна являться целью любого решения проблемы потери соответствия.

Многоуровневая итерация (P4). Особенно трудным случаем навигации является многоуровневая итерация через многозначные связи. Этот паттерн трудно представляется с использованием текущего варианта SQL. Одним из примеров является многоуровневая итерация, в которой несколько уровней многозначных зависимостей включается в результаты запросов. Такой паттерн иллюстрируется на рис. 5. Даже если коллекции родственных элементов загружаются в одном запросе, запросу требуется загрузить служащих каждого отдела и проекты каждого служащего. Если в Остине имеется n отделов, и в каждом отделе в Остине в среднем работают m служащих, то потребуется выполнить 1 + n + nm запросов.

Можно загрузить требуемые данные за три запроса: один для загрузки отделов в Остине, один для загрузки служащих, работающих в этих отделах, и один для загрузки проектов, в которых участвуют эти служащие. В каждый из этих запросов должно дублироваться условие из верхней части цикла. Если все результаты должны возвращаться в правильном порядке, то требуется поддерживать вложенный порядок сортировки. Наконец, клиент должен сопоставлять связанные элементы одной таблицы и соответствующие поднаборы во вложенных таблицах. Заметим, что возможен и одиночный запрос, хотя названия отделов и имена служащих будут дублироваться.

for each Department d in DB.getDepartments() sorted by size
    if d.city = ‘Austin’ then
	print( d.name );
for each Employee e in d.employees sorted by e.name
    print( e.name );
for each Project p in e.projects sorted by p.date
    print( p.name );

Рис. 5. Псевдокод для многоуровневой итерации

Этот стиль невозможно выразить на SQL, хотя это можно сделать на языке OQL. При пересмотре SQL следует обращать большее внимание на виды запросов, требуемых для поддержки объектно-реляционного отображения [42]. Если проводить аналогию с разработкой RISC-процессоров, SQL можно рассматривать как разновидность языка ассемблера: вместо того, чтобы разрабатывать понятный, удобный для человеческого восприятия интерфейс, более эффективным оказывается определять распространенные паттерны, генерируемые клиентскими программами, для разработки оптимизированного интерфейса. Проводилось исследование по обнаружению многоуровневой итерации и повышению ее эффективности с использованием упреждающего чтения [25], но ни в одной коммерческой системе этот тип упреждающего чтения не реализован.

6.3 Манипулирование массивными данными (P5)

В то время как в поиске по определению используется много объектов, операции обновления часто затрагивают только несколько объектов, что снижает возможности оптимизаций, описанных в предыдущих подразделах. Однако в любом приложении обычно имеется несколько случаев, в которых требуется манипулирование массивными данными. Операции манипулирования данными включают вставки, удаления и обновления данных. Упрощенным примером может служить следующая конструкция:

for (Employee e in db.allEmployees() )
    if ( e.department.name.equals(“Sales”) )
	e.salary = e.salary * 1.2;

В этом случае применимы оптимизации, описанные в предыдущем подразделе. Легко использовать явное выполнение запроса для запуска оператора SQL, выполняющего массивную операцию:

UPDATE Employee set salary = salary * 1.2 
    FROM Employee INNER JOIN Department d ON d.ID = e.Department
    WHERE d.name = 'Sales'

Но ни средства объектно-реляционного отображения, ни основанные на Java персистентные языки программирования не допускают эффективного выполнения массивных операций обновления в реляционных базах данных. AppleScript допускает удаленное выполнение операций обновления. В проектах языка DBPL [38] и его приемника Tycoon [35] исследовались оптимизация поиска и массивные операции в среде ортогональной персистентности. В проекте Tycoon предполагалось интегрировать оптимизацию в компиляторе с оптимизацией запросов к базам данных, но никакие окончательные результаты не были опубликованы [22]. Запросы, не ограниченные кодом одного модуля, оптимизировались во время выполнения путем применения динамической компиляции. Ни для DBPL, на для Tycoon не публиковались какие-либо оценки производительности; известно только число строк кода в реализации.

Кэширование. Стратегии кэширования не зависят от особенностей интерфейса между языком и базой данных, по крайней мере, с точки зрения программиста, использующего интерфейс. Конечно, на способы кэширования должны обращать внимание разработчики инфраструктуры доступа к данным и баз данных. Однако в этой статье мы фокусируемся на проектных решениях, затрагивающих пользователей инфраструктуры доступа к данным, а не разработчиков.

7. Повторное использование

В предыдущем разделе обсуждались вопросы локальной оптимизации. Кроме того, имеются аспекты, связанные с композицией или декомпозицией операций.

Параметризуемые запросы (R1). Параметризуемые и динамические запросы возникают при извлечении запросов из программы для их удаленного выполнения в базе данных. Например, при извлечении запроса из кода с ортогональной персистентностью, показанного на рис. 2, для его явного выполнения в виде SQL-запроса (см. рис. 3) префикс фамилии служащего становится параметром запроса. При явном выполнении запросов трудно специфицировать параметры запросов, и типы параметров не проверяются до времени выполнения.

Динамические запросы (R2). Динамические запросы – это запросы, которые представляются в виде символьных строк, конструируемых во время выполнения программы. Хотя идея динамических запросов может казаться ужасной, они достаточно распространены, и от них не следует сходу отказываться. Динамические запросы могут обрабатываться путем частичного выполнения запроса на основе данных, которые влияют на запрос, но не зависят от базы данных [17]. Например, если в поисковой форме пользовательского интерфейса допускается указание набора необязательных критериев поиска, то результирующий запрос можно частично вычислить для конкретного набора критериев. Подробное объяснение приводится в [17]. Динамические запросы также возникают при реализации тонких правил авторизации, применяемых индивидуально к каждому пользователю [36]. Мы полагаем, что отсутствие поддержки динамических запросов является основной причиной отказа от большинства форм встраиваемого SQL [27].

Динамические запросы также требуются для создания произвольных соединений (ad-hoc joins) в приложениях, поддерживающих оперативную аналитическую обработку (OLAP). Такие приложения в этой статье не обсуждаются.

Модульные запросы (R3). Поскольку в большинстве языков программирования общего назначения имеется поддержка модульной декомпозиции – с использованием функциональной абстракции и абстракции данных, – у соответствующих персистентных языков имеются те же возможности. Хорошо известно, что модульность может мешать оптимизации, но в контексте доступа к базам данных эта проблема может еще более усложниться. И поисковая, и навигационная оптимизация зависят от знания всех условий и данных, участвующих в доступе к персистентным данным. Оптимизация запросов лучше всего работает в тех случаях, когда базе данных поручается выполнение больших частей работы, а не отдельных операций. Тогда сокращается число взаимодействий приложения с базой данных, а оптимизатор получает больший простор для оптимизации. Важно заметить, что, хотя в реляционной алгебре поддерживается модульная композиция, поскольку каждый запрос является отношением, на практике может быть достаточно трудно комбинировать результаты двух SQL-запросов на семантическом уровне. В решении проблемы интеграции языков и баз данных следует поддерживать модульность, композицию и повторное использование структур программ, обрабатывающих большие объемы данных.

8. Параллелизм

Параллелизм в базах данных позиционируется не так, как в обычных языках программирования [3]. В языках программирования обычно используются потоки управления и синхронизация для определения параллельных процессов, которые взаимодействуют (cooperate) для достижения общей цели, периодически обмениваясь данными или запрашивая разделяемые ресурсы.

В базе данных параллелизм представляется как конкурентный (competitive): несколько транзакций конкурирует за доступ к разделяемым ресурсам. Индивидуальные операции могут перекрываться, если набор транзакций является сериализуемым. Менеджер транзакций может аварийно прекратить выполнение любой транзакции и откатить ее. В базах данных обеспечиваются гарантии общего поведения системы, а в языках программирования обычно такие гарантии отсутствуют.

Параллелизм вызывает существенные проблемы для чистых разновидностей ортогональной персистентности [11]. Вместо того чтобы настаивать на полной ортогонализации языков программирования, исследования следует сосредотачивать на обеспечении семантически осмысленного поведения транзакций/восстановления [19].

На пользу транзакциям идет и оптимизация поиска: если некоторая операция затрагивает много объектов, но обновляет только один из них, то затронутые объекты оставляют большой «след», который может мешать другим транзакциям. Оптимизация поиска сокращает число объектов, затрагиваемых транзакцией. Долго работающие транзакции с большей вероятностью блокируют другие транзакции или подвергаются аварийному завершению.

9. Оценка

На рис. 6 приведена сводка результатов качественного сравнения различных имеющихся решений проблемы потери соответствия. В разделах оптимизации и сопровождения содержатся две оценки. Первая оценка указывает на то, поддерживается ли данное свойство. Вторая оценка показывает, применяется ли в данном случае статическая типизация. Для систем, которые не проектировались в расчете на работу с реляционными базами данных, конечно, возможно отображение в реляционные базы данных.

Рис. 6. Сводка качественной оценки решений проблемы потери соответствия

PJama и OPJ являются вариантами Java с чисто ортогональной персистентностью [30]. В этих системах не поддерживаются истинные параллельные транзакции в смысле баз данных, но имеется понятие фиксации в «контрольных точках» глобально согласованного состояния области хранения [11]. Хотя целевая установка чисто ортогональной персистентности препятствует использованию явных запросов, остается открытым вопрос, может ли компилятор Java использовать пересылку критериев или навигационное упреждающее чтение в качестве части своей стратегии компиляции. В эти системы можно было бы включить и поддержку истинной модели ACID-транзакций, хотя это и уменьшило бы уровень ортогональности персистентных данных.

Exodus [13] и Ontos [40] – это объектно-ориентированные базы данных с собственным менеджером хранения. Для оптимизации поиска программисты могут использовать явные индексы. В Exodus ключ индекса является статически типизированным, но объекты данных, загружаемые с использованием индекса статически не типизируются. EJB 1.0 – это средство объектно-реляционного отображения, имеющее сходство с интерфейсом между объектами и базами данных. В EJB 1.0 не поддерживаются явные запросы, но допускаются методы-искатели (finder methods), представляющие собой разновидность явного индекса.

ObjectStore и O2 – это объектно-ориентированные базы данных с собственным менеджером хранения [40]. В них поддерживается пересылка критериев на основе использования критериальных объектов. Но для них отсутствует статическая проверка, и поля именуются строками. JDO 1.0 и EJB 2.0 являются средствами объектно-реляционного отображения для Java. Они обеспечивают высокий уровень ортогональной персистентности [29]. Поддерживается пересылка критериев. Частично поддерживается оптимизация навигации: ее можно задать в качестве глобального свойства связи, но не для отдельных запросов.

Hibernate и TopLink – это средства объектно-реляционного отображения для Java с некоторой поддержкой ортогональной персистентности. В каждом из них имеется специальный язык для пересылки критериев. В этих языках запросов поддерживаются пересылка критериев и навигационное упреждающее чтение, хотя спецификации упреждающего чтения не вполне универсальны. Также обеспечиваются частичные решения многоуровневой итерации.

ODBC и JDBC – это стандартные интерфейсы уровня вызова. Они обеспечивают полный набор функциональных возможностей, но не поддерживают статическую типизацию. При использовании этих интерфейсов оптимизация многоуровневой итерации является затруднительной. SQLJ [8] – это разновидность встраиваемого SQL для Java. Обеспечивается высокий уровень статической типизации, но не поддерживаются динамические или модульные запросы.

Safe/Native Queries (S/NQ) [17] и Linq [18] – это два новых проекта. Подобно JDO и Hibernate, Safe Queries строится поверх средства объектно-реляционного отображения, но в нем обеспечивается статическая типизация запросов, которые представляются как стандартные классы Java. Linq – это расширение Linq, позволяющее определять статически типизированные запросы над реляционными и другими источниками данных. В отличие от встраиваемого SQL в этих подходах поддерживаются динамические запросы. В Linq поддерживается разновидность многоуровневой итерации; поскольку для возврата всех данных в нем используется единственное соединение, значения из объемлющих итераций повторяются для каждого элемента, возвращаемого из вложенной итерации. В Linq также допускается некоторая разновидность упреждающего чтения, хотя возвращаемыми объектами являются записи, а не экземпляры отображаемых классов. Заметим, что наша оценка Linq носит предварительный характер.

10. Заключение

В полном решении проблемы потери соответствия должны обеспечиваться как понятная модель программирования, так и высокая производительность. Хотя проблемы отображения данных между базами данных и языками программирования в значительной степени решены, имеются и нерешенные проблемы. Интерфейс должен обеспечивать наилучшие возможности и для баз данных, и для языков программирования для оптимизации, статической типизации и модульной разработки. Для каждого из этих аспектов по отдельности имеются решения. Но в решении проблемы потери соответствия все эти цели должны достигаться одновременно. В этой статье предложены качественные критерии оценки имеющихся решений и в соответствии с этими критериями произведена оценка ряда имеющихся решений.

Литература

  1. Scott Ambler. Mapping objects to relational databases. 1998.
  2. Apple Computer Inc. AppleScript Language Guide. Addison-Wesley, 1993.
  3. M. P. Atkinson and R. Morrison. Orthogonally persistent object systems. VLDB Journal, 4(3):319{401, 1995.
  4. Malcolm Atkinson, David Dewitt, David Maier, Francois Bancilhon, Klaus Dittrich, and Stanley Zdonik. The object-oriented database system manifesto.
  5. Malcolm P. Atkinson. Persistence and java - a balancing act. In Proceedings of the International Symposium on Objects and Databases, pages 1-31, London, UK, 2001. Springer-Verlag.
  6. Malcolm P. Atkinson and O. Peter Buneman. Types and persistence in database programming languages. ACM Comput. Surv., 19(2):105-170, 1987.
  7. Malcolm P. Atkinson, Laurent Daynes, Mick J. Jordan, Tony Printezis, and Susan Spence. An orthogonally persistent Java. SIGMOD Record, 25(4):68-75, 1996.
  8. Julie Basu. An overview of SQLJ: Embedded SQL in Java. Oracle Open World, 1998.
  9. Philip A. Bernstein, Shankar Pal, and David Shutt. Context-based prefetch for implementing objects on relations. In The VLDB Journal, pages 327-338, 1999.
  10. Gavin Bierman, Erik Meijer, and Wolfram Schulte. The essence of data access in c. In European Conference on Object-Oriented Programming, 2005.
  11. Stephen Blackburn and John N. Zigman. Concurrency – the fly in the ointment? In POS/PJW, pages 250-258, 1998.
  12. Toby Bloom and Stanley B. Zdonik. Issues in the design of object-oriented database programming languages. In Proc. of ACM Conf. on Object-Oriented Programming,
    Systems, Languages and Applications, pages 441-451. ACM Press, 1987.
  13. M. J. Carey, D. J. DeWitt, G. Graefe, D. M. Haight, J. E. Richardson, D. T. Schuh, E. J. Shekita, and S. Vandenberg. The EXODUS extensible DBMS project: An overview. In D. Maier and S. Zdonik, editor, Readings on Object-Oriented Database Sys. Morgan Kaufmann, San Mateo, CA, 1990.
  14. Michael J. Carey and David J. DeWitt. Of objects and databases: A decade of turmoil. In Proceedings of the 22th International Conference on Very Large Data Bases, pages 3-14. Morgan Kaufmann Publishers Inc., 1996.
  15. Michael J. Carey, David J. DeWitt, Chander Kant, and Jeffrey F. Naughton. A status report on the OO7 OODBMS benchmarking effort. In Proc. of ACM Conf. on Object-Oriented Programming, Systems, Languages and Applications, pages 414-426. ACM Press, 1994.
  16. R. G. G. Cattell, Douglas K. Barry, Mark Berler, Jeff Eastman, David Jordan, Craig Russell, Olaf Schadow, Torsten Stanienda, and Fernando Velez, editors. The Object Data Standard ODMG 3.0. Morgan Kaufmann, January 2000.
  17. William R. Cook and Siddhartha Rai. Safe query objects: statically typed objects as remotely executable queries. In ICSE '05: Proceedings of the 27th international conference on Software engineering, pages 97-106, New York, NY, USA, 2005. ACM Press.
  18. Microsoft Corporation. The LINQ project.
  19. Laurent Daynes. Extensible transaction management in PJava. In Proceedings of the First International Workshop on Persistence and Java (PJW2), 1996.
  20. Jacques-Antoine Dub, Rick Sapir, and Peter Purich. Oracle Application Server TopLink application developers guide, 10g (9.0.4). Oracle Corporation, 2003.
  21. Michael J. Franklin, Björn Thór Jónsson, and Donald Kossmann. Performance tradeoffs for client-server query processing. In Proceedings of the 1996 ACM SIGMOD International Conference on Management of Data, pages 149-160. ACM Press, 1996.
  22. A. Gawecki and F. Matthes. Integrating query and program optimization using persistent CPS representations. In Malcom P. Atkinson and Ray Welland, editors, Fully Integrated Data Environments, ESPRIT Basic Research Series, pages 496-501. Springer Verlag, 2000.
  23. C. Gould, Z. Su, and P. Devanbu. Static checking of dynamically generated queries in database applications. In Proceedings, 26th International Conference on Sofware Engineering (ICSE). IEEE Press, 2004.
  24. Graham Hamilton and Rick Cattell. JDBCTM: A Java SQL API. Sun Microsystems, 1997.
  25. Wook-Shin Han, Yang-Sae Moon, and Kyu-Young Whang. PrefetchGuide: capturing navigational access patterns for prefetching in client/server object-oriented/object-relational dbmss. Information Sciences, 152(1):47-61, 2003.
  26. Hibernate reference documentation., May 2005.
  27. INCITS/ISO/IEC. Information technology - database languages - SQL – part 5: Host language bindings (SQL/Bindings). Technical Report 9075-5-1999, IN-CITS/ISO/IEC, 1999.
  28. ISO/IEC. Information technology - database languages - SQL - part 3: Call-level interface (SQL/CLI). Technical Report 9075-3:2003, ISO/IEC, 2003.
  29. Mick Jordan. Comparative study of persistence mechanisms for the java platform. Technical Report TR-2004-136, Sun Microsystems, September 2004.
  30. Mick Jordan and Malcolm P. Atkinson. Orthogonal persistence for the java platform - specifcation and rationale. Technical Report TR-2000-94, Sun Microsystems, September 2000.
  31. B. Liskov, A. Adya, M. Castro, S. Ghemawat, R. Gruber, U. Maheshwari, A. C. Myers, M. Day, and L. Shrira. Safe and efficient sharing of persistent objects in Thor. In Proceedings of the 1996 ACM SIGMOD international conference on Management of data, pages 318-329. ACM Press, 1996.
  32. David Maier. Representing database programs as objects. In Francois Bancilhon and Peter Buneman, editors, Advances in Database Programming Languages, Papers from DBPL-1, pages 377-386. ACM Press / Addison-Wesley, 1987.
  33. Alonso Marquez, Stephen Blackburn, Gavin Mercer, and John N. Zigman. Implementing orthogonally persistent Java. In Proceedings of the Workshop on Persistent Object Systems (POS), 2000.
  34. V. Matena and M. Hapner. Enterprise Java Beans Specification 1.0. Sun Microsystems, 1998.
  35. F. Matthes, G. Schroder, and J.W. Schmidt. Tycoon: A scalable and interoperable persistent system environment. In M.P. Atkinson, editor, Fully Integrated Data Environments. Springer-Verlag, 1995.
  36. Shariq Rizvi, Alberto Mendelzon, S. Sudarshan, and Prasan Roy. Extending query rewriting techniques for fine-grained access control. In SIGMOD '04: Proceedings of the 2004 ACM SIGMOD international conference on Management of data, pages 551-562. ACM Press, 2004.
  37. C. Russell. Java Data Objects (JDO) Specification JSR-12. Sun Microsystems, 2003.
  38. Joachim W. Schmidt and Florian Matthes. The DBPL project: advances in modular database programming. Inf. Syst., 19(2):121-140, 1994.
  39. J.W. Schmidt, F. Matthes, and P. Valduriez. Building persistent application systems in fully integrated data environments: Modularization, abstraction and interoperability. In Proceedings of Euro-Arch'93 Congress. Springer Verlag, October 1993.
  40. V. Soloviev. An overview of three commercial object-oriented database management systems: ONTOS, ObjectStore, and O2. SIGMOD Record (ACM Special Interest Group on Management of Data), 21(1):93-105, 1992.
  41. Je®rey D. Ullman, Hector Garcia-Molina, and Jennifer Widom. Database Systems: The Complete Book. Prentice Hall PTR, Upper Saddle River, NJ, USA, 2001.
  42. W.P. Zhang and Norbert Ritter. The real benefits of object-relational db-technology for object-oriented software development. In B. Read, editor, Proc. 18th British National Conference on Databases (BNCOD 2001), Advances in Databases, pages 89-104. Springer-Verlag, 7 2001.

Новости мира IT:

Архив новостей

Последние комментарии:

Loading

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 985 1945361
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2015 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...