3

我的脚本有问题,该脚本从 csv 文件导入条目并将它们作为 wordpress 自定义帖子插入(每一行都是一个帖子)......最初,我在自己的类中设置了导入功能,但几乎没有工作...从我收集的信息来看,问题是全局变量没有被缓存,每次我调用实例时都会消耗更多的内存,直到进程耗尽内存并崩溃......所以我删除了类并设置按照下面的代码描述的导入功能。

有了这个设置,我达到了它可以正常处理多达 17k 个帖子的程度,但是如果我尝试导入比这更多的帖子,它就会退出而没有任何错误(我的 php 错误日志或wordpress debug.log 文件)

该脚本成功插入了 17k 个帖子,打印出回显信息,直到它在“剩余 XXX 项”处过早停止,并完成加载页面,此时不再输出……它永远不会进入最终echo "Done!";语句。 .

这发生在 localhost 开发环境和托管开发服务器上。我一直关注内存使用情况,它在我的本地主机上从未超过 60%(从 ~50% 开始),而且我没有看到逐步的内存攀升表明内存泄漏......

我也尝试过使用 ini_set('memory_limit', '64M'); 和 set_time_limit(0);

从我读到的其他一些类似的问题来看,

  • 对于 SQL 20k 条目应该没什么大不了的
  • 如果服务器足够强大,wordpress 也应该能够处理这个问题

我可以对下面的代码进行哪些优化/改进以使该脚本在这种规模上工作?

或者可能跳过 wordpress 内置功能并使用 LOAD DATA INFILE 处理所有内容,如 fancypants在这里提到的

我更愿意通过提供的 wordpress 功能处理数据。

csv文件约为1mb ...

编码:


这些函数驻留在自己的文件中 - import.php

function fileupload_process() {
  ini_set('memory_limit', '64M');
  set_time_limit(0);
  $uploadfiles = $_FILES['uploadfiles'];
  if (is_array($uploadfiles)) {
    foreach ($uploadfiles['name'] as $key => $value) {
      // look only for uploaded files
      if ($uploadfiles['error'][$key] == 0) {
        $filetmp = $uploadfiles['tmp_name'][$key];
        if (($handle = fopen($filetmp, "r")) !== FALSE) {
          $flag = true;
          $songs = explode("\n",file_get_contents($filetmp));
          $count = count( $songs );
          unset($songs);
          echo "Total item count: " . $count . "<BR />";
          // typical entry: If You Have To Ask,Red Hot Chili Peppers,0:03:37, Rock & Alternative,1991,on
          // using a generous 1000 length - will lowering this actually impact performance in terms of memory allocation?
          while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
            // Skip the first entry in the csv containing colmn info
            if($flag) {
                      $flag = false; 
              echo "<BR />"; 
              $count--; 
              continue; 
            }
            // insert the current post and relevant info into the database
            $currently_processed = process_custom_post($data, $count);
            $count--;
          }
          echo "Done!";
          fclose($handle);
        }
        unlink($filetmp); // delete the temp csv file
      }
    }
  }
} // END: file_upload_process()
function process_custom_post($song, $count) {
  $track =  (array_key_exists(0, $song) && $song[0] != "" ?  $song[0] : 'N/A');
  $artist = (array_key_exists(1, $song) && $song[1] != ""  ?  $song[1] : 'N/A');
  $length = (array_key_exists(2, $song) && $song[2] != ""  ?  $song[2] : 'N/A');
  $genre = (array_key_exists(3, $song) && $song[3] != ""  ?  $song[3] : 'N/A');
  $year = (array_key_exists(4, $song) && $song[4] != ""  ?  $song[4] : 'N/A');
  $month = (array_key_exists(5, $song) && $song[5] != ""  ?  $song[5] : 'N/A');
  $playlist = (array_key_exists(6, $song) && $song[6] != ""  ?  $song[6] : '');
  $custom_post = array();
  $custom_post['post_type'] = 'songs';
  $custom_post['post_status'] = 'publish';
  $custom_post['post_title'] = $track;
  echo "Importing " . $artist  . " - " . $track . " <i> (" . $count ." items remaining)...</i><BR />";
  $post_id = wp_insert_post( $custom_post );
  $updated = update_post_meta($post_id, 'artist_name', $artist);
  $updated = update_post_meta($post_id, 'song_length', $length);
  $updated = update_post_meta($post_id, 'song_genre', $genre);
  $updated = update_post_meta($post_id, 'song_year', $year);
  $updated = update_post_meta($post_id, 'song_month', $month);
  $updated = update_post_meta($post_id, 'sample_playlist', $playlist);
  return true;
} // END: process_custom_post()
function import_page () {
//HTML for the import page + the file upload form
  if (isset($_POST['uploadfile'])) {
    fileupload_process();
       }
}

import.php 包含在插件类之外的 wordpress 插件中

即这里是关于我如何在导入页面上获取脚本的相关信息:

define( 'MY_PLUGIN_ROOT' , dirname(__FILE__) );
include_once( MY_PLUGIN_ROOT . 'import.php');
class my_plugin () {
 function __construct() {
  add_action( 'init', array( &$this, 'admin_menu_init' ) );
 }
 function admin_menu_init() {
   if(is_admin()) {
     //Add the necessary pages for the plugin
     add_action('admin_menu', array(&$this, 'add_menu_items'));
   }
 }
 function add_menu_items() {
  add_submenu_page( 'edit.php?post_type=songs', 'Import Songs', 'Import Songs',  'manage_options', 'import-songs', 'import_page' );
 }
}

任何想法、评论或建议将不胜感激。

4

1 回答 1

3

因此,在与 XDEBUG、phpmyadmin 和朋友们进行了长时间的分析会议之后,我终于缩小了瓶颈所在:mysql 查询

我启用了

define('SAVEQUERIES', true);

更详细地检查查询并将查询数组输出到我的 debug.log 文件

global $wpdb;
error_log( print_r( $wpdb->queries, true ) );

在检查对数据库的每个 INSERT 调用时

  1. wp_insert_post()

    [0] => 插入wp_posts( post_author, post_date, post_date_gmt, post_content, post_content_filtered, post_title, post_excerpt, post_status, post_type, comment_status, ping_status, post_password, post_name, to_ping, pinged, post_modified, post_modified_gmt, post_parent, menu_order, guid) 值 (...)

    [1] => 0.10682702064514

  2. update_post_meta()

    [0] => 插入wp_postmeta( post_id, meta_key, meta_value) 值 (...)

    [1] => 0.10227680206299

我发现每次对数据库的调用平均在我的本地主机服务器上花费约 0.1 秒

但是,由于我要为每个条目更新 6 个自定义字段,因此它可以

6 cf 插入调用/条目 * ~0.1s/cf 插入调用 *20 000 总条目 * 1 分钟/60 秒 = 200 分钟

仅处理 20k 个帖子的自定义字段约 2.5 小时

现在,由于所有自定义字段都插入到同一个表 wp_post_meta 中,我希望将所有 update_post_meta 调用合并到一个调用中,因此,就此导入脚本的执行时间而言,大大提高了性能。

我查看了 wp 核心中的 update_post_meta 函数,发现没有传递数组而不是单个 cf 键和值的本机选项,所以我通过相关代码来查明 SQL INSERT 行并查看如何做一些事情的行:

`INSERT INTO `wp_postmeta` (`post_id`,`meta_key`,`meta_value`) 
  VALUES ( $post_id,  'artist_name' , $artist) 
         ( $post_id,  'song_length' , $length )
         ( $post_id,  'song_genre' , $genre ) ...` 

以此类推,在我的案例中,所有 6 个自定义字段都包含在一个$wpdb->query();.

调整我的 process_custom_post() 函数以反映这一点,我最终得到:

function process_custom_post($song) {
  global $wpdb;

  // Prepare and insert the custom post
  $track =  (array_key_exists(0, $song) && $song[0] != "" ?  $song[0] : 'N/A');
  $custom_post = array();
  $custom_post['post_type'] = 'songs';
  $custom_post['post_status'] = 'publish';
  $custom_post['post_title'] = $track;
  $post_id = wp_insert_post( $custom_post );

  // Prepare and insert the custom post meta
  $meta_keys = array();
  $meta_keys['artist_name'] = (array_key_exists(1, $song) && $song[1] != ""  ?  $song[1] : 'N/A');
  $meta_keys['song_length'] = (array_key_exists(2, $song) && $song[2] != ""  ?  $song[2] : 'N/A');
  $meta_keys['song_genre'] = (array_key_exists(3, $song) && $song[3] != ""  ?  $song[3] : 'N/A');
  $meta_keys['song_year'] = (array_key_exists(4, $song) && $song[4] != ""  ?  $song[4] : 'N/A');
  $meta_keys['song_month'] = (array_key_exists(5, $song) && $song[5] != ""  ?  $song[5] : 'N/A');
  $meta_keys['sample_playlist'] = (array_key_exists(6, $song) && $song[6] != ""  ?  $song[6] : '');
  $custom_fields = array();
  $place_holders = array();
  $query_string = "INSERT INTO $wpdb->postmeta ( post_id, meta_key, meta_value) VALUES ";
  foreach($meta_keys as $key => $value) {
     array_push($custom_fields, $post_id, $key, $value);
     $place_holders[] = "('%d', '%s', '%s')";
  }
  $query_string .= implode(', ', $place_holders);
  $wpdb->query( $wpdb->prepare("$query_string ", $custom_fields));
  return true;
}

和中提琴!不是每个自定义帖子对数据库进行 7 次 INSERT 调用,而是在迭代大量帖子时尝试处理多个自定义字段时,我最终会产生 2 次巨大的差异 - 在我现在的情况下,20k 条目在 15 内完成处理-20 分钟(相对于在大约 17k 个帖子和几小时的处理时间后我会经历辍学的情况)......

所以我从整个经历中学到的关键是,

注意你的数据库调用——它们可以快速加起来!

于 2013-10-01T15:07:51.807 回答