Если вам необходимо на друпал-сайте использовать данные из другой базы (может быть даже с другого хоста), то это достаточно просто сделать. Предоставленные для этого возможности не затрагивают кода ядра и могут быть использованы, как в виде вставок PHP-кода в материалы сайта, так и в собственных модулях и темах.
Находим в папке sites/default файл settings.php.
Редактируем его. Вместо строчки
ставим
'default'=>'mysql://username:pass@localhost/databasename',
'db1'=>'mysql://username1:pass@host1/databasename1',
'db2'=>'mysql://username2:pass@host2/databasename2',
/*
** Сколько угодно параметров для требуемого количества баз данных
** Индексы кроме 'default' могут быть любыми.
*/
);
Теперь если в сниппете, модуле, блоке или материале вам понадобятся данные из других баз, используем следующий код:
** Делаем активной базу с параметрами под индексом 'db1'
*/
db_set_active('db1');
$result = db_query("Здесь нужный вам запрос к таблицам в db1")
/*
** Здесь обрабатываем результат первого запроса
*/
// Делаем активной базу с параметрами под индексом 'db2'
db_set_active('db2');
$result = db_query("Здесь нужный вам запрос к таблицам в db2")
/*
** Здесь обрабатываем результат второго запроса
*/
/*
** В конце ОБЯЗАТЕЛЬНО делаем активной «родную» базу,
** чтобы Drupal мог нормально завершить обработку страницы.
*/
db_set_active('default');
Переключаться между базами можно сколько угодно раз – после первого обращения Drupal кеширует ресурс соединения с БД в массиве $db_conns и повторного соединения не производится.
Если вам недоступно редактирование файла установок, то можно установить требуемое соединение сразу в PHP-коде:
** Получаем глобальные переменные Drupal
*/
global $db_url;
/*
** Добавляем свою строку подключения к БД, а родную оставляем под индексом 'default'
*/
$db_url = array(
"default"=>$db_url,
"db1"=>"mysql://username1:pass@host1/databasename1"
);
/*
** Делаем активной базу с параметрами под индексом 'db1'
*/
db_set_active('db1');
$result = db_query("Здесь нужный вам запрос к таблицам в db1")
/*
** Здесь обрабатываем результат запроса
*/
/*
** В конце ОБЯЗАТЕЛЬНО делаем активной «родную» базу,
** чтобы Drupal мог нормально завершить обработку страницы.
*/
db_set_active('default');
Указанные решения работают в D5 и D6.
Единственное ограничение – тип баз данных должен быть одним для всех соединений, т.е. следующий код вызовет ошибку:
'default'=>'mysqli://username:pass@localhost/databasename',
'db1'=>'pgsql://username1:pass@host1/databasename1',
);
Вы получите: Cannot redeclare db_status_report() (previously declared in /var/www/mysite/includes/database.mysqli.inc:23) in /var/www/mysite.ru/includes/database.pgsql.inc
Устранить данное ограничение можно только хаком ядра Drupal. Хак получается весьма объемистым. С указанной ошибкой все дело не в функции db_status_report(), а со способом которым Drupal подключает интерфейс требуемого типа баз данных. Дело в том, что названия всех функций (за исключением одной – db_check_setup) находящихся в файлах database.pgsql.inc, database.mysql.inc, database.mysqli.inc и database.mysql-common.inc совпадают, что приводит к конфликту в именах 41 функции. Поэтому использовать несколько подключений к базам разных типов можно только полностью переписав слой абстракции баз данных в Drupal.
Чтобы сделать это откроем все упомянутые файлы в редакторе.
Из файлов database.mysql.inc и database.mysqli.inc удаляем строчку
Содержимое файла database.mysql-common.inc копируем в файлы database.mysql.inc и database.mysqli.inc, а сам файл делаем пустым.
Далее во всех трех файлах database.pgsql.inc, database.mysql.inc, database.mysqli.inc переименовываем все функции добавляя к их именам постфикс типа базы данных: в файле database.pgsql.inc – _pgsql; в файле database.mysql.inc – _mysql; в файле database.mysqli.inc – _mysqli. Переименовывать не надо только функцию db_check_setup – она уникальна для файла database.pgsql.inc.
Открываем файл database.inc и создаем там 41(!!!) функцию со следующими именами.
_db_create_key_sql()
_db_create_keys_sql()
_db_process_field()
_db_process_field()
_db_query()
db_add_field()
db_add_index()
db_add_primary_key()
db_add_unique_key()
db_affected_rows()
db_change_field()
db_column_exists()
db_connect()
db_create_table_sql()
db_decode_blob()
db_distinct_field()
db_drop_field()
db_drop_index()
db_drop_primary_key()
db_drop_table()
db_drop_unique_key()
db_encode_blob()
db_error/6">db_error()
db_escape_string()
db_fetch_array()
db_fetch_object()
db_field_set_default()
db_field_set_no_default()
db_last_insert_id()
db_lock_table()
db_query()
db_query_range()
db_query_temporary()
db_rename_table()
db_result()
db_status_report()
db_table_exists()
db_type_map()
db_unlock_tables()
db_version()
Все функции выглядят однотипно, различаясь только названием.
global $db_type;
$args = func_get_args();
return call_user_func_array("имя_функции_".$db_type, $args);
}
Несомненно, большой объем правок кода ядра может отпугнуть желающих использовать это. Есть еще один способ, требующий меньшего объема кода. Основная проблема в том, что в PHP обычно нет возможности удаления или переопределения (перегрузки) функций. Правда, одно из расширений PHP – Runkit позволяет сделать это. Если runkit подключен, то достаточно сделать небольшую вставку в код функции db_set_active(). Находим строчки:
$handler = "./includes/database.$db_type.inc";
if (is_file($handler)) {
include_once $handler;
}
else {
_db_error_page("The database type '". $db_type ."' is unsupported. Please use either
'mysql' or 'mysqli' for MySQL, or 'pgsql' for PostgreSQL databases.");
}
$db_conns[$name] = db_connect($connect_url);
}
и добавляем свой код:
$handler = "./includes/database.$db_type.inc";
if (is_file($handler)) {
$fnames = array(
'_db_create_field_sql', '_db_create_key_sql', '_db_create_keys_sql',
'_db_process_field', '_db_process_field', '_db_query',
'db_add_field', 'db_add_index', 'db_add_primary_key',
'db_add_unique_key', 'db_affected_rows', 'db_change_field',
'db_column_exists', 'db_connect', 'db_create_table_sql',
'db_decode_blob', 'db_distinct_field', 'db_drop_field',
'db_drop_index', 'db_drop_primary_key', 'db_drop_table',
'db_drop_unique_key', 'db_encode_blob', 'db_error',
'db_escape_string', 'db_fetch_array', 'db_fetch_object',
'db_field_set_default', 'db_field_set_no_default', 'db_last_insert_id',
'db_lock_table', 'db_query', 'db_query_range', 'db_query_temporary',
'db_rename_table', 'db_result', 'db_status_report',
'db_table_exists', 'db_type_map', 'db_unlock_tables', 'db_version',
);
foreach ($fnames as $fname) {
@runkit_function_remove($fname);
}
include $handler;
}
else {
_db_error_page("The database type '". $db_type ."' is unsupported. Please use either
'mysql' or 'mysqli' for MySQL, or 'pgsql' for PostgreSQL databases.");
}
$db_conns[$name] = db_connect($connect_url);
}
В этом случае определения всех функций баз данных будут удалены, а при подключении файла другого типа БД определены заново.
Источники:
[ru-api=db_set_active]
[man=runkit_function_remove]
2 соединения с БД
Переключение между базами
Статья в блоге автора
Комментарии
В закладки на память;) Пригодится.
Какая прелесть Просто и... Ну сами все видите...
В закладки. Однозначно.
Спасибо, полезно
хотя пока не знаю зачем
Я тоже пока не представляю зачем это нужно.
Есть идеи как это можно использовать?
Разные базы дают возможность получить разные сайты на одном коде.
Напоминает мультисайтовость.
В принципе так можно реализовать мультиязычность без всяких хистростей с отдельным переводом меню, блоков и прочего. Одна база для каждого языка. Это может быть удобно. Статистика будет учитывать язык, блоки можно по разному расставить. Да и база не содержит лишних данных по другим языкам, но я думаю, что выиграш от этого не большой (разве что на сайте где языков десятки).
Странно, что сначала появляется инструмент, а затем придумываем, что им можно делать...
Причина должна идти сначала. А потом следствие.
Простейший пример: несколько сайтов с общей таблицей пользователей, юзеры используют один и тот же логин и пароль для доступа к разным сайтам. (Т.е. OpenID-философия в очень-очень грубом приближении.)
Вариант использования данной схемы:
Есть 2-е и более баз данных. Основная master остальные реплицирующие.
1. Для распределения нагрузки всех анонимов (запросы на чтение) направляем на реплицирующие базы, а зарегистрированных на основную с возможностью писать в базу (материалы и комменты).
2. Распределение анонимов по реплицирующим базам, быстрая доставка контента в зависимости от географического положения юзера (соответствующий модуль белым домом разработан и отдан сообществу, нечто на подобии CDN - сеть доставки (и дистрибуции) контента англ. Content Delivery Network или Content Distribution Network, CDN).
Как я понимаю код обращения к базе нужно не в нодах и блоках писать, а использовать свой класс, 7 особенно это хорошо понимает.
Какие будут замечания по схеме?
Красота! Делал такое извращение не на Друпале, было три сайта с разными БД, на одном хосте, новостной блок должен был обновляться одновременно. Помучался с подключениями, а тут ... красота!
eagle,
У вас наверное был так называемый "мультисайтинг" - общий код для разных сайтов, а базы разные, но для каждого сайта создается отдельный файл с настройками для доступа к базе данных и может быть свой набор модулей и тем.
А то, что предлагает direqtor, позволяет иметь доступ к нескольким базам из одной, но одинаковой для всех базы кода PHP. Модули и темы будут одинаковые.
Хотя!
Можно сделать общий набор модулей, а в каждой базе будут использоваться только нужные (не все).
Короче, я так понимаю, что этот способ - очень простая и элегантная альтернатива общеизвестному методы построения мультисайтинга.
Ура!
Ещё есть варианты?
Ну, не я предложил. Решение описано в англоязычной документации. Кроме того и на d.ru имеется (см. источники). Факт в том, что здесь многие решения появляются как ответ на чей-то вопрос и часто теряются в массе ненужной информации.
Ага, mysqli бд на файлах, разграничение нагрузки, вот что этим можно достигнуть.
Мне это полезно с точки зрения безопасности. В отдельную базу на отдельном сервере я собираюсь вынести данные о пользовательских денежных счетах
Ну, а я пожалуй на новом проекте блок курсов валют буду дергать со старого. Там все отлично работает, а в друпале подходящего мне модуля пока нету.
А я тоже использую эту возможность. У меня сайт для универа. Там будет использоваться информация (данные о студентах), берущаяся из другой базы, с другого сервака. Только проблема в том, что сайт на mysql, а база универа на mssql. Подскажите, как ядро хакать надо для этого?
Может сегодня вечером покодю, пока занят. В любом случае заметку надо дополнить, но вот вроде бы возможности подключения к mssql в Друпал нет.
очень пригодиться) direqtor`у спасибо.
Спасибо, на заметку.
Добавил в заметку про хак для работы с разными типами БД.
Хак получился монументальным!
Очень полезная информация!
Переключения БД при помощи db_set_active() нужны, например, если нам нужна интеграция не-друпаловским сайтом или скриптом.
И насколько я знаю, нельзя задать разные префиксы таблиц для разных соединений с БД.
Добавлю: классический drupal-мультисайтинг c разделением таблиц делается путем настройки settings.php.
Пример1. Допустим, у нас на одном виртуальном хосте два сайта - site1.ru и site2.ru. Эти два независимых сайта должны иметь общую базу пользователей.
Создаем следующую структуру директорий:
Подробное пояснение, как называть директории с файлами настроек, есть в начале default/settings.php.
В site1.ru/settings.php пишем:
<?php
$db_url = 'mysql://root@localhost/db'; # общая БД
$db_prefix = array(
'default' => 'site1_', # разный префикс таблиц
'users' => 'shared_', # префикс общей таблицы (shared_users)
?>
В site2.ru/settings.php пишем:
<?php
$db_url = 'mysql://root@localhost/db'; # общая БД
$db_prefix = array(
'default' => 'site2_', # разный префикс таблиц
'users' => 'shared_', # префикс общей таблицы (shared_users)
?>
В этом примере сайты имеют общий набор пользователей, но каждый сайт может определять свои права и роли пользователей.
Пример 2. Допустим, у нас на одном виртуальном хосте два сайта - site.ru и pda.site.ru. Сайт pda.site.ru должен представлять ту же информацию, что и site.ru, но в виде, удобном для hand-held devices. Помимо предоставления пользователям других тем на выбор, необходимо использовать специальные модули (не нужные на основном сайте) и другую структуру меню.
Создаем следующую структуру директорий:
В site.ru/settings.php пишем:
<?php
$db_url = 'mysql://root@localhost/db'; # общая БД
$db_prefix = array(
'default' => 'site_', # общий префикс
?>
В pda.site.ru/settings.php пишем:
<?php
$db_url = 'mysql://root@localhost/db'; # общая БД
$db_prefix = array(
'default' => 'site_', # общий префикс
# отдельные таблицы для независимой настройки модулей
'variable' => 'pda_',
'system' => 'pda_',
'permission' => 'pda_', # нужна отдельная таблица прав доступа пользователей, т.к. включение/отключение модулей приводит к её изменениям
'menu' => 'pda_',
# независимый кэш
'cache' => 'pda_',
'cache_menu' => 'pda_',
?>
Сайты имеют полностью одинаковое содержимое, пользователей, но могут иметь различный набор настроенных модулей, меню, тем.
Ограничение стандартного мультисайтинга: все таблицы должны находится в одной БД.
Кажется был вариант для разных бд на одном серваке. Насколько помню выглядел так:
$db_prefix = array(
'default' => '', # без префикса в db1
'users' => 'db2.', # без префикса в db2
);
Ну, вообще, конечно жаль, что в Drupal нет внутренней поддержки работы на нескольких базах. Правда, тут возникает масса интересных коллизий. К примеру, как выполнять запросы объединяющие несколько таблиц. Если они находятся на одном сервере, еще куда ни шло, DB-layer справится. А если на разных?
Например, выборка всех материалов пользователя станет невозможной, если инфу о юзверях хранить в MySQL, а о нодах – в Postgre.
А никак. Имхо средств такого объединения меду СУБД разных типов нет. В MySQL можно объединять таблицы если они расположены в разных базах на одном сервере.
спасибо! в закладочки...
а что нужно вписать вместо локалхост, если база на другом хосте?
IP или доменное имя того хоста.
Фундаментально и всеобъемлюще!
спасибо, отличная статья.
уже сталкивался с этой проблемой
Можно еще проще, но этот метод не совсем правильный.
Можно использовать только, если разные базы расположены на одном сервере и доступны "пользователю mysql", который "подключается" к самой базе drupal. Я бы рекомендовал использовать его только в целях тестирования или для "одноразовых" манипуляций с базами.
Прямо в модуле пишем
$result = db_query($sql); //Дальше все как обычно
Полезная статейка. Возникал такой вопрос, оказывается есть гибкое решение. А мотивация для использования очень простая: масштабируемость. К примеру, сайт потипу одноклассники наверняка имеет огромную базу данных пользователей, таблицу пользователей можно держать на одном сервере, а их аккаунты с постами на нескольких десятках серверов. На протяжении нескольких месяцев можно собрать статистику активности пользователя, пользователь из Владивостока появляется на сайте в ночное (по Москве) время, а житель Рязани в дневное время, далее ДБ балансер (какой нибудь автоматический экшн) может автоматически перекинуть аккаунты на разные сервера, для того чтобы сбалансировать нагрузку на сервера баз данных. Выигрыш получается двойной: оптимальнее используются вычислительные мощности процессоров, и с другой стороны работа с относительно маленькими пейдж файлами базы данных значительно ускоряет работу SQL сервера.
Интересно, в дополнение к ДБ балансеру, есть ли решение для WEB балансера, для полноценного масштабирования Веб Аппликации? По типу того же балансера одноклассников, когда логин делается по одному адресу, после чего балансер редиректом отправляет запрос на менее загруженный Веб Сервер. Это чуть сложнее чем ДБ балансер, больше ньюансов, нужно деликатно разделить таблицы сессий персонально для каждого веб сервера, таблицу пользователей держать общую, а таблицу постов распределить между разными SQL серверами. В принципе можно воспользоваться все той же статистикой, и не делать динамический Веб балансер, а прописать в аккаунте адрес того Веб Сервера, который будет постоянно принимать редирект для построения контента страничек.
Жаль что MySQL не поддерживает linked tables, это решало бы сразу две проблемы - нет необходимости в драйверах для Друпал, и возможность работать как бы с одной базой, без переключения, с поддержкой join и всего остального. Правда возникали бы проблемы из-за разницы в диалектах SQL. Хранимые процедуры тоже не мешало бы иметь, с прекомпиляцией...
одна интересная статейка: http://www.razgonka.ru/info/39
отличная статья, спасибо!
Можно поинтересоваться: - А смысл этого действия?
Файл database.mysql-common.inc подтягивают только database.mysql.inc и database.mysqli.inc.
Ага.. понял. Происходит разьединение mysql и mysqli.
Хм..
Спасибо за статью! Делаю интеграцию с Друпал - как раз помогла!
Очень ценная информация, спасибо.
Замечательные возможности. Уже некоторое время использую. Только никак не могу найти информацию как создавать таблицы во второй БД при установке модуля.
Использую D7. Имеется 2 БД. В одной ядреные таблицы и таблицы модулей с д.орг. Во второй - таблицы моих модулей. При установке модуля таблицы БД конечно же создаются в дефольтном БД, что меня (читай - заказчика) совершенно не устраивает.
Так же в проекте для контроля таблиц используется модуль schema который ни в какую не видит перенесенные во вторую БД в ручную таблицы моих модулей. Приходится лезть в настройки модуля schema и переключать соединение с БД. Это конечно мелочи, вполне юзабельно, но хотелось бы "все в одном флаконе".
Не подскажите хорошую пилу для этих дров?