Быстрый простой способ перенести SQLite3 на MySQL? - PullRequest
211 голосов
/ 20 августа 2008

Кто-нибудь знает быстрый и простой способ переноса базы данных SQLite3 в MySQL?

Ответы [ 26 ]

104 голосов
/ 18 сентября 2008

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

Вот список ВСЕХ различий в синтаксисе SQL, о которых я знаю между двумя форматами файлов: Строки, начинающиеся с:

  • НАЧАЛО СДЕЛКИ
  • COMMIT
  • sqlite_sequence
  • СОЗДАТЬ УНИКАЛЬНЫЙ ИНДЕКС

не используются в MySQL

  • SQLlite использует CREATE TABLE/INSERT INTO "table_name", а MySQL использует CREATE TABLE/INSERT INTO table_name
  • MySQL не использует кавычки внутри определения схемы
  • MySQL использует одинарные кавычки для строк внутри предложений INSERT INTO
  • SQLlite и MySQL имеют разные способы экранирования строк внутри INSERT INTO предложений
  • SQLlite использует 't' и 'f' для логических значений, MySQL использует 1 и 0 (простое регулярное выражение для этого может потерпеть неудачу, если у вас есть строка типа «Я делаю, вы не делаете» внутри ваш INSERT INTO)
  • SQLLite использует AUTOINCREMENT, MySQL использует AUTO_INCREMENT

Вот очень простой взломанный Perl-скрипт, который работает для my набора данных и проверяет многие из этих условий, какие другие Perl-скрипты я обнаружил в Интернете. Мы гарантируем, что это будет работать для ваших данных, но вы можете изменить их и опубликовать здесь.

#! /usr/bin/perl

while ($line = <>){
    if (($line !~  /BEGIN TRANSACTION/) && ($line !~ /COMMIT/) && ($line !~ /sqlite_sequence/) && ($line !~ /CREATE UNIQUE INDEX/)){

        if ($line =~ /CREATE TABLE \"([a-z_]*)\"(.*)/){
            $name = $1;
            $sub = $2;
            $sub =~ s/\"//g;
            $line = "DROP TABLE IF EXISTS $name;\nCREATE TABLE IF NOT EXISTS $name$sub\n";
        }
        elsif ($line =~ /INSERT INTO \"([a-z_]*)\"(.*)/){
            $line = "INSERT INTO $1$2\n";
            $line =~ s/\"/\\\"/g;
            $line =~ s/\"/\'/g;
        }else{
            $line =~ s/\'\'/\\\'/g;
        }
        $line =~ s/([^\\'])\'t\'(.)/$1THIS_IS_TRUE$2/g;
        $line =~ s/THIS_IS_TRUE/1/g;
        $line =~ s/([^\\'])\'f\'(.)/$1THIS_IS_FALSE$2/g;
        $line =~ s/THIS_IS_FALSE/0/g;
        $line =~ s/AUTOINCREMENT/AUTO_INCREMENT/g;
        print $line;
    }
}
57 голосов
/ 14 мая 2011

Вот список конвертеров (не обновляется с 2011 года):

<ч /> Альтернативный метод, который будет работать хорошо, но редко упоминается: используйте класс ORM, который абстрагирует вас от конкретных различий в базе данных. например Вы получаете их в PHP ( RedBean ), Python (слой ORM Джанго, Storm , SqlAlchemy ), Ruby on Rails ( ActiveRecord ) , Какао ( CoreData )

т.е. Вы могли бы сделать это:

  1. Загрузка данных из исходной базы данных с использованием класса ORM.
  2. Хранить данные в памяти или сериализовать на диск.
  3. Сохранение данных в базе данных назначения с использованием класса ORM.
47 голосов
/ 01 июля 2009

Вот скрипт на python, построенный на ответе Шалманеса и некоторой помощи Алекса Мартелли в Перевод Perl в Python

Я делаю это вики-сообществом, поэтому, пожалуйста, не стесняйтесь редактировать и рефакторинг, если он не нарушает функциональность (к счастью, мы можем просто откатиться) - Это довольно уродливо, но работает

используйте так (при условии, что скрипт называется dump_for_mysql.py:

sqlite3 sample.db .dump | python dump_for_mysql.py > dump.sql

Который вы можете затем импортировать в MySQL

примечание - вам нужно добавить ограничения внешнего ключа вручную, поскольку sqlite фактически не поддерживает их

вот скрипт:

#!/usr/bin/env python

import re
import fileinput

def this_line_is_useless(line):
    useless_es = [
        'BEGIN TRANSACTION',
        'COMMIT',
        'sqlite_sequence',
        'CREATE UNIQUE INDEX',
        'PRAGMA foreign_keys=OFF',
    ]
    for useless in useless_es:
        if re.search(useless, line):
            return True

def has_primary_key(line):
    return bool(re.search(r'PRIMARY KEY', line))

searching_for_end = False
for line in fileinput.input():
    if this_line_is_useless(line):
        continue

    # this line was necessary because '');
    # would be converted to \'); which isn't appropriate
    if re.match(r".*, ''\);", line):
        line = re.sub(r"''\);", r'``);', line)

    if re.match(r'^CREATE TABLE.*', line):
        searching_for_end = True

    m = re.search('CREATE TABLE "?(\w*)"?(.*)', line)
    if m:
        name, sub = m.groups()
        line = "DROP TABLE IF EXISTS %(name)s;\nCREATE TABLE IF NOT EXISTS `%(name)s`%(sub)s\n"
        line = line % dict(name=name, sub=sub)
    else:
        m = re.search('INSERT INTO "(\w*)"(.*)', line)
        if m:
            line = 'INSERT INTO %s%s\n' % m.groups()
            line = line.replace('"', r'\"')
            line = line.replace('"', "'")
    line = re.sub(r"([^'])'t'(.)", "\1THIS_IS_TRUE\2", line)
    line = line.replace('THIS_IS_TRUE', '1')
    line = re.sub(r"([^'])'f'(.)", "\1THIS_IS_FALSE\2", line)
    line = line.replace('THIS_IS_FALSE', '0')

    # Add auto_increment if it is not there since sqlite auto_increments ALL
    # primary keys
    if searching_for_end:
        if re.search(r"integer(?:\s+\w+)*\s*PRIMARY KEY(?:\s+\w+)*\s*,", line):
            line = line.replace("PRIMARY KEY", "PRIMARY KEY AUTO_INCREMENT")
        # replace " and ' with ` because mysql doesn't like quotes in CREATE commands 
        if line.find('DEFAULT') == -1:
            line = line.replace(r'"', r'`').replace(r"'", r'`')
        else:
            parts = line.split('DEFAULT')
            parts[0] = parts[0].replace(r'"', r'`').replace(r"'", r'`')
            line = 'DEFAULT'.join(parts)

    # And now we convert it back (see above)
    if re.match(r".*, ``\);", line):
        line = re.sub(r'``\);', r"'');", line)

    if searching_for_end and re.match(r'.*\);', line):
        searching_for_end = False

    if re.match(r"CREATE INDEX", line):
        line = re.sub('"', '`', line)

    if re.match(r"AUTOINCREMENT", line):
        line = re.sub("AUTOINCREMENT", "AUTO_INCREMENT", line)

    print line,
22 голосов
/ 12 ноября 2008

Это грязно, потому что файлы дампа зависят от поставщика базы данных.

Если вы используете Rails, для этого существует отличный плагин. Читать: http://blog.heroku.com/archives/2007/11/23/yamldb_for_databaseindependent_data_dumps/

Обновление

В настоящее время поддерживается вилка: https://github.com/ludicast/yaml_db

9 голосов
/ 15 мая 2017

MySQL Workbench (лицензия GPL) очень легко мигрирует из SQLite с помощью мастера Database Migration . Устанавливается на Windows, Ubuntu, RHEL, Fedora и OS X .

8 голосов
/ 19 января 2011

Удивлен, что никто не упомянул об этом, но на самом деле есть инструмент для этого. Это в Perl, SQL: Переводчик: http://sqlfairy.sourceforge.net/

Преобразует большинство табличных данных в любой форме (различные форматы SQL, электронные таблицы Excel) и даже создает диаграммы вашей схемы SQL.

8 голосов
/ 13 октября 2011
aptitude install sqlfairy libdbd-sqlite3-perl

sqlt -f DBI --dsn dbi:SQLite:../.open-tran/ten-sq.db -t MySQL --add-drop-table > mysql-ten-sq.sql
sqlt -f DBI --dsn dbi:SQLite:../.open-tran/ten-sq.db -t Dumper --use-same-auth > sqlite2mysql-dumper.pl
chmod +x sqlite2mysql-dumper.pl
./sqlite2mysql-dumper.pl --help
./sqlite2mysql-dumper.pl --add-truncate --mysql-loadfile > mysql-dump.sql
sed -e 's/LOAD DATA INFILE/LOAD DATA LOCAL INFILE/' -i mysql-dump.sql

echo 'drop database `ten-sq`' | mysql -p -u root
echo 'create database `ten-sq` charset utf8' | mysql -p -u root
mysql -p -u root -D ten-sq < mysql-ten-sq.sql
mysql -p -u root -D ten-sq < mysql-dump.sql
7 голосов
/ 13 ноября 2012

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

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

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

Я выложу скрипт ниже. Но, во-первых, вот инструкция по конвертации ...

Я запустил скрипт на OS X 10.7.5 Lion. Python работал из коробки.

Чтобы сгенерировать входной файл MySQL из существующей базы данных SQLite3, запустите сценарий для своих файлов следующим образом:

Snips$ sqlite3 original_database.sqlite3 .dump | python ~/scripts/dump_for_mysql.py > dumped_data.sql

Затем я скопировал полученный файл dumped_sql.sql в коробку Linux с Ubuntu 10.04.4 LTS, где должна была находиться моя база данных MySQL.

Другая проблема, с которой я столкнулся при импорте файла MySQL, заключалась в том, что некоторые символы Unicode UTF-8 (в частности, одинарные кавычки) импортировались некорректно, поэтому мне пришлось добавить переключатель в команду, чтобы указать UTF-8.

Получившаяся команда для ввода данных в новую пустующую базу данных MySQL выглядит следующим образом:

Snips$ mysql -p -u root -h 127.0.0.1 test_import --default-character-set=utf8 < dumped_data.sql

Пусть готовит, и так и должно быть! Не забывайте тщательно проверять ваши данные до и после.

Итак, по просьбе ОП, это быстро и легко, когда вы знаете, как! : -)

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

Удачи!

UPDATE

С момента включения этого переключателя я заметил проблему, которую раньше не замечал. В моем приложении на Rails мои текстовые поля определены как «строка», и это распространяется на схему базы данных. Описанный здесь процесс приводит к тому, что они определяются как VARCHAR (255) в базе данных MySQL. Это накладывает ограничение на 255 символов для этих размеров полей - и все, что за этим было молча усечено во время импорта. Я считаю, что для поддержки длины текста, превышающей 255, в схеме MySQL нужно будет использовать 'TEXT', а не VARCHAR (255). Определенный здесь процесс не включает это преобразование.


Вот объединенный и переработанный скрипт Python, который работал для моих данных:

#!/usr/bin/env python

import re
import fileinput

def this_line_is_useless(line):
    useless_es = [
        'BEGIN TRANSACTION',
        'COMMIT',
        'sqlite_sequence',
        'CREATE UNIQUE INDEX',        
        'PRAGMA foreign_keys=OFF'
        ]
    for useless in useless_es:
        if re.search(useless, line):
            return True

def has_primary_key(line):
    return bool(re.search(r'PRIMARY KEY', line))

searching_for_end = False
for line in fileinput.input():
    if this_line_is_useless(line): continue

    # this line was necessary because ''); was getting
    # converted (inappropriately) to \');
    if re.match(r".*, ''\);", line):
        line = re.sub(r"''\);", r'``);', line)

    if re.match(r'^CREATE TABLE.*', line):
        searching_for_end = True

    m = re.search('CREATE TABLE "?([A-Za-z_]*)"?(.*)', line)
    if m:
        name, sub = m.groups()
        line = "DROP TABLE IF EXISTS %(name)s;\nCREATE TABLE IF NOT EXISTS `%(name)s`%(sub)s\n"
        line = line % dict(name=name, sub=sub)
        line = line.replace('AUTOINCREMENT','AUTO_INCREMENT')
        line = line.replace('UNIQUE','')
        line = line.replace('"','')
    else:
        m = re.search('INSERT INTO "([A-Za-z_]*)"(.*)', line)
        if m:
            line = 'INSERT INTO %s%s\n' % m.groups()
            line = line.replace('"', r'\"')
            line = line.replace('"', "'")
            line = re.sub(r"(?<!')'t'(?=.)", r"1", line)
            line = re.sub(r"(?<!')'f'(?=.)", r"0", line)

    # Add auto_increment if it's not there since sqlite auto_increments ALL
    # primary keys
    if searching_for_end:
        if re.search(r"integer(?:\s+\w+)*\s*PRIMARY KEY(?:\s+\w+)*\s*,", line):
            line = line.replace("PRIMARY KEY", "PRIMARY KEY AUTO_INCREMENT")
        # replace " and ' with ` because mysql doesn't like quotes in CREATE commands

    # And now we convert it back (see above)
    if re.match(r".*, ``\);", line):
        line = re.sub(r'``\);', r"'');", line)

    if searching_for_end and re.match(r'.*\);', line):
        searching_for_end = False

    if re.match(r"CREATE INDEX", line):
        line = re.sub('"', '`', line)

    print line,
6 голосов
/ 25 августа 2008

Вероятно, самый простой способ - использовать команду sqlite .dump, в этом случае создать дамп базы данных примера.

sqlite3 sample.db .dump > dump.sql

Затем (теоретически) вы можете импортировать это в базу данных mysql, в данном случае тестовую базу данных на сервере базы данных 127.0.0.1, используя пользователя root.

mysql -p -u root -h 127.0.0.1 test < dump.sql

Я говорю теоретически, поскольку есть несколько различий между грамматиками.

В sqlite начинаются транзакции

BEGIN TRANSACTION;
...
COMMIT;

MySQL использует только

BEGIN;
...
COMMIT;

Существуют и другие подобные проблемы (всплывают varchars и двойные кавычки), но ничего найти и заменить не удалось.

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

6 голосов
/ 12 июля 2017

Если вы используете Python / Django, это довольно просто:

создать две базы данных в settings.py (как здесь https://docs.djangoproject.com/en/1.11/topics/db/multi-db/)

тогда просто сделай так:

objlist = ModelObject.objects.using('sqlite').all()

for obj in objlist:
    obj.save(using='mysql')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...