SQL более гибок и удобен для распределенных запросов (сбор данных из нескольких баз данных и рассовывание их в несколько баз данных), чем фирменные программы; в т.ч. более удобен для репликации, чем фирменные программы. Но нет необходимого синтаксиса. Попытаемся устранить этот пробел.
При работе с несколькими базами данных, существует два чистых случая: выбор необходимой базы на основе ее идентификатора - назовем этот случай распределенным запросом (distributed query, dQ), и на основе значения индекса для записи - назовем этот случай обтекающим запросом (ambient query, aQ) [1]. Для того, чтобы в дальнейшем отличать упомянутые запросы от обычных, назовем последние одиночными (single query, sQ). Одноименные таблицы разных баз данных будем называть генеральной таблицей.
Важным отличием dQ является нарушение unique constraint для общества, т.к. записи могут дублироваться в одноименных таблицах разных баз. Что касается синтаксиса, предлагаю:
connect address=remote.bz; -- база данных по умолчанию, хранитель общества create nick db1 address=site.com; -- прозвища создаются в базе по умолчанию create nick db2 address=data.net; create nick db3 address=store.org; create nick db4 address=place.ws; insert into tab select * from db1:tab; -- копирование в базу по умолчанию insert into db3:tab select * from db1:tab;
create society s; create society c; append db1, c, default to s; -- база по умолчанию добавлена в общество 's' refuse db2, c from s; insert into tab select * from s:tab; insert into db1:tab select * from s:tab; insert into (db1+db4):tab select * from s:tab; insert into s:tab select * from s:tab; insert into (s-c):tab select * from s:tab;
В целях безопасности предлагаю концепцию полного недоверия одной базы данных другой:
Таким образом одна база данных не может породить и ввести sql-команду в другую базу данных ни прямо, ни косвенно (прося клиента перенаправить команду). А клиент не может сконструировать новую sql-команду на основе разбора (parse) введенной.
Первая база данных отправляет клиенту специальную команду, которая заставляют клиента сделать подстановку в последней отправленной первой базе данных sql-команде, запомненной в стеке клиента, и выслать результат преобразования второй базе данных. Специальные команды должны быть настолько ограниченными, чтобы не допускать порождения sql-команды, вредной для второй базы данных (подробности на с.135-161 отдельного pdf-документа).
В SQL не должна существовать возможность установить общество-по-умолчанию (дабы запрос по умолчанию отправлялся не в одну таблицу, а во все таблицы общества), т.к. автоматическая вставка (INSERT) во все базы данных еще имеет смысл, а визуализация (SELECT) одинаковых данных всех баз - никакого. Таким образом используемое общество устанавливается принудительно с помощью префикса перед именем схемы [3].
Сбор записей из баз данных общества может выполняться во всех базах одновременно (SELECT ... PARALLELLY, с.139), если мы хотим сравнить записи, содержащиеся в них, или не знаем, в какой из баз данных они расположены, но хотим получить ответ максимально быстро - или последовательно (SELECT ... SEQUENTIALLY, с.140, первая возвращенная запись останавливают перебор баз данных), если хотим уменьшить количество операций во всех компьютерах общества. SELECT без постфикса выполняется одновременно или последовательно в зависимости от состояния переключателя, например 'hobo': 'SET HOBO PARALLELLY', 'SET HOBO SEQUENTIALLY'.
Модификация данных (UPDATE, DELETE) всегда выполняется одновременно, а не последовательно, т.к. ни одну базу данных заранее нельзя исключить из обработки.
Используемое во всех запросах общество-по-умолчанию может быть установлено директивой, например 'SET AUTOSOCIETY societyname', или установлено принудительно с помощью префикса перед именем схемы через восклицательный знак [3]. В любом случае запрос сначала отправляется в базу-данных-по-умолчанию, которая хранит определение общества, и которая дает указание клиенту сделать подстановку в последней SQL-команде, сохраненной в его стеке, и действовать в соответствии с результатом трансформации.
Только в базе-данных-по-умолчанию [4] мы должны указать, на основе каких полей какой генеральной таблицы какого общества должно происходить разбрасывание записей. Таким образом помимо обычных индексов для поиска записей, в базе-данных-по-умолчанию нужны отдельного рода индексы для поиска других баз данных - будем называть их глоссариями и создавать их командой 'CREATE GLOSSARY glossaryname ON tablename (fieldname) FOR societyname'.
Совокупность индексов одного и того же поля одноименных таблиц разных баз данных (включая базу-данных-по-умолчанию) одного общества (независимо от того, имеют индексы одинаковое название или нет) будем называть генеральным индексом [5]. Совокупность уникальных индексов - уникальным генеральным индексом. Глоссарий и уникальный генеральный индекс обеспечивают unique constraint для генеральной таблицы.
Поскольку глоссарий и генеральный индекс индексируют одно и тоже поле, предлагаю в глоссарии хранить не сами проиндексированные значения, а только диапазоны значений, принадлежащих той или иной базе данных. Для любых двух баз эти диапазоны не пересекаются, иначе вместо aQ мы получим dQ [6].
На случай, если будут вставляться записи с близкими индексируемыми значениями (они попадут преимущественно в один интервал, и нагрузка ляжет преимущественно на одну базу данных), предлагаю индексировать не сами значения, а их хэш-суммы; глоссарии создавать командой 'create HASH GLOSSARY glossaryname on tablename (fieldname) for societyname', и хранить в них диапазоны хэш-сумм. Такой глоссарий равномерно разбросает записи с близкими индексируемыми значениями по всем базам общества. Использовать нехэширующие или хэширующие глоссарии - зависит от того, какие aQ-запросы преимущественно выполняются: вычисляющие корреляцию записей (тогда лучше использовать 'create GLOSSARY'), или не делающие этого (тогда лучше 'create HASH GLOSSARY'). Вычисление корреляции по близким индексируемым значениями, находящимся в разных базах, породит большой траффик.
После добавления/изъятия базы данных из общества придется обращаться за записями уже не в одну базу данных, а в две - и в ту, на которую указывал глоссарий до добавления/изъятия, и в ту, на которую после. Чтобы замена одной базы данных на две случалась как можно реже, желательно, чтобы после добавления/изъятия наибольшее количество диапазонов глоссария отсылали к прежней базе.
После добавления новой базы данных в общество ('APPEND nick TO societyname'), часть записей из тех, что ранее внесли бы в другую базу (INSERT), будут направлены в новую - иначе она останется незадействованной. Для сохранения равномерного распределения нагрузки нужно, чтобы 1/(M+1) часть потока записей, вставляемых ранее в каждую из M баз, была направлена в добавленную базу. А для этого адресующие в них диапазоны должны быть расщеплены на двое в пропорции M:1. Только INSERT будет выполняться по новому набору диапазонов - SELECT, UPDATE, DELETE будут выполняться и по старому, и по новому наборам. А значит в глоссарии нужно хранить все предыдущие наборы диапазонов и URLs [7], соответствующие им.
После изъятия базы данных из общества ('REFUSE nick FROM societyname'), новые записи, которые ранее внесли бы в эту базу, будут направлены в оставшиеся M-1 базы - иначе записи будут потеряны. Для сохранения равномерного распределения нагрузки нужно, чтобы в каждую из них была направлена 1/(M-1) часть этого потока. А для этого адресовавший в изъятую базу диапазон должны быть расщеплен на M-1 равных частей. Чтобы SELECT, UPDATE, DELETE выполнялись в наименьшем количестве баз, за все время существования диапазон должен указывать на наименьшее их количество, поэтому предлагаю при повторном добавлении изъятой базы наделять ее в первую очередь диапазонами, ранее принадлежавшими ей, а не участками диапазонов других баз. И считать базы идентичными, если у них совпадает URL [7]. А значит в глоссарии опять же нужно хранить все предыдущие наборы диапазонов и URLs, соответствующие им.
[1] Надо заметить, что вставка (INSERT) записи в случайную базу общества, или в каждую следующую базу для каждой следующей записи, хотя и указывает базу на основе значения, вычисленного для записи, по существу является случаем dQ: это хорошо видно на примере остальных трех операторов SELECT, UPDATE, DELETE, которые не знают, где расположены записи, подпадающие под их WHERE
insert into m%s:tab select * from %s:tab -- 'm%s' and '%s' are different societies where @fld > (select @f from m%s:tab); -- 'into m%s:tab' and 'from m%s:tab' are the same database
[3] Для dQ и aQ должны использоваться разные разделители, например двоеточие для первого и восклицательный знак для второго: 'societyname:tablename' и 'societyname!tablename'
[4] И ни в какой другой, чтобы избежать даже возможной обратной пересылки данных
[5] Его легко создать dQ-запросом 'CREATE INDEX societyname:indexname ON tablename (fieldname)'
[6] В этом смысле глоссарии всегда 'unique', не требуется писать 'create UNIQUE glossary'
[7] Таким образом глоссарий представляет собой множество записей некой системной таблицы с тремя колонками: нижняя и верхняя границы диапазона, URL. Не лишним будет для ее поля 'URL' иметь системный (т.е. недоступный для команд 'alter index, drop index') индекс. Кроме того, если внешний мир каждую схему базы видит как отдельную базу данных (с.176-178), то база-схема идентифицируется уже не одним URL, а тройкой параметров - URL, OS user, schema. И как следствие базы-схемы являются совпадающими только при совпадении всех трех параметров, и все три они должны храниться в глоссарии, и упомянутый системный индекс должен строиться по всем трем (итого вместе с именем пользователя в ОС и путем к вложенной схеме, упомянутая системная таблица будет иметь пять колонок)
Тюрин Дмитрий, dmitryturin@yandex.ru