在PHP中运行MySQL的*.sql文件

55
我有两个*.sql文件,用于创建新的网站数据库。第一个文件创建所有表格,第二个文件填充一些默认记录。我希望能够从PHP中执行这些文件。如果使用Zend_Framework可以实现,我也会使用它。 附加信息
  1. 我没有控制台访问权限
  2. 我正在尝试在我们的应用程序内部自动化站点生成。
解决方案

使用shell_exec()...

$command = 'mysql'
        . ' --host=' . $vals['db_host']
        . ' --user=' . $vals['db_user']
        . ' --password=' . $vals['db_pass']
        . ' --database=' . $vals['db_name']
        . ' --execute="SOURCE ' . $script_path
;
$output1 = shell_exec($command . '/site_db.sql"');
$output2 = shell_exec($command . '/site_structure.sql"');

...我从来没有得到有用的输出,但是按照另一个线程上的一些建议,最终使它全部工作起来了。我切换到命令的--option=value格式,并使用--execute="SOURCE ..."代替<来执行文件。

此外,我从未得到过关于shell_exec()exec()之间区别的好解释。


您不能使用控制台吗?那可太简单了... - Pekka
在Linux上对我非常有效。我还没有在Windows XAMPP上尝试过,但我怀疑它不会起作用 :) - ethanpil
4
shell_exec()exec()的区别在于,shell_exec()将所有输出流作为字符串返回,而exec()仅返回输出流的最后一行。来源:https://dev59.com/RWw05IYBdhLWcg3wrDtS#7093892 - michaellindahl
这里是我想到的一个非常好的解决方案https://dev59.com/uFrUa4cB1Zd3GeqPhjIh#41404203 - Chris Muench
15个回答

52
这个问题时不时会出现。直接从PHP运行.sql脚本没有好的解决方案。有一些边缘情况,其中在.sql脚本中常见的语句不能作为SQL语句执行。例如,mysql工具具有MySQL服务器无法识别的内置命令,例如CONNECTTEESTATUSDELIMITER
因此,我给@Ignacio Vazquez-Abrams的答案加1。您应该通过调用mysql工具在PHP中运行.sql脚本,例如使用shell_exec()
我成功地通过了这个测试:
$command = "mysql --user={$vals['db_user']} --password='{$vals['db_pass']}' "
 . "-h {$vals['db_host']} -D {$vals['db_name']} < {$script_path}";

$output = shell_exec($command . '/shellexec.sql');

请参阅我对以下相关问题的答案:


我正在尝试使用shell_exec()方法,但是我没有找到指定要执行的文件的示例。目前我的代码如下:shell_exec('mysql' . ' -u ' . $vals['db_user'] . ' -p ' . $vals['db_pass'] . ' -D ' . $vals['db_name']); - Sonny
1
你可以通过 shell 重定向来读取文件并执行命令:mysql ... < mysqldump.sql - Bill Karwin
我想为那些无法使其正常工作的读者提供一些见解。 shell_exec可能会出现的一个问题是mysql不在路径上。通过在受影响机器的控制台中运行命令mysql,可以轻松测试此问题。在这种情况下,需要将MySQL添加到路径中或需要采用其他方法。 - Aaron Newton
@AaronNewton,好主意。路径相关的问题非常基础,但我忘记了有多少人仍然在与它们斗争。 - Bill Karwin
@LuongTranNguyen,不,我宁愿使用--defaults-file并将用户名和密码放在该文件中。请参见https://dev.mysql.com/doc/refman/8.0/en/option-file-options.html。 - Bill Karwin
显示剩余2条评论

23
$commands = file_get_contents($location);   
$this->_connection->multi_query($commands);

5
你的解决方案适用于mysqli,而我使用的是PDO。你的回答启发了我进行搜索,我找到了这个链接:https://dev59.com/GGw15IYBdhLWcg3w3vpe - Sonny
我正在使用mysqli,但无法使此解决方案工作。$commands返回为空字符串。对于$location,我尝试过相对本地路径(脚本文件与php文件在同一文件夹中)和完全限定的超链接。无论哪种方式,我都得到了相同的结果。 - Jimbo
这似乎对某些类型的高级查询失败,例如创建一个过程并执行它。 - Jonathan
如果其中一个查询失败,你将永远不会知道。 - Your Common Sense

15

你需要为此创建一个完整的SQL解析器。我建议您使用mysql命令行工具来代替,从PHP外部调用它。


13

这是我使用的代码:


function run_sql_file($location){
    //load file
    $commands = file_get_contents($location);

    //delete comments
    $lines = explode("\n",$commands);
    $commands = '';
    foreach($lines as $line){
        $line = trim($line);
        if( $line && !startsWith($line,'--') ){
            $commands .= $line . "\n";
        }
    }

    //convert to array
    $commands = explode(";", $commands);

    //run commands
    $total = $success = 0;
    foreach($commands as $command){
        if(trim($command)){
            $success += (@mysql_query($command)==false ? 0 : 1);
            $total += 1;
        }
    }

    //return number of successful queries and total number of queries found
    return array(
        "success" => $success,
        "total" => $total
    );
}


// Here's a startsWith function
function startsWith($haystack, $needle){
    $length = strlen($needle);
    return (substr($haystack, 0, $length) === $needle);
}

9
在某些情况下,这种方法可能会失败,但不会返回错误消息,而是可能会出现意外行为。例如,在SQL语句中,多行字符串文字可能以字符串'--'开头,或者字符串文字可能包含;字符。如果您要使用此方法,建议使用完整的SQL解析器。 - mindplay.dk

8

6

我知道我有点晚了,但PHP Mini Admin在某些情况下确实是一个救命稻草。它基本上是一个“轻量级”PHPMyAdmin,所有内容都包含在一个文件中,所以不需要复杂的安装,只需上传并登录即可。 简单易行!


2
您可以使用此脚本来运行MySQL脚本文件。当然,您需要设置$hostName、$userName、$password、$dataBaseName、$port和$fileName。
<?php

function parseScript($script) {

  $result = array();
  $delimiter = ';';
  while(strlen($script) && preg_match('/((DELIMITER)[ ]+([^\n\r])|[' . $delimiter . ']|$)/is', $script, $matches, PREG_OFFSET_CAPTURE)) {
    if (count($matches) > 2) {
      $delimiter = $matches[3][0];
      $script = substr($script, $matches[3][1] + 1);
    } else {
      if (strlen($statement = trim(substr($script, 0, $matches[0][1])))) {
        $result[] = $statement;
      }
      $script = substr($script, $matches[0][1] + 1);
    }
  }

  return $result;

}

function executeScriptFile($fileName, $dbConnection) {
  $script = file_get_contents($scriptFleName);
  $statements = parseScript($script);
  foreach($statements as $statement) {
    mysqli_query($dbConnection, $statement);
  }
}

$hostName = '';
$userName = '';
$password = '';
$dataBaseName = '';
$port = '';
$fileName = '';

if ($connection = @mysqli_connect($hostName, $userName, $password, $dataBaseName, $port)) {
  executeScriptFile($fileName, $connection);
} else {
  die('Can not connect to MySQL');
}

当你回答自己的问题时,请附上解释和代码。如果没有解释,我们仍然不知道为什么问题被修复了,而需要研究很多无用的代码。 - Xantium
这不是我的问题。TC说:“我想从PHP中执行这些文件”。我给了脚本如何做到这一点。如果你无法阅读这么小的代码(没有太多的东西需要研究,它太小和明显了),而且如果你不需要解决方案-为什么你不能跳过我的答案呢?:) - Sergiy Lavryk
1
我并不是有意冒犯。你看到上面的踩(如果你认为那是我的话,其实是其他人点的),我是想告诉你为什么会有人踩你,并建议你怎样才能得到赞。我只是想帮忙。此外,它在3分钟前被编辑过,现在看起来更好了。不幸的是,对于新手用户来说,这里的用户期望得到一个很棒的答案(和问题),还要有可行的代码。这需要技巧。一旦你掌握了它,你就能够回答出好的答案(和问题),并得到赞。 - Xantium
1
希望我没有冒犯你,我的意思并非如此。正如我之前所说,我正在尝试通过帮助你改进你的答案和问题来提高你的声誉。 - Xantium

2
不要忘记 phpMyAdmin。它是一个与MySQL交互的非常稳定的界面。
我不知道它是否可以直接从代码中与之交互,但只是想提出来。

好建议。使用纯PHP解析mySQL转储文件太麻烦了,phpMyAdmin则可以轻松解决这个问题(尽管无法自动化)。 - Pekka

1
这是我的解决方案,下面的代码说明了它的作用。原则是逐行读取文件,构建查询并执行每个查询。我看到许多解决方案使用"file_get_contents",这不是一个好的解决方案,因为它可能会导致缓冲区问题,因为它将整个文件内容读入字符串变量中。我的解决方案还考虑了触发器的查询。没有数组分配,注释和空行被剥离。
<?php
 /**
 * Get a connection from database
 * @param type $db_host database hostname
 * @param type $db_user database username
 * @param type $db_password database password
 * @param type $db_name database name
 * @return \PDO
 */
 function get_db_connection($db_host, $db_user, $db_password, $db_name)
{
    $dns = "mysql:host=$db_host;dbname=$db_name";
    try
    {
        return new PDO($dns, $db_user, $db_password);
    } catch (PDOException $ex)
    {
        return null;
    }
}

/**
 * Runs SQL queries from file
 */

 function exec_sql_queries_from_file($script_file, $db_host, $db_user, $db_password, $db_name)
{
    // to increase the default PHP execution time
    set_time_limit ( 60 ); // Max time = 60 seconds

    // Connect to database
    $connection = get_db_connection($db_host, $db_user, $db_password, $db_name);

    // If the connection is acquired
    if($connection != null){

        // Open sql file
        $f = fopen($script_file, 'r');

        // sql query
        $query = '';

        // Default delimiter for queries
        $delimiter = ';';

        // read line by line
        while (!feof($f))
        {           
            $line = str_replace(PHP_EOL, '', fgets($f)); // read a line and remove the end of line character

            /* if the current line contains the key word 'DELIMITER'. Ex: DELIMITER ;; or DELIMITER $$
             * mostly used for TRIGGERS' queries
             */
            if(strpos($line, 'DELIMITER') !== false)
            {
                // change the delimiter and read the next line
                $delimiter = str_replace('DELIMITER ', '', $line);
                continue;
            }   

            // Consider the line as part of a query if it's not empty and it's not a comment line
            if (!empty($line) && !starts_with($line, '/*') && !starts_with($line, '--'))
            {
                // the query hasn't reach its end: concatenate $line to $query if $line is not a delimiter
                $query .= $line !== $delimiter ? $line : '';

                // if the current line ends with $delimiter: end of current query
                if (ends_with($line, $delimiter))
                {                
                    // exec the query
                    $connection->exec($query) or die($connection->errorInfo());
                    // start new query
                    $query = '';
                }
            }                    
        }

        fclose($f);
    }
}

 /**
 * Starts with function
 */
function starts_with($haystack, $needle)
{
    return $haystack{0} === $needle{0} ? stripos($haystack, $needle) === 0 : false;
}

/**
 * Ends with function
 */
function ends_with($haystack, $needle)
{
    $pos = stripos($haystack, $needle);
    return $pos === FALSE ? FALSE : substr($haystack, $pos) === $needle;

}


不错的想法,但有一些错误。 将eol替换为无。 通过;搜索EOL或命令结束(这也可以用于注释)等。 但再次强调,这个想法很棒。 - raiserle

1
我使用了multi_query创建了一个迁移脚本。它可以处理mysqldump输出和phpmyadmin导出,无需使用mysql命令行工具。我还编写了一些逻辑来处理基于存储在数据库中的时间戳的多个迁移文件,就像Rails一样。我知道它需要更多的错误处理,但目前对我来说已经可以正常工作了。
请查看:https://github.com/kepes/php-migration 我认为,如果你不使用它处理用户输入,只使用由开发人员或导出工具制作的脚本,那么它是安全可用的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接