Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/264.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
最佳实践:在PHP中导入mySQL文件;拆分查询_Php_Mysql - Fatal编程技术网

最佳实践:在PHP中导入mySQL文件;拆分查询

最佳实践:在PHP中导入mySQL文件;拆分查询,php,mysql,Php,Mysql,我有一个情况,我必须更新一个共享托管提供商的网站。该网站有一个CMS。使用FTP上传CMS文件非常简单 我还必须导入一个大的(相对于PHP脚本的限制)数据库文件(大约2-3MB未压缩)。Mysql已关闭,无法从外部访问,因此我必须使用FTP上传一个文件,并启动一个PHP脚本将其导入。遗憾的是,我无法访问mysql命令行函数,因此我必须使用本机PHP解析和查询它。我也不能使用加载数据填充。我也不能使用像phpMyAdmin这样的任何交互前端,它需要以自动化的方式运行。我也不能使用mysqli\u

我有一个情况,我必须更新一个共享托管提供商的网站。该网站有一个CMS。使用FTP上传CMS文件非常简单

我还必须导入一个大的(相对于PHP脚本的限制)数据库文件(大约2-3MB未压缩)。Mysql已关闭,无法从外部访问,因此我必须使用FTP上传一个文件,并启动一个PHP脚本将其导入。遗憾的是,我无法访问
mysql
命令行函数,因此我必须使用本机PHP解析和查询它。我也不能使用加载数据填充。我也不能使用像phpMyAdmin这样的任何交互前端,它需要以自动化的方式运行。我也不能使用
mysqli\u multi\u query()


是否有人知道或拥有一个已编码的简单解决方案,该解决方案能够可靠地将此类文件拆分为单个查询(可能有多行语句)并运行查询。由于我可能会遇到许多问题(如何检测字段分隔符是否是数据的一部分;如何处理备注字段中的换行符;等等),我希望避免自己动手处理它。必须有现成的解决方案。

您不能安装phpMyAdmin,gzip文件(这会使文件更小)并使用phpMyAdmin导入吗

编辑:如果不能使用phpMyAdmin,可以使用phpMyAdmin中的代码。我不确定这个特定的部分,但它的结构一般都很好。

你能用吗

如果您使用SELECT INTO OUTFILE格式化db转储文件,这正是您所需要的。没有理由让PHP解析任何内容。

已回答: 此外:


    • 您认为:

      system("cat xxx.sql | mysql -l username database"); 
      

      这是一个内存友好的函数,它应该能够在单个查询中拆分一个大文件,而无需立即打开整个文件:

      function SplitSQL($file, $delimiter = ';')
      {
          set_time_limit(0);
      
          if (is_file($file) === true)
          {
              $file = fopen($file, 'r');
      
              if (is_resource($file) === true)
              {
                  $query = array();
      
                  while (feof($file) === false)
                  {
                      $query[] = fgets($file);
      
                      if (preg_match('~' . preg_quote($delimiter, '~') . '\s*$~iS', end($query)) === 1)
                      {
                          $query = trim(implode('', $query));
      
                          if (mysql_query($query) === false)
                          {
                              echo '<h3>ERROR: ' . $query . '</h3>' . "\n";
                          }
      
                          else
                          {
                              echo '<h3>SUCCESS: ' . $query . '</h3>' . "\n";
                          }
      
                          while (ob_get_level() > 0)
                          {
                              ob_end_flush();
                          }
      
                          flush();
                      }
      
                      if (is_string($query) === true)
                      {
                          $query = array();
                      }
                  }
      
                  return fclose($file);
              }
          }
      
          return false;
      }
      
      和相应的输出:

      CREATE TABLE IF NOT EXISTS "test" (
          "id" INTEGER PRIMARY KEY AUTOINCREMENT,
          "name" TEXT,
          "description" TEXT
      );
      
      BEGIN;
          INSERT INTO "test" ("name", "description")
          VALUES (";;;", "something for you mind; body; soul");
      COMMIT;
      
      UPDATE "test"
          SET "name" = "; "
          WHERE "id" = 1;
      
      SUCCESS: CREATE TABLE IF NOT EXISTS "test" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT, "description" TEXT );
      SUCCESS: BEGIN;
      SUCCESS: INSERT INTO "test" ("name", "description") VALUES (";;;", "something for you mind; body; soul");
      SUCCESS: COMMIT;
      SUCCESS: UPDATE "test" SET "name" = "; " WHERE "id" = 1;
      

      当StackOverflow以XML格式发布他们每月的数据转储时,我编写了PHP脚本将其加载到MySQL数据库中。我在几分钟内导入了大约2.2GB的XML

      我的技巧是使用参数占位符插入列值的
      语句。然后使用循环遍历XML元素和我准备的查询,插入参数的值。我选择XMLReader是因为它是一个流式XML阅读器;它以增量方式读取XML输入,而不需要将整个文件加载到内存中

      您还可以使用一次读取一行CSV文件

      如果您要导入InnoDB表,我建议显式启动和提交事务,以减少自动提交的开销。我每1000行提交一次,但这是任意的

      我不打算在这里发布代码(因为StackOverflow的许可政策),而是使用伪代码:

      connect to database
      open data file
      PREPARE parameterizes INSERT statement
      begin first transaction
      loop, reading lines from data file: {
          parse line into individual fields
          EXECUTE prepared query, passing data fields as parameters
          if ++counter % 1000 == 0,
              commit transaction and begin new transaction
      }
      commit final transaction
      
      用PHP编写这段代码并不是什么了不起的事,当使用准备好的语句和显式事务时,它会运行得非常快。这些功能在过时的PHP扩展中不可用,但如果使用或,则可以使用它们

      我还添加了一些方便的东西,如错误检查、进度报告,以及在数据文件不包含其中一个字段时支持默认值


      我在一个
      abstract
      PHP类中编写了代码,我为需要加载的每个表创建了子类。每个子类声明要加载的列,并按名称(如果数据文件是CSV,则按位置)将它们映射到XML数据文件中的字段。

      单页PHPMyAdmin-Adminer-仅一个PHP脚本文件。
      检查:

      我遇到了同样的问题。我用正则表达式解决了这个问题:

      function splitQueryText($query) {
          // the regex needs a trailing semicolon
          $query = trim($query);
      
          if (substr($query, -1) != ";")
              $query .= ";";
      
          // i spent 3 days figuring out this line
          preg_match_all("/(?>[^;']|(''|(?>'([^']|\\')*[^\\\]')))+;/ixU", $query, $matches, PREG_SET_ORDER);
      
          $querySplit = "";
      
          foreach ($matches as $match) {
              // get rid of the trailing semicolon
              $querySplit[] = substr($match[0], 0, -1);
          }
      
          return $querySplit;
      }
      
      $queryList = splitQueryText($inputText);
      
      foreach ($queryList as $query) {
          $result = mysql_query($query);
      }
      

      可以使用phpMyAdmin导入文件。即使它很大,也只需使用配置目录,将其上传到那个里,然后从phpMyAdmin导入页面中选择它。一旦文件处理接近PHP限制,phpMyAdmin将中断导入,再次显示带有预定义值的导入页面,指示继续导入的位置。

      Export 第一步是以合理的格式获取输入,以便在导出时进行解析。从你的问题 您似乎可以控制此数据的导出,但不能控制导入

      ~: mysqldump test --opt --skip-extended-insert | grep -v '^--' | grep . > test.sql
      
      这会将测试数据库(不包括所有注释行和空行)转储到test.sql中。它还禁用 扩展INSERT,意味着每行有一条INSERT语句。这将有助于限制内存使用 在导入过程中,但以导入速度为代价

      进口 导入脚本如下所示:

      <?php
      
      $mysqli = new mysqli('localhost', 'hobodave', 'p4ssw3rd', 'test');
      $handle = fopen('test.sql', 'rb');
      if ($handle) {
          while (!feof($handle)) {
              // This assumes you don't have a row that is > 1MB (1000000)
              // which is unlikely given the size of your DB
              // Note that it has a DIRECT effect on your scripts memory
              // usage.
              $buffer = stream_get_line($handle, 1000000, ";\n");
              $mysqli->query($buffer);
          }
      }
      echo "Peak MB: ",memory_get_peak_usage(true)/1024/1024;
      
      这说明,您在不到3分钟的时间内处理了一个15MB的mysqldump,其峰值RAM使用率为1.75MB

      替代出口 如果内存限制足够高,但速度太慢,可以使用以下导出尝试此操作:

      ~: mysqldump test --opt | grep -v '^--' | grep . > test.sql
      
      这将允许扩展插入,即在一个查询中插入多行。以下是同一数据库的统计数据:

      daves-macbookpro:~ hobodave$ du -hs test.sql 
       11M    test.sql
      daves-macbookpro:~ hobodave$ time php import.php 
      Peak MB: 3.75
      real    0m23.878s
      user    0m0.110s
      sys 0m0.101s
      
      请注意,它在3.75 MB时使用了超过2倍的RAM,但所需时间约为RAM的1/6。我建议尝试这两种方法,看看哪种适合你的需要

      编辑: 使用CHAR、VARCHAR、BINARY、VARBINARY和BLOB字段类型,我无法在任何mysqldump输出中显示换行符。如果您确实有BLOB/二进制字段,请使用以下内容以防万一:

      ~: mysqldump5 test --hex-blob --opt | grep -v '^--' | grep . > test.sql
      

      如果不进行分析,则无法可靠地拆分查询。下面是无法用正则表达式正确拆分的有效SQL

      SELECT ";"; SELECT ";\"; a;";
      SELECT ";
          abc";
      
      我用PHP编写了一个小的SqlFormatter类,其中包括一个查询标记器。我添加了一个splitQuery方法来可靠地拆分所有查询(包括上面的示例)

      如果不需要,可以删除格式并突出显示方法

      一个缺点是它需要整个sql字符串都在内存中,如果您使用的是大型sql文件,这可能会成为一个问题。我是苏
      SELECT ";"; SELECT ";\"; a;";
      SELECT ";
          abc";
      
          DELIMITER //
          CREATE TRIGGER `mytrigger` BEFORE INSERT ON `mytable`
          FOR EACH ROW BEGIN
               SET NEW.`create_time` = NOW();
          END
          //
          DELIMITER ;
      
          CREATE TRIGGER `mytrigger` BEFORE INSERT ON `mytable`
          FOR EACH ROW BEGIN
               SET NEW.`create_time` = NOW();
          END;
      
          function SplitSQL($file, $delimiter = ';')
          {
              set_time_limit(0);            
              $matches = array();
              $otherDelimiter = false;
              if (is_file($file) === true) {
                  $file = fopen($file, 'r');
                  if (is_resource($file) === true) {
                      $query = array();
                      while (feof($file) === false) {
                          $query[] = fgets($file);
                          if (preg_match('~' . preg_quote('delimiter', '~') . '\s*([^\s]+)$~iS', end($query), $matches) === 1){     
                              //DELIMITER DIRECTIVE DETECTED
                              array_pop($query); //WE DON'T NEED THIS LINE IN SQL QUERY
                              if( $otherDelimiter = ( $matches[1] != $delimiter )){
                              }else{
                                  //THIS IS THE DEFAULT DELIMITER, DELETE THE LINE BEFORE THE LAST (THAT SHOULD BE THE NOT DEFAULT DELIMITER) AND WE SHOULD CLOSE THE STATEMENT                                
                                  array_pop($query);
                                  $query[]=$delimiter;
                              }                                                                                    
                          }                        
                          if ( !$otherDelimiter && preg_match('~' . preg_quote($delimiter, '~') . '\s*$~iS', end($query)) === 1) {                            
                              $query = trim(implode('', $query));
                              if (mysql_query($query) === false){
                                  echo '<h3>ERROR: ' . $query . '</h3>' . "\n";
                              }else{
                                  echo '<h3>SUCCESS: ' . $query . '</h3>' . "\n";
                              }
                              while (ob_get_level() > 0){
                                  ob_end_flush();
                              }
                              flush();                        
                          }
                          if (is_string($query) === true) {
                              $query = array();
                          }
                      }                    
                      return fclose($file);
                  }
              }
              return false;
      }