将SQLite3迁移到MySQL的快速简便方法?

时间:2020-03-05 18:41:15  来源:igfitidea点击:

任何人都知道将SQLite3数据库迁移到MySQL的快速简便方法吗?

解决方案

回答

最近,我不得不从MySQL迁移到JavaDB,以进行我们团队正在研究的项目。我找到了由Apache编写的名为DdlUtils的Java库,这使此工作变得非常容易。它提供了一个API,可让我们执行以下操作:

  • 发现数据库的架构,并将其导出为XML文件。
  • 根据此架构修改数据库。
  • 假设记录具有相同的架构,则将记录从一个DB导入到另一个DB。

我们最终使用的工具并不是完全自动化的,但是效果很好。即使应用程序不是用Java编写的,也不用太费力地挑起一些小工具进行一次迁移。我认为我能够用不到150行的代码来完成我们的迁移。

回答

也许最简单快捷的方法是使用sqlite .dump命令,在这种情况下,创建示例数据库的转储。

sqlite3 sample.db .dump > dump.sql

然后,我们可以(理论上)使用root用户将其导入mysql数据库,在本例中为数据库服务器127.0.0.1上的测试数据库。

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

我在理论上说,因为语法之间存在一些差异。

在sqlite交易开始

BEGIN TRANSACTION;
...
COMMIT;

MySQL只使用

BEGIN;
...
COMMIT;

还有其他类似的问题(varchars和双引号回想起),但是找不到并替换没有解决的问题。

也许我们应该问为什么要迁移,如果性能/数据库大小是问题,也许要重新架构,再看看系统是否正在转向功能更强大的产品,这可能是规划数据未来的理想时机。

回答

每个人似乎都从几个粗俗的东西和perl表达式开始,我们有点得到了适用于特定数据集的信息,但是我们不知道它是否正确导入了数据。令我惊讶的是,没人能建立一个可以在两者之间转换的可靠库。

下面列出了我知道的两种文件格式在SQL语法上的所有差异:
以以下内容开头的行:

  • 开始交易
  • 犯罪
  • 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

这是一个非常基本的hacked perl脚本,适用于我的数据集,并检查我在网络上发现的其他perl脚本的更多这些条件。 Nu guarentees,它将为数据工作,但随时可以修改并在此处发布。

#! /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 = ;
            $sub = ;
            $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 \n";
            $line =~ s/\"/\\"/g;
            $line =~ s/\"/\'/g;
        }else{
            $line =~ s/\'\'/\\'/g;
        }
        $line =~ s/([^\'])\'t\'(.)/THIS_IS_TRUE/g;
        $line =~ s/THIS_IS_TRUE/1/g;
        $line =~ s/([^\'])\'f\'(.)/THIS_IS_FALSE/g;
        $line =~ s/THIS_IS_FALSE/0/g;
        $line =~ s/AUTOINCREMENT/AUTO_INCREMENT/g;
        print $line;
    }
}

回答

这很混乱,因为转储文件是特定于数据库供应商的。

如果我们使用的是Rails,则为此提供了一个很棒的插件。阅读:http://blog.heroku.com/archives/2007/11/23/yamldb_for_databaseindependent_data_dumps/

更新

当前维护的分叉:https://github.com/ludicast/yaml_db

回答

这是一个Python脚本,它是基于Shalmanese的答案以及Alex martelli在将Perl转换为Python时提供的一些帮助而构建的

我正在将其制作为社区Wiki,所以请随时进行编辑和重构,只要它不会破坏功能(感谢我们可以回滚),虽然很丑陋,但是可以

像这样使用(假设脚本名为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'(.)", "THIS_IS_TRUE", line)
    line = line.replace('THIS_IS_TRUE', '1')
    line = re.sub(r"([^'])'f'(.)", "THIS_IS_FALSE", 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,

回答

我使用数据加载器来迁移几乎所有数据,它可以帮助我将MSSQL转换为MYSQL,MS对MSSQL,mysql,csv加载器,foxpro的访问以及将MSSQL转换为MS Access的MYSQl,CSV,foxpro等。数据迁移工具

免费下载:http://www.dbload.com

回答

这个脚本是可以的,除了这种情况,我当然满足了:

INSERT INTO "requestcomparison_stopword" VALUES(149,'f');
INSERT INTO "requestcomparison_stopword" VALUES(420,'t');

该脚本应提供以下输出:

INSERT INTO requestcomparison_stopword VALUES(149,'f');
INSERT INTO requestcomparison_stopword VALUES(420,'t');

但是给出了输出:

INSERT INTO requestcomparison_stopword VALUES(1490;
INSERT INTO requestcomparison_stopword VALUES(4201;

在最后的0和1周围带有一些奇怪的非ascii字符。

当我注释了以下代码行(43-46)时,此问题不再显示,但出现了其他问题:

line = re.sub(r"([^'])'t'(.)", "THIS_IS_TRUE", line)
    line = line.replace('THIS_IS_TRUE', '1')
    line = re.sub(r"([^'])'f'(.)", "THIS_IS_FALSE", line)
    line = line.replace('THIS_IS_FALSE', '0')

This is just a special case, when we want to add a value being 'f' or 't' but I'm not really comfortable with regular expressions, I just wanted to spot this case to be corrected by someone.

Anyway thanks a lot for that handy script !!!

Answer

Ha... I wish I had found this first! My response was to this post... script to convert mysql dump sql file into format that can be imported into sqlite3 db

Combining the two would be exactly what I needed:

When the sqlite3 database is going to be used with ruby you may want to change:

tinyint([0-9]*)

,,这只有一半有效,因为即使我们将1和0插入标记为boolean的字段中,sqlite3也会将它们存储为1和0,因此我们必须执行以下操作:

sed 's/ tinyint(1*) / boolean/g ' |
sed 's/ tinyint([0|2-9]*) / integer /g' |

但是让sql文件查找所有布尔值会有所帮助。

回答

fallino在脚本中正确标识了错误的位置。我有解决方案。问题是以下几行:

Table.find(:all, :conditions => {:column => 1 }).each { |t| t.column = true }.each(&:save)
Table.find(:all, :conditions => {:column => 0 }).each { |t| t.column = false}.each(&:save)

re.sub调用中的替换模式(第二个参数)是一个"常规"字符串,因此,代替\ 1扩展到第一个regexp匹配,它扩展为原义0x01. 同样,\ 2扩展为0x02. 例如,包含以下内容的行:
,'t','f',
将被替换为:
<0x01> 10 <0x02>
(第一次替换将't'更改为<0x1> 1 <0x2>
第二次替换将<0x02>'f'更改为<0x1> 0 <0x1>)

解决方法是通过添加" r"前缀或者通过在现有字符串中转义\ 1和\ 2来更改替换字符串。由于正则表达式字符串的简单操作就是原始字符串的用途,因此使用以下方法解决此问题:

line = re.sub(r"([^'])'t'(.)", "THIS_IS_TRUE", line)
line = line.replace('THIS_IS_TRUE', '1')
line = re.sub(r"([^'])'f'(.)", "THIS_IS_FALSE", line)
line = line.replace('THIS_IS_FALSE', '0')

代码数量不匹配