我正在为我正在开发的应用程序创建一个安装脚本,需要在PHP中动态创建数据库.我已经有了它来创建数据库,但现在我需要加载几个.sql文件.我曾打算一次打开文件和mysql_query一行 - 直到我查看模式文件并意识到它们不是每行一个查询.
那么,我如何从PHP中加载一个sql文件(如phpMyAdmin的import命令)?
$db = new PDO($dsn, $user, $password); $sql = file_get_contents('file.sql'); $qr = $db->exec($sql);
phpBB使用一些函数来解析他们的文件.他们的评论相当好(这是一个例外!)所以你可以很容易地知道他们做了什么(我从http://www.frihost.com/forums/vt-8194.html得到了这个解决方案).这是我经常使用它的解决方案:
0)) { if (isset($lines[$i][0]) && $lines[$i][0] != "#") { $output .= $lines[$i] . "\n"; } else { $output .= "\n"; } // Trading a bit of speed for lower mem. use here. $lines[$i] = ""; } } return $output; } // // split_sql_file will split an uploaded sql file into single sql statements. // Note: expects trim() to have already been run on $sql. // function split_sql_file($sql, $delimiter) { // Split up our string into "possible" SQL statements. $tokens = explode($delimiter, $sql); // try to save mem. $sql = ""; $output = array(); // we don't actually care about the matches preg gives us. $matches = array(); // this is faster than calling count($oktens) every time thru the loop. $token_count = count($tokens); for ($i = 0; $i < $token_count; $i++) { // Don't wanna add an empty string as the last thing in the array. if (($i != ($token_count - 1)) || (strlen($tokens[$i] > 0))) { // This is the total number of single quotes in the token. $total_quotes = preg_match_all("/'/", $tokens[$i], $matches); // Counts single quotes that are preceded by an odd number of backslashes, // which means they're escaped quotes. $escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$i], $matches); $unescaped_quotes = $total_quotes - $escaped_quotes; // If the number of unescaped quotes is even, then the delimiter did NOT occur inside a string literal. if (($unescaped_quotes % 2) == 0) { // It's a complete sql statement. $output[] = $tokens[$i]; // save memory. $tokens[$i] = ""; } else { // incomplete sql statement. keep adding tokens until we have a complete one. // $temp will hold what we have so far. $temp = $tokens[$i] . $delimiter; // save memory.. $tokens[$i] = ""; // Do we have a complete statement yet? $complete_stmt = false; for ($j = $i + 1; (!$complete_stmt && ($j < $token_count)); $j++) { // This is the total number of single quotes in the token. $total_quotes = preg_match_all("/'/", $tokens[$j], $matches); // Counts single quotes that are preceded by an odd number of backslashes, // which means they're escaped quotes. $escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$j], $matches); $unescaped_quotes = $total_quotes - $escaped_quotes; if (($unescaped_quotes % 2) == 1) { // odd number of unescaped quotes. In combination with the previous incomplete // statement(s), we now have a complete statement. (2 odds always make an even) $output[] = $temp . $tokens[$j]; // save memory. $tokens[$j] = ""; $temp = ""; // exit the loop. $complete_stmt = true; // make sure the outer loop continues at the right point. $i = $j; } else { // even number of unescaped quotes. We still don't have a complete statement. // (1 odd and 1 even always make an odd) $temp .= $tokens[$j] . $delimiter; // save memory. $tokens[$j] = ""; } } // for.. } // else } } return $output; } $dbms_schema = 'yourfile.sql'; $sql_query = @fread(@fopen($dbms_schema, 'r'), @filesize($dbms_schema)) or die('problem '); $sql_query = remove_remarks($sql_query); $sql_query = split_sql_file($sql_query, ';'); $host = 'localhost'; $user = 'user'; $pass = 'pass'; $db = 'database_name'; //In case mysql is deprecated use mysqli functions. mysqli_connect($host,$user,$pass) or die('error connection'); mysqli_select_db($db) or die('error database selection'); $i=1; foreach($sql_query as $sql){ echo $i++; echo "
"; mysql_query($sql) or die('error in query'); } ?>
我觉得这里回答这个问题的每个人都不知道成为一个允许人们在自己的服务器上安装应用程序的Web应用程序开发人员是什么感觉.特别是共享托管,不允许您使用SQL,如前面提到的"LOAD DATA"查询.大多数共享主机也不允许您使用shell_exec.
现在,要回答OP,最好的办法是在变量中构建一个包含查询的PHP文件,然后运行它们.如果您决定解析.sql文件,您应该查看phpMyAdmin并获取一些从.sql文件中获取数据的想法.查看具有安装程序的其他Web应用程序,您将看到它们,而不是使用.sql文件进行查询,它们只是将它们打包成PHP文件,然后通过mysql_query运行每个字符串或者它们需要做什么.
最简单的解决方案是使用shell_exec()以SQL脚本作为输入来运行mysql客户端.这可能会慢一点,因为它必须fork,但你可以在几分钟内编写代码,然后回到处理有用的东西.编写PHP脚本来运行任何SQL脚本可能需要数周时间.
支持SQL脚本比人们在这里描述的更复杂,除非您确定您的脚本仅包含脚本功能的子集.下面是普通SQL脚本中可能出现的一些示例,这些示例使得编写脚本以逐行解释它变得复杂.
-- Comment lines cannot be prepared as statements -- This is a MySQL client tool builtin command. -- It cannot be prepared or executed by server. USE testdb; -- This is a multi-line statement. CREATE TABLE foo ( string VARCHAR(100) ); -- This statement is not supported as a prepared statement. LOAD DATA INFILE 'datafile.txt' INTO TABLE foo; -- This statement is not terminated with a semicolon. DELIMITER // -- This multi-line statement contains a semicolon -- but not as the statement terminator. CREATE PROCEDURE simpleproc (OUT param1 INT) BEGIN SELECT COUNT(*) INTO param1 FROM foo; END //
如果您只支持SQL脚本的一个子集,排除上面的一些极端情况,那么编写一个读取文件并在文件中执行SQL语句的PHP脚本相对容易.但是如果你想支持任何有效的SQL脚本,那就复杂得多.
另见我对这些相关问题的回答:
在PHP中运行MySQL*.sql文件
是否可以从另一个sql脚本中的存储过程调用sql脚本?
PHP:一个mysql_query语句中的多个SQL查询
mysqli
可以运行由a分隔的多个查询 ;
你可以在整个文件中读取并立即运行它 mysqli_multi_query()
但是,我会第一个说这不是最优雅的解决方案.
在我的项目中,我使用了下一个解决方案:
0) { query($row); } fclose($file); } /** * Remove comments from sql * * @param string sql * @param boolean is multicomment line * @return string */ function clearSQL($sql, &$isMultiComment) { if ($isMultiComment) { if (preg_match('#\*/#sUi', $sql)) { $sql = preg_replace('#^.*\*/\s*#sUi', '', $sql); $isMultiComment = false; } else { $sql = ''; } if(trim($sql) == ''){ return $sql; } } $offset = 0; while (preg_match('{--\s|#|/\*[^!]}sUi', $sql, $matched, PREG_OFFSET_CAPTURE, $offset)) { list($comment, $foundOn) = $matched[0]; if (isQuoted($foundOn, $sql)) { $offset = $foundOn + strlen($comment); } else { if (substr($comment, 0, 2) == '/*') { $closedOn = strpos($sql, '*/', $foundOn); if ($closedOn !== false) { $sql = substr($sql, 0, $foundOn) . substr($sql, $closedOn + 2); } else { $sql = substr($sql, 0, $foundOn); $isMultiComment = true; } } else { $sql = substr($sql, 0, $foundOn); break; } } } return $sql; } /** * Check if "offset" position is quoted * * @param int $offset * @param string $text * @return boolean */ function isQuoted($offset, $text) { if ($offset > strlen($text)) $offset = strlen($text); $isQuoted = false; for ($i = 0; $i < $offset; $i++) { if ($text[$i] == "'") $isQuoted = !$isQuoted; if ($text[$i] == "\\" && $isQuoted) $i++; } return $isQuoted; } function query($sql) { global $mysqli; //echo '#SQL CODE TO RUN:
' . htmlspecialchars($sql) . ';
'; if (!$query = $mysqli->query($sql)) { throw new Exception("Cannot execute request to the database {$sql}: " . $mysqli->error); } } set_time_limit(0); $mysqli = new mysqli('localhost', 'root', '', 'test'); $mysqli->set_charset("utf8"); header('Content-Type: text/html;charset=utf-8'); sqlImport('import.sql'); echo "Peak MB: ", memory_get_peak_usage(true)/1024/1024;
在测试sql文件(41Mb)内存峰值使用情况:3.25Mb
由于我无法评论答案,请注意使用以下解决方案:
$db = new PDO($dsn, $user, $password); $sql = file_get_contents('file.sql'); $qr = $db->exec($sql);
PHP PDO中有一个错误https://bugs.php.net/bug.php?id=61613
db->exec('SELECT 1; invalidstatement; SELECT 2');
不会出错或返回false(在PHP 5.5.14上测试).
Plahcinski解决方案的更新解决方案。另外,您可以使用fopen和fread处理较大的文件:
$fp = file('database.sql', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); $query = ''; foreach ($fp as $line) { if ($line != '' && strpos($line, '--') === false) { $query .= $line; if (substr($query, -1) == ';') { mysql_query($query); $query = ''; } } }