从PHP中加载.sql文件

Jos*_*ton 65 php mysql sql import scripting

我正在为我正在开发的应用程序创建一个安装脚本,需要在PHP中动态创建数据库.我已经有了它来创建数据库,但现在我需要加载几个.sql文件.我曾打算一次打开文件和mysql_query一行 - 直到我查看模式文件并意识到它们不是每行一个查询.

那么,我如何从PHP中加载一个sql文件(如phpMyAdmin的import命令)?

小智 71

$db = new PDO($dsn, $user, $password);

$sql = file_get_contents('file.sql');

$qr = $db->exec($sql);
Run Code Online (Sandbox Code Playgroud)

  • 脚本是643mb时怎么样?或者在任何情况下,大于`max_allowed_pa​​cket`? (5认同)
  • 这主要对我有用。它似乎没有导入存储过程或触发器,但可以很好地处理 DROP、TRUNCATE、SELECT、INSERT、UPDATE。 (2认同)

Yas*_*sin 58

phpBB使用一些函数来解析他们的文件.他们的评论相当好(这是一个例外!)所以你可以很容易地知道他们做了什么(我从http://www.frihost.com/forums/vt-8194.html得到了这个解决方案).这是我经常使用它的解决方案:

<php
ini_set('memory_limit', '5120M');
set_time_limit ( 0 );
/***************************************************************************
*                             sql_parse.php
*                              -------------------
*     begin                : Thu May 31, 2001
*     copyright            : (C) 2001 The phpBB Group
*     email                : support@phpbb.com
*
*     $Id: sql_parse.php,v 1.8 2002/03/18 23:53:12 psotfx Exp $
*
****************************************************************************/

/***************************************************************************
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 ***************************************************************************/

/***************************************************************************
*
*   These functions are mainly for use in the db_utilities under the admin
*   however in order to make these functions available elsewhere, specifically
*   in the installation phase of phpBB I have seperated out a couple of
*   functions into this file.  JLH
*
\***************************************************************************/

//
// remove_comments will strip the sql comment lines out of an uploaded sql file
// specifically for mssql and postgres type files in the install....
//
function remove_comments(&$output)
{
   $lines = explode("\n", $output);
   $output = "";

   // try to keep mem. use down
   $linecount = count($lines);

   $in_comment = false;
   for($i = 0; $i &lt; $linecount; $i++)
   {
      if( preg_match("/^\/\*/", preg_quote($lines[$i])) )
      {
         $in_comment = true;
      }

      if( !$in_comment )
      {
         $output .= $lines[$i] . "\n";
      }

      if( preg_match("/\*\/$/", preg_quote($lines[$i])) )
      {
         $in_comment = false;
      }
   }

   unset($lines);
   return $output;
}

//
// remove_remarks will strip the sql comment lines out of an uploaded sql file
//
function remove_remarks($sql)
{
   $lines = explode("\n", $sql);

   // try to keep mem. use down
   $sql = "";

   $linecount = count($lines);
   $output = "";

   for ($i = 0; $i &lt; $linecount; $i++)
   {
      if (($i != ($linecount - 1)) || (strlen($lines[$i]) > 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 &lt; $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("/(?&lt;!\\\\)(\\\\\\\\)*\\\\'/", $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 &lt; $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("/(?&lt;!\\\\)(\\\\\\\\)*\\\\'/", $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 "<br />";
mysql_query($sql) or die('error in query');
}

?>
Run Code Online (Sandbox Code Playgroud)

  • 这应该是公认的.工作就像一个魅力,谢谢. (7认同)
  • 感谢phpBB集团,感谢Abu Sadat,你注意到这段代码也可以重用于其他项目.再次 (2认同)

Jer*_*ett 48

我觉得这里回答这个问题的每个人都不知道成为一个允许人们在自己的服务器上安装应用程序的Web应用程序开发人员是什么感觉.特别是共享托管,不允许您使用SQL,如前面提到的"LOAD DATA"查询.大多数共享主机也不允许您使用shell_exec.

现在,要回答OP,最好的办法是在变量中构建一个包含查询的PHP文件,然后运行它们.如果您决定解析.sql文件,您应该查看phpMyAdmin并获取一些从.sql文件中获取数据的想法.查看具有安装程序的其他Web应用程序,您将看到它们,而不是使用.sql文件进行查询,它们只是将它们打包成PHP文件,然后通过mysql_query运行每个字符串或者它们需要做什么.

  • `phpMyAdmin`的代码库是sh*t.执行导入的文件(`phpMyAdmin/library/import/sql.php`)大量使用全局变量,许多注释都有重大错误.你知道其他更好的例子吗? (5认同)
  • 我仍然不明白他为什么不能将.sql文件读入字符串并使用PDO或mysqli执行它.我就是这样做的.PDO和mysqli支持多个查询.不可否认,我还没有运行任何巨大的.sql文件,但你不能只增加或删除PHP的最大脚本执行时间吗? (4认同)
  • 好的一点是,托管环境更具限制性.OP的问题没有提到应用程序需要在托管环境中部署.嗯.在PHP中运行SQL脚本的问题经常出现,这是一个很棒的小项目. (2认同)
  • 通过Luis Granja http://stackoverflow.com/a/7178917/80353查看以下答案 (2认同)

Bil*_*win 27

最简单的解决方案是使用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
// 
Run Code Online (Sandbox Code Playgroud)

如果您只支持SQL脚本的一个子集,排除上面的一些极端情况,那么编写一个读取文件并在文件中执行SQL语句的PHP脚本相对容易.但是如果你想支持任何有效的SQL脚本,那就复杂得多.


另见我对这些相关问题的回答:


pha*_*ckk 10

mysqli 可以运行由a分隔的多个查询 ;

你可以在整个文件中读取并立即运行它 mysqli_multi_query()

但是,我会第一个说这不是最优雅的解决方案.


Gro*_*omo 10

在我的项目中,我使用了下一个解决方案:

<?php

/**
 * Import SQL from file
 *
 * @param string path to sql file
 */
function sqlImport($file)
{

    $delimiter = ';';
    $file = fopen($file, 'r');
    $isFirstRow = true;
    $isMultiLineComment = false;
    $sql = '';

    while (!feof($file)) {

        $row = fgets($file);

        // remove BOM for utf-8 encoded file
        if ($isFirstRow) {
            $row = preg_replace('/^\x{EF}\x{BB}\x{BF}/', '', $row);
            $isFirstRow = false;
        }

        // 1. ignore empty string and comment row
        if (trim($row) == '' || preg_match('/^\s*(#|--\s)/sUi', $row)) {
            continue;
        }

        // 2. clear comments
        $row = trim(clearSQL($row, $isMultiLineComment));

        // 3. parse delimiter row
        if (preg_match('/^DELIMITER\s+[^ ]+/sUi', $row)) {
            $delimiter = preg_replace('/^DELIMITER\s+([^ ]+)$/sUi', '$1', $row);
            continue;
        }

        // 4. separate sql queries by delimiter
        $offset = 0;
        while (strpos($row, $delimiter, $offset) !== false) {
            $delimiterOffset = strpos($row, $delimiter, $offset);
            if (isQuoted($delimiterOffset, $row)) {
                $offset = $delimiterOffset + strlen($delimiter);
            } else {
                $sql = trim($sql . ' ' . trim(substr($row, 0, $delimiterOffset)));
                query($sql);

                $row = substr($row, $delimiterOffset + strlen($delimiter));
                $offset = 0;
                $sql = '';
            }
        }
        $sql = trim($sql . ' ' . $row);
    }
    if (strlen($sql) > 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 '#<strong>SQL CODE TO RUN:</strong><br>' . htmlspecialchars($sql) . ';<br><br>';
    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;
Run Code Online (Sandbox Code Playgroud)

在测试sql文件(41Mb)内存峰值使用情况:3.25Mb


zst*_*ate 6

由于我无法评论答案,请注意使用以下解决方案:

$db = new PDO($dsn, $user, $password);

$sql = file_get_contents('file.sql');

$qr = $db->exec($sql);
Run Code Online (Sandbox Code Playgroud)

PHP PDO中有一个错误https://bugs.php.net/bug.php?id=61613

db->exec('SELECT 1; invalidstatement; SELECT 2');
Run Code Online (Sandbox Code Playgroud)

不会出错或返回false(在PHP 5.5.14上测试).


Sch*_*kie 5

我的建议是查看 PHPMyBackup 的源代码。它是一个自动化的 PHP SQL 加载器。您会发现 mysql_query 一次只加载一个查询,而 PHPMyAdmin 和 PHPMyBackup 等项目已经为您完成了以正确方式解析 SQL 的艰苦工作。请不要重新发明那个轮子:P

  • FWIW,phpMyBackup 和 phpMyAdmin 都获得了 GPL 许可。如果您“借用”了他们的任何代码,您也有义务制作自己的项目 GPL。 (4认同)

san*_*neo 5

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 = '';
        }
    }
}
Run Code Online (Sandbox Code Playgroud)