MySQLからSQLiteへデータ移行
さくらVPSサーバーからレンタルサーバー(ライトプラン)へ引っ越す必要があったのでMySQL(innodb)からSQLiteにデータを移行した時のメモ
MySQLの主なデータ型
数値型
| データ型 | 値の範囲 | |
|---|---|---|
| TYNYINT | 整数型(1バイト) | -128~127 0~255 |
| SMALLINT | 整数型(2バイト) | -32768~32767 0~65536 |
| MEDIUMINT | 整数型(3バイト) | -8388608~8388607 0~16777215 |
| INT | 整数型(4バイト) | -2147483648~2147483647 0~4294967295 |
| BIGINT | 整数型(8バイト) | -9223372036854775808~9223372036854775807 0~18446744073709551615 |
| FLOAT | 単精度浮動小数点型(4バイト) | -3.402823466E+38~-1.175494351E-38、0 1.175494351E-38~3.402823466E+38 |
| DOUBLE | 倍精度浮動小数点型(8バイト) | -1.7976931348623157E+308~-2.2250738585072014E-308、0 2.2250738585072014E-308~1.7976931348623157E+308 |
| DECIMAL(M,D) | 固定小数点型 | M:1~65 D:0~30 |
文字列(バイナリ)型
| データ型 | 値の範囲 | |
|---|---|---|
| CHAR(M) | 固定長文字列型 | M:0~255文字 |
| VARCHAR(M) | 可変長文字列型 | M:0~65535文字 |
| TYNYTEXT | テキスト型 | 0~255文字 |
| TEXT | テキスト型 | 0~65535文字 |
| MEDIUMTEXT | テキスト型 | 0~16777215文字 |
| LONGTEXT | テキスト型 | 0~4294967295文字 |
| TYNYBLOB | バイナリ型 | 0~255バイト |
| BLOB | バイナリ型 | 0~65535バイト |
| MEDIUMBLOB | バイナリ型 | 0~16777215バイト |
| LONGBLOB | バイナリ型 | 0~4294967295バイト |
日付(時刻)型
| データ型 | 基本フォーマット 「ゼロ」値 |
|
|---|---|---|
| DATE | 日付型(3バイト) | 'YYYY-MM-DD' '0000-00-00' |
| DATETIME | 日付時刻型(8バイト) | 'YYYY-MM-DD HH:MM:SS' '0000-00-00 00:00:00' |
| TIMESTAMP | タイムスタンプ型(4バイト) | 'YYYY-MM-DD HH:MM:SS' '0000-00-00 00:00:00' |
| TIME | 時刻型(3バイト) | 'HH:MM:SS' '00:00:00' |
| YEAR | 年型(1バイト) | 'YYYY' 0000 |
SQLite3のデータ型
| データ型 | 詳細 |
|---|---|
| TEXT | テキスト。エンコーディング(UTF-8、UTF-16BE、またはUTF-16LE)を使用して格納 |
| INTEGER | 符号付整数。値の大きさに応じて1、2、3、4、6、または8バイトに格納 |
| REAL | 浮動小数点数。8バイトで格納 |
| BLOB | 入力されたとおりに格納 |
MySQLのデータをmysqldumpでバックアップ
mysqldumpの基本の構文は、次のようになります。
mysqldump -u ユーザ名 -p データベース名 > ダンプファイル名
# mysqldump --opt --default-character-set=utf8 --skip-lock-tables --single-transaction --hex-blob -u dbuser -p webadmin > dump.sql
# mysqldump --defaults-file=${CONF_FILE} --opt --default-character-set=utf8 --skip-lock-tables --single-transaction --hex-blob -u ${DB_USER} ${DB_NAME} > ${BACKUP_DIR}/${BACKUP_FILE_NAME}.sql
# mysqldump --opt --default-character-set=utf8 --skip-lock-tables --single-transaction --hex-blob -u ユーザ -p データベース名 > dump.sql
--default-character-set
デフォルト文字セットを指定
--skip-lock-tables
lock-tablesオプションを無効にする
--single-transaction
BEGINステートメントを発行しDBをロックせずにダンプする
--hex-blob
16進表記を使用してバイナリカラムをダンプする
ダンプファイルをSQLite用に加工
必要なものだけ読み込む
//コメント
if (preg_match('#^--#', $value, $arrMatch) === 1) {
continue;
}
//コメント
if (preg_match('#^/\*[\s\S]*\*/;$#', $value, $arrMatch) === 1) {
continue;
}
//LOCK TABLES
if (preg_match('#^LOCK TABLES [\s\S]*;$#i', $value, $arrMatch) === 1) {
continue;
}
//UNLOCK TABLES
if (preg_match('#^UNLOCK TABLES;$#i', $value, $arrMatch) === 1) {
continue;
}
//SET
if (preg_match('#^SET [\s\S]*;$#i', $value, $arrMatch) === 1) {
continue;
}
//START TRANSACTION;
if (preg_match('#^START TRANSACTION;$#i', $value, $arrMatch) === 1) {
continue;
}
//COMMIT;
if (preg_match('#^COMMIT;$#i', $value, $arrMatch) === 1) {
continue;
}
SQLiteで使用できないものは削除
$row = preg_replace('#\) ENGINE=[\s\S]*;$#', ');', $row);
$row = preg_replace('# COMMENT \'[\s\S]*\'#', ' ', $row);
$row = preg_replace('#\([0-9,\s]+\)#', '', $row);
$row = preg_replace('#COLLATE [a-zA-z0-9_]+#', ' ', $row);
$row = preg_replace('#CHARACTER SET [a-zA-z0-9]+#', ' ', $row);
$row = str_ireplace(' UNSIGNED', ' ', $row);
$row = str_ireplace(' ZEROFILL', ' ', $row);
データ型変換
$row = str_ireplace(array(' TINYINT',' SMALLINT',' MEDIUMINT',' BIGINT'), ' INTEGER', $row);
$row = str_ireplace(array(' BOOLEAN'), ' INTEGER', $row);
$row = str_ireplace(' INT ', ' INTEGER', $row);
$row = str_ireplace(array(' FLOAT',' DOUBLE'), ' REAL', $row);
$row = str_ireplace(array(' NUMERIC', ' DECIMAL'), ' REAL', $row);
$row = str_ireplace(array(' VARCHAR',' CHAR'), ' TEXT', $row);
$row = str_ireplace(array(' TINYTEXT',' MEDIUMTEXT',' LONGTEXT'), ' TEXT', $row);
$row = str_ireplace(array(' DATETIME',' TIMESTAMP',' DATE',' TIME',' YEAR'), ' TEXT', $row);
$row = str_ireplace(array(' TINYBLOB',' MEDIUMBLOB',' LONGBLOB', ' BINARY',' VARBINARY'), ' BLOB', $row);
$row = str_ireplace(array(' GEOMETRY'), ' BLOB', $row);
文字列のエスケープ処理
//シングルクォーテーション
$value = str_replace("\'", "''", $value);
//ダブルクォーテーション
$value = str_replace('\"', '"', $value);
//改行
$value = str_replace('\n', "\n", $value);
PDOを使ってSQLiteにインポート
//オプションの設定(エラーレポート => 例外を投げる)
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
];
//インスタンスを作成
$clsDb = new PDO('sqlite:sample.db', '', '', $options);
//トランザクションを開始する
$clsDb->beginTransaction();
//SQL ステートメントを実行し、結果セットを PDOStatement オブジェクトとして返す
$clsDb->query($sql)
//SQL ステートメントを実行し、作用した行数を返す;
$clsDb->exec($sql);
//トランザクションをコミットする
$clsDb->commit();
//トランザクションをロールバックする
$clsDb->rollBack();
//接続を閉じる
$clsDb = null;
使い方
サンプルソースのファイル名設定・データベース名設定を環境に合わせて修正してください。
$filename = 'dump.sql';
$filename = 'sample.db';
サンプルソース
import.php
//ファイル名を設定
$filename = 'dump.sql';
//データベース名を設定
$dbname = 'sample.db';
try {
//ダンプファイル読込み
$data = readDumpFile($filename);
//SQL加工
$arrSQL = array();
$arrData = explode(";\n", $data);
foreach ($arrData as $key=>$row) {
if (strlen($row) === 0) {
continue;
}
$row .= ';';
if (preg_match('#^CREATE TABLE (.*?) [\s\S]*;$#i', $row, $arrMatch) === 1) {
//CREATE TABLE
$table = trim($arrMatch[1]);
createTable($table, $row, $arrSQL);
} else if (preg_match('#^DROP TABLE IF EXISTS (.*?);$#i', $row, $arrMatch) === 1) {
//DROP TABLE
$table = trim($arrMatch[1]);
$arrSQL['drop'][$table][] = $row;
} else if (preg_match('#^INSERT INTO (.*?) [\s\S]*\);$#i', $row, $arrMatch) === 1) {
//INSERT INTO
$table = trim($arrMatch[1]);
//16進表記(--hex-blob)
$row = preg_replace('#(\(|, ?)0x([a-f0-9]+)(\)|, ?)#i', "$1x'$2'$3", $row);
//\(エスケープ)連続
$pos = mb_strpos($row, '\\', 0, 'UTF-8');
if ($pos !== false) {
$rowBk = '';
$value = '';
$count = 0;
$arrChar = preg_split('##u', $row, -1, PREG_SPLIT_NO_EMPTY);
foreach ($arrChar as $key2=>$row2) {
$char = $row2;
if ($char === '\\') {
$value .= $char;
$count++;
} else {
if (strlen($value) === 0) {
$rowBk .= $char;
} else {
$value = $value.$char;
$value = str_replace('\\\\', '\\', $value);
//奇数回
if (($count % 2) !== 0) {
if ($char === '\'') {
//シングルクォーテーション
$value = str_replace("\'", "''", $value);
} else if ($char === '"') {
//ダブルクォーテーション
$value = str_replace('\"', '"', $value);
} else if ($char === 'n') {
//改行
$value = str_replace('\n', "\n", $value);
}
}
$rowBk .= $value;
$count = 0;
$value = '';
}
}
}
$row = $rowBk;
}
$arrSQL['insert'][$table][] = $row;
}
}
//DBインポート
if ((is_array($arrSQL) === true) && (count($arrSQL) > 0)) {
importDb($dbname, $arrSQL);
}
} catch (Exception $e) {
echo $e->getMessage()."\n";
}
/**
* ダンプファイル読込み
* 引数:ダンプファイル名
* 戻値:読込みデータ
*/
function readDumpFile($filename) {
if (is_file($filename) === false) {
throw new Exception('ERROR FILE');
}
$fp = @fopen($filename, 'r');
if ($fp === false) {
throw new Exception('ERROR FOPEN');
}
$data = '';
while (!feof($fp)) {
$value = fgets($fp);
if ($value === false) {
break;
}
//コメント
if (preg_match('#^--#', $value, $arrMatch) === 1) {
continue;
}
//コメント
if (preg_match('#^/\*[\s\S]*\*/;$#', $value, $arrMatch) === 1) {
continue;
}
//LOCK TABLES
if (preg_match('#^LOCK TABLES [\s\S]*;$#i', $value, $arrMatch) === 1) {
continue;
}
//UNLOCK TABLES
if (preg_match('#^UNLOCK TABLES;$#i', $value, $arrMatch) === 1) {
continue;
}
//SET
if (preg_match('#^SET [\s\S]*;$#i', $value, $arrMatch) === 1) {
continue;
}
//START TRANSACTION;
if (preg_match('#^START TRANSACTION;$#i', $value, $arrMatch) === 1) {
continue;
}
//COMMIT;
if (preg_match('#^COMMIT;$#i', $value, $arrMatch) === 1) {
continue;
}
//改行コード変換
$value = preg_replace("#\r\n|\r#", "\n", $value);
if ($value === "\n") {
continue;
}
//必要なものだけを取得
$data .= $value;
}
fclose($fp);
return $data;
}
/**
* CREATE TABLE
* 引数:テーブル名
* 引数:ダンプデータ
* 引数:SQL(参照渡し)
* 戻値:なし
*/
function createTable($table, $row, &$arrSQL) {
$primary = '';
$auto = '';
//SQLiteにないものは削除
$row = preg_replace('#\) ENGINE=[\s\S]*;$#', ');', $row);
$row = preg_replace('# COMMENT \'[\s\S]*\'#', ' ', $row);
$row = preg_replace('#\([0-9,\s]+\)#', '', $row);
$row = preg_replace('#COLLATE [a-zA-z0-9_]+#', ' ', $row);
$row = preg_replace('#CHARACTER SET [a-zA-z0-9]+#', ' ', $row);
$row = str_ireplace(' UNSIGNED', ' ', $row);
$row = str_ireplace(' ZEROFILL', ' ', $row);
//データ型変換
$row = str_ireplace(array(' TINYINT',' SMALLINT',' MEDIUMINT',' BIGINT'), ' INTEGER', $row);
$row = str_ireplace(array(' BOOLEAN'), ' INTEGER', $row);
$row = str_ireplace(' INT ', ' INTEGER', $row);
$row = str_ireplace(array(' FLOAT',' DOUBLE'), ' REAL', $row);
$row = str_ireplace(array(' NUMERIC', ' DECIMAL'), ' REAL', $row);
$row = str_ireplace(array(' VARCHAR',' CHAR'), ' TEXT', $row);
$row = str_ireplace(array(' TINYTEXT',' MEDIUMTEXT',' LONGTEXT'), ' TEXT', $row);
$row = str_ireplace(array(' DATETIME',' TIMESTAMP',' DATE',' TIME',' YEAR'), ' TEXT', $row);
$row = str_ireplace(array(' TINYBLOB',' MEDIUMBLOB',' LONGBLOB', ' BINARY',' VARBINARY'), ' BLOB', $row);
$row = str_ireplace(array(' GEOMETRY'), ' BLOB', $row);
//PRIMARY KEY
if (preg_match('#PRIMARY KEY \((.*?)\)#i', $row, $arrMatch) === 1) {
$primary = trim($arrMatch[1]);
}
$arrWork = explode("\n", $row);
foreach ($arrWork as $key2=>$row2) {
//AUTO_INCREMENT
if (preg_match('#^\s*(.*?) [\s\S]* AUTO_INCREMENT#i', $row2, $arrMatch) === 1) {
$auto = trim($arrMatch[1]);
if ($primary === $auto) {
$row2 = str_ireplace('AUTO_INCREMENT', 'PRIMARY KEY AUTOINCREMENT', $row2);
} else {
throw new Exception('ERROR AUTOINCREMENT *1');
}
}
//PRIMARY KEY
if (preg_match('#^\s*PRIMARY KEY \((.*?)\)#i', $row2, $arrMatch) === 1) {
if ($primary === $auto) {
continue;
}
}
//UNIQUE KEY
if (preg_match('#^\s*UNIQUE KEY (.*?) \((.*?)\)#i', $row2, $arrMatch) === 1) {
if ((isset($arrMatch[1]) === true) && (isset($arrMatch[2]) === true)) {
$arrSQL['index'][$table][trim($arrMatch[1])] = sprintf('CREATE UNIQUE INDEX %s ON %s(%s);', $arrMatch[1], $table, $arrMatch[2]);
}
continue;
}
//KEY
if (preg_match('#^\s*KEY (.*?) \((.*?)\)#i', $row2, $arrMatch) === 1) {
if ((isset($arrMatch[1]) === true) && (isset($arrMatch[2]) === true)) {
$arrSQL['index'][$table][trim($arrMatch[1])] = sprintf('CREATE INDEX %s ON %s(%s);', $arrMatch[1], $table, $arrMatch[2]);
}
continue;
}
$arrSQL['create'][$table][] = $row2;
}//foreach ($arrWork as $key2=>$row2)
}
/**
* DBインポート
* 引数:DB名
* 引数:SQL
* 戻値:なし
*/
function importDb($dbname, $arrSQL) {
try {
$chk = false;
$msg = '';
$sql = '';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
];
$dsn = sprintf('sqlite:%s', $dbname);
$clsDb = new PDO($dsn, '', '', $options);
$clsDb->beginTransaction();
$arrType = array('drop', 'create', 'insert', 'index');
foreach ($arrType as $key=>$row) {
if (isset($arrSQL[$row]) === true) {
if ($row === 'create') {
foreach ($arrSQL[$row] as $key2=>$row2) {
$sql = implode('', $row2);
$sql = preg_replace('#,\);$#', ');', $sql);
$clsDb->exec($sql);
usleep(1);
}
} else {
foreach ($arrSQL[$row] as $key2=>$row2) {
foreach ($row2 as $key3=>$row3) {
$sql = $row3;
$clsDb->exec($sql);
usleep(1);
}
}
}
}
}
$clsDb->commit();
$chk = true;
} catch (Exception $e) {
$msg = $e->getMessage()."\n";
$clsDb->rollBack();
}
$clsDb = null;
if ($chk === false) {
throw new Exception(sprintf("ERROR IMPORT DB\n %s %s", $msg, $sql));
}
}