備忘録

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));
	}
}