Преобразование таблиц SQL Server в MySQL с использованием ZF2 и Doctrine2
Я разработал приложение для одного из моих клиентов. У него уже есть один. Поэтому мне нужно преобразовать его фактическую базу данных (SQL Server) в новую (MySQL).
Некоторые таблицы SQL Server имеют более 10.000.000 записей. Когда я начинаю начинать разработку этого конвертера, я начал с некоторых таблиц с несколькими записями, поэтому я нахожу все записи и сохраняю их в своей новой базе данных MySQL. Я покажу вам код для лучшего понимания (это только пример)
<?php
namespace Converter\Model;
class PostConverter extends AbstractConverter
{
public function convert()
{
// this is the default connection, it is a mysql database (new application)
$em = $this->getEntityManager();
// this return an alternative connection to the sqlserver database (actual application)
$emAlternative = $this->getEntityManagerAlternative();
// instance of Converter\Repository\Post
$repository = $emAlternative->getRepository('Converter\Entity\Post');
$posts = $repository->findAll();
foreach ($posts as $post)
$post = new Post();
$post->setTitle($object->getTitle());
$em->persist($post);
}
$em->flush();
}
}
Теперь предположим, что таблица Post имеет более 10.000.000 записей. Я не могу просто найти все и перебрать его. Я выйду из ОЗУ. Поэтому я сделал что-то вроде этого.
Класс репозитория:
<?php
namespace Converter\Repository;
class Posts extends \Doctrine\ORM\EntityRepository
{
public function findPosts($limit, $offset)
{
$qb = $this->createQueryBuilder('Post');
$qb->setMaxResults($limit);
$qb->setFirstResult($offset);
return $qb->getQuery->getResult();
}
}
Здесь я нахожу только несколько сообщений во время цикла while. Но это довольно медленно. Я не мог найти лучшего решения для повышения производительности
<?php
namespace Converter\Model;
class PostConverter extends AbstractConverter
{
public function convert()
{
$em = $this->getEntityManager();
$emAlternative = $this->getEntityManagerAlternative();
$repository = $emAlternative->getRepository('Converter\Entity\Post');
$limit = 1000;
while ($object = $repository->findPosts($limit, $offset) {
$post = new Post();
$post->setTitle($object->getTitle());
$em->persist($post);
$offset += $limit;
}
$em->flush();
}
}
Я никогда раньше не делал ничего подобного. Возможно, я ошибусь. Я буду очень признателен, если кто-то из вас может сказать мне правильный, поэтому я могу двигаться дальше.
Спасибо всем
ИЗМЕНИТЬ
Я не могу просто сбрасывать друг друга. То, что я разместил здесь, является всего лишь примером, в преобразовании мне приходится обрабатывать почти все данные перед вставкой в новую базу данных. Его фактическое применение было разработано в 2005 году. База данных даже не нормализована
Ответы
Ответ 1
В настоящее время я создаю систему хранения данных с аналогичными проблемами. Доктрина собственной документации правильно заявляет:
Инструмент ORM в основном не подходит для массовых вставок, обновлений или удаления. Каждая РСУБД имеет свой собственный, наиболее эффективный способ борьбы с такими операциями, и если приведенные ниже варианты недостаточны для ваших целей, мы рекомендуем вам использовать инструменты для вашей конкретной РСУБД для этих массовых операций.
Вот как я бы справился с этим:
- Создайте свою пустую базу данных MySQL с помощью инструментов Doctrine.
- Составьте список всех индексов и первичных ключей в базе данных MySQL и оставьте их. Я бы это сделал. Это приведет к удалению накладных расходов постоянных обновлений индекса до завершения переноса данных.
- Напишите script, чтобы скопировать данные. Пронумеруйте данные SQL Server пакетами в несколько тысяч и вставьте в MySQL.
- Использовать PDO или собственные библиотеки. Нет доктрины или построителей запросов. Напишите запросы вручную.
- Откройте одно соединение с вашим SQL Server и одно соединение с MySQL. Держите их открытыми на время script.
- Запрос в партиях с использованием
LIMIT
и первичного ключa > последнего идентификатора. Запрос с использованием OFFSET
часто медленнее.
- Подготовьте инструкции за пределами циклов для оптимизации обработки запросов.
- Оберните каждую партию вставок в одну транзакцию, чтобы уменьшить транзакционные издержки.
- "Вручную" проверьте ссылочную целостность, если необходимо. В ваших таблицах еще нет первичных ключей.
- Если у вас много таблиц, отделите свой код на объекты или функции, чтобы локальные переменные могли быть удалены из памяти, и отладку будет легче отлаживать.
- Вы можете периодически звонить
gc_collect_cycles()
. Если ваш код разбит на объекты, это простой способ держать память под контролем.
- Восстановить индексы базы данных и первичные ключи. Бонусные баллы, если они были написаны с самого начала. Следите за любыми первичными ключами, которые не могут быть созданы из-за ошибок с дублирующимися данными.
- Тестирование и тестирование перед открытием новой базы данных MySQL для использования в производстве. Вы не хотите писать еще один script для исправления переноса данных позже.
Ответ 2
Если схема в исходной базе данных (MSSQL) и целевой базе данных (MySQL) является точной или похожей, я бы экспортировал записи из одной базы данных, а затем импортировал их в другую, используя только инструменты базы данных. Пример:
Вы можете использовать оболочку script, чтобы склеить все это вместе и автоматизировать процесс.
Этот экспорт/импорт будет достаточно быстрым, как это происходит на уровне базы данных. Это также самый быстрый, который вы, возможно, получите.
Перемещение всей базы данных с уровня модели будет медленнее, по определению: вы собираетесь создать модельный объект для каждой строки. Тем не менее, использование модельного слоя является хорошим подходом, когда исходная и целевая схемы расходятся, потому что тогда вы можете использовать программную модель для адаптации одной схемы к другой.
В вашем конкретном примере вы можете увидеть некоторое улучшение производительности, если вы unset($object)
в нижней части вашего цикла while
, хотя я сомневаюсь, что память является узким местом. (I/O есть.)
Ответ 3
Я пробовал этот подход до и из своего опыта, всегда быстрее использовать собственные средства для сброса и восстановления данных СУБД, а не записи процессов через такую структуру.
Я бы предложил использовать утилиту, такую как bcp
(https://msdn.microsoft.com/en-us/library/aa337544.aspx), чтобы выгрузить данные из SQL Server, а затем использовать MySQL LOAD DATA
(http://dev.mysql.com/doc/refman/5.7/en/load-data.html) или mysqlimport
для переноса данных в MySQL.
Если вам нужно переструктурировать данные перед загрузкой в MySQL, вы можете сделать это, настроив новую структуру данных в MySQL и затем манипулируя импортируемыми данными с помощью утилиты, которая может искать и заменять как sed
.
Ответ 4
Предпочитаете использовать демпинг данных на основе СУБД и восстанавливать инструменты, а не обрабатывать записи через как это. Экспорт базы данных в формате CSV и импорт в mysql.