如何流式创建JSON文件?

时间:2018-01-31 06:29:22

标签: php json wordpress woocommerce

我试图从数据库查询的大型转储中创建一个JSON文件,并在我将LIMIT设置为返回100000行时工作,但是当我想要返回所有行时,它只会转到502错误(页面请求已取消,因为完成时间太长。想知道是否有一种方法可以使用php简化JSON文件的创建,或者如果有一个库允许我在部分中构建json文件?

基本上我在这里运行.php文件试图从woocommerce获取json格式的所有订单,因为我购买的“CSV Import Suite”插件在导入订单时不起作用,它只是留在队列中。

所以,我决定尝试自己导出所有订单,但是继续点击502错误页面,它也从不创建.json文件,所以我想我需要一种方法来流式传输这个。任何有关这方面的帮助将不胜感激......

ini_set('memory_limit', '-1');
ini_set('max_execution_time', '-1');
set_time_limit(0);
error_reporting(E_ALL);
ob_implicit_flush(TRUE);
ob_end_flush();

global $wpdb, $root_dir;

if (!defined('ABSPATH'))
    $root_dir = dirname(__FILE__) . '/';
else
    $root_dir = ABSPATH;


$download = isset($_GET['download']);

// Allows us to use WP functions in a .php file without 404 headers!
require_once($root_dir . 'wp-config.php');
$wp->init();
$wp->parse_request();
$wp->query_posts();
$wp->register_globals();

if (empty($download))
    $wp->send_headers();

// exclude
$exclude_post_statuses = array('trash', 'wc-refunded', 'wc_cancelled');


$start_date = !empty($_GET['start_date']) ? DateTime::createFromFormat('Y-m-d', $_GET['start_date']) : '';
$end_date = !empty($_GET['end_date']) ? DateTime::createFromFormat('Y-m-d', $_GET['end_date']) : '';


$order_db = array(
    'columns' => array(
        'p' => array('ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_excerpt', 'post_status', 'comment_status', 'ping_status', 'post_password', 'post_name', 'to_ping', 'pinged', 'post_modified', 'post_modified_gmt', 'post_content_filtered', 'post_parent', 'guid', 'menu_order', 'post_type', 'post_mime_type', 'comment_count'),
        'pm' => array('meta_id', 'post_id', 'meta_key', 'meta_value'),
        'oi' => array('order_item_id', 'order_item_name', 'order_item_type', 'order_id'),
        'oim' => array('meta_id', 'order_item_id', 'meta_key', 'meta_value')
    )
);

$select_data = '';
$total_columns = count($order_db['columns']);
$i = 1;

foreach($order_db['columns'] as $column_key => $columns)
{
    $select_data .= implode(', ', array_map(
        function ($v, $k) { return $k . '.' . $v . ' AS ' . $k . '_' . $v; },
        $columns,
        array_fill(0, count($columns), $column_key)
    ));

    if ($i < $total_columns)
        $select_data .= ', ';

    $i++;
}

// HUGE DATABASE DUMP HERE, needs to be converted to JSON, after getting all columns of all tables...
$orders_query = $wpdb->get_results('
    SELECT ' . $select_data . '
    FROM ' . $wpdb->posts . ' AS p
    INNER JOIN ' . $wpdb->postmeta . ' AS pm ON (pm.post_id = p.ID)
    LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_items AS oi ON (oi.order_id = p.ID)
    LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta AS oim ON (oim.order_item_id = oi.order_item_id)
    WHERE p.post_type = "shop_order"' . (!empty($exclude_post_statuses) ? ' AND p.post_status NOT IN ("' . implode('","', $exclude_post_statuses) . '")' : '') . (!empty($start_date) ? ' AND post_date >= "' . $start_date->format('Y-m-d H:i:s') . '"' : '') . (!empty($end_date) ? ' AND post_date <= "' . $end_date->format('Y-m-d H:i:s') . '"' : '') . '
    ORDER BY p.ID ASC', ARRAY_A);

$json = array();

if (!empty($orders_query))
{
    foreach($orders_query as $order_query)
    {
        if (!isset($json[$order_query['p_post_type']], $json[$order_query['p_post_type']][$order_query['p_post_name']]))
            $json[$order_query['p_post_type']][$order_query['p_post_name']] = array(
                'posts' => array(),
                'postmeta' => array(),
                'woocommerce_order_items' => array(),
                'woocommerce_order_itemmeta' => array()
            );

        if (!empty($order_query['p_ID']))
            $json[$order_query['p_post_type']][$order_query['p_post_name']]['posts'][$order_query['p_ID']] = array_filter($order_query, function($k) {
                $is_p = strpos($k, 'p_');
                return $is_p !== FALSE && empty($is_p);
            }, ARRAY_FILTER_USE_KEY);

        if (!empty($order_query['pm_meta_id']))
            $json[$order_query['p_post_type']][$order_query['p_post_name']]['postmeta'][$order_query['pm_meta_id']] = array_filter($order_query, function($k) {
                $is_pm = strpos($k, 'pm_');
                return $is_pm !== FALSE && empty($is_pm);
            }, ARRAY_FILTER_USE_KEY);

        if (!empty($order_query['oi_order_item_id']))
            $json[$order_query['p_post_type']][$order_query['p_post_name']]['woocommerce_order_items'][$order_query['oi_order_item_id']] = array_filter($order_query, function($k) {
                $is_io = strpos($k, 'oi_');
                return $is_io !== FALSE && empty($is_io);
            }, ARRAY_FILTER_USE_KEY);


        if (!empty($order_query['oim_meta_id']))
            $json[$order_query['p_post_type']][$order_query['p_post_name']]['woocommerce_order_itemmeta'][$order_query['oim_meta_id']] = array_filter($order_query, function($k) {
                $is_oim = strpos($k, 'oim_');
                return $is_oim !== FALSE && empty($is_oim);
            }, ARRAY_FILTER_USE_KEY);
    }
}

// Downloading or viewing?
if (!empty($download))
{
    // Outputs json in a textarea for you to copy and paste into a .json file for import...

    if (!empty($json))
    {
        $filename = uniqid('orders_') . '.json';

        $fp = fopen($filename, 'w');
        fwrite($fp, json_encode($json));
        fclose($fp);


        $size   = filesize($root_dir . '/' . $filename);
        header('Content-Description: File Transfer');
        header('Content-Type: application/octet-stream');
        header("Content-Disposition: attachment; filename=\"" . $filename . "\""); 
        header('Content-Transfer-Encoding: binary');
        header('Connection: Keep-Alive');
        header('Expires: 0');
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');
        header('Content-Length: ' . $size);

        readfile($root_dir . '/' . $filename);
    }

}
else
{
    // Outputs json in a textarea for you to copy and paste into a .json file for import...
    if (!empty($json))
        echo '<textarea cols="200" rows="50">', json_encode($json), '</textarea>';
}

创建的JSON文件可能超过500 MB,甚至可能高达1 Gig的数据。所以,我相信PHP在这里耗尽内存,并且需要以某种方式逐位处理,无论是在后台还是完全,而不会达到php内存限制。我相信内存限制设置为1024 MB,这是非常高,但不够高和tbh,对于我正在做的事情,我认为我们没有足够的内存来执行操作。我需要改变处理json和/或下载它的方式。而且我不想创建多个json文件,请只使用1个JSON文件。

6 个答案:

答案 0 :(得分:7)

我认为可能会有几个问题。首先,我建议你做一些分析。

    // HUGE DATABASE DUMP HERE, needs to be converted to JSON, after getting all columns of all tables...
    echo 'Start Time: '. date("Y-m-d H:i:s");
    echo ' Memory Usage: ' . (memory_get_usage()/1048576) . ' MB \n';

    $orders_query = $wpdb->get_results('
        SELECT ' . $select_data . '
        FROM ' . $wpdb->posts . ' AS p
        INNER JOIN ' . $wpdb->postmeta . ' AS pm ON (pm.post_id = p.ID)
        LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_items AS oi ON (oi.order_id = p.ID)
        LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta AS oim ON (oim.order_item_id = oi.order_item_id)
        WHERE p.post_type = "shop_order"' . (!empty($exclude_post_statuses) ? ' AND p.post_status NOT IN ("' . implode('","', $exclude_post_statuses) . '")' : '') . (!empty($start_date) ? ' AND post_date >= "' . $start_date->format('Y-m-d H:i:s') . '"' : '') . (!empty($end_date) ? ' AND post_date <= "' . $end_date->format('Y-m-d H:i:s') . '"' : '') . '
        ORDER BY p.ID ASC', ARRAY_A);

    echo 'End Time: '. date("Y-m-d H:i:s");
    echo ' Memory Usage: ' . (memory_get_usage()/1048576) . ' MB \n';
    die('Finished');

    $json = array();

以上内容将帮助您了解正在使用的内存量。如果它在打印完成之前失败了,我们知道这不是一个json问题。如果脚本运行正常,那么我们可以先创建一个csv文件而不是json。由于您正在运行select查询(此时),因此它不必是您需要的嵌套json文件。只需创建一个CSV文件即可实现平面结构。

$csvFile = uniqid('orders') . '.csv';
$fp = fopen($csvFile, 'w');
if (!empty($orders_query))
{
    $firstRow = true;
    foreach($orders_query as $order_query)
    {
        if(true === $firstRow) {
            $keys = array_keys($order_query);
            fputcsv($fp, $order_query);
            $firstRow = false;
        }

        fputcsv($fp, $order_query);
    }
}
fclose($fp);

如果以上工作正常,至少有一个csv文件可以使用。

此时我不确定您的数据结构嵌套有多复杂。例如,对于&#39; p_post_type&#39;存在多少个不同的值。和&#39; p_post_name&#39;你正拥有的。您可能需要解析csv文件并为每个 [&#39; p_post_type&#39;] [&#39; p_post_name&#39;] [&#39;帖子&#39;]创建多个json文件,[ &#39; p_post_type&#39;] [&#39; p_post_name&#39;] [&#39;发布&#39;],[&#39; p_post_type&#39;] [&#39; p_post_name&#39;] [&#39; woocommerce_order_items&#39;]和[&#39; p_post_type&#39;] [&#39; p_post_name&#39;] [&#39; woocommerce_order_itemmeta&#39;]

如果文件数量很少,您可以编写脚本自动合并或手动执行。如果您有太多嵌套项,可能创建的json文件数量可能很多,可能很难合并它们,可能不是一个可行的选项。

如果json文件的数量很多,我想知道拥有如此庞大的单个json文件的目的是什么。如果导出是一个问题,导入也可能是一个问题,尤其是在内存中摄取如此庞大的json文件。我相信如果创建json文件的目的是以某种形式导入它,在将来的某个阶段,我想你可能不得不看一下只有一个csv文件的选项,你用来过滤掉任何东西在那个时间点需要。

我希望这会有所帮助。

  

进一步更新

我认为 $ wpdb-&gt; get_results 正在使用mysqli_query / mysql_query(取决于您的配置)来获取结果。见wordpress query docs。以这种方式获取数据不是内存有效的方法。我相信你可能会失败( $ wpdb-&gt; get_results )本身。我建议您在不使用 $ wpdb 的情况下运行查询。每当需要大数据检索时,都存在无缓冲查询的概念,这对内存的影响非常小。更多信息可以在mysql unbuffering找到。

即使你已经超越了这一点,你仍然会遇到内存问题,因为你在 $ json 变量中存储所有内容的方式很多你的记忆。 $ json 是一个数组,知道PHP数组如何工作会很有趣。 PHP数组是动态的,并且每次添加新元素时它们都不会分配额外的内存,因为这样会非常慢。相反,它将数组大小增加到2的幂,这意味着每当限制耗尽时,它将数组限制增加到其当前限制的两倍,并且在此过程中尝试将内存增加到限制的两倍。这对PHP 7来说不是一个问题,因为它们已经对php核心做了一些重大改动。因此,如果您有2GB数据可能需要存储在 $ json 中,脚本可能很容易在3-4 GB内存之间分配,具体取决于它何时到达限制。有关详细信息,请参阅php arrayHow does PHP memory actually work

如果您考虑 $ orders_query 的开销,这是一个数组,加上 $ json 的开销由于PHP数组的工作方式,它是相当可观的。

您也可以尝试创建另一个数据库B.因此,当您从数据库A读取数据时,您同时开始将数据写入数据库B.最后,您拥有数据库B,其中包含MySQL的所有数据。您也可以将相同的数据推送到一个MongoDB,它可以快速闪电,并可能帮助您使用json嵌套。 MongoDB旨在高效地处理大型数据集。

  

JSON流媒体解决方案

首先,我想说流式传输是顺序/线性过程。因此,它没有记录在此时间点之前添加的内容或在此时间点之后添加的内容。它适用于小块,这就是它如此高效的内存。所以当你真正写或读时,责任在于脚本,它维护一个特定的顺序,这就是说你正在写/读你自己的json,因为流只能理解文本而且不知道json是什么和在写作/阅读正确的文章时不会感到烦恼。

我在github上发现了一个图书馆https://github.com/skolodyazhnyy/json-stream,这将有助于您实现自己想要的目标。我已经对代码进行了实验,我可以看到它会对你的代码进行一些调整。

我要为你写一些伪代码。

//order is important in this query as streaming would require to maintain a proper order.
$query1 = select distinct p_post_type from ...YOUR QUERY... order by p_post_type;
$result1 = based on $query1; 

$filename = 'data.json';
$fh = fopen($filename, "w");
$writer = new Writer($fh);
$writer->enter(Writer::TYPE_OBJECT);  

foreach($result1 as $fields1) {
    $posttype = $fields1['p_post_type'];
    $writer->enter($posttype, Writer::TYPE_ARRAY); 

    $query2 = select distinct p_post_name from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype order by p_post_type,p_post_name;
    $result2 = based on $query2;

    foreach($result2 as $fields2) {
        $postname = $fields1['p_post_name'];
        $writer->enter($postname, Writer::TYPE_ARRAY); 

        $query3 = select ..YOUR COLUMNS.. from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype and p_post_name=$postname where p_ID is not null order by p_ID;
        $result3 = based on $query3;
        foreach($result2 as $field3) {
            $writer->enter('posts', Writer::TYPE_ARRAY); 
            // write an array item
            $writer->write(null, $field3);
        }
        $writer->leave(); 

        $query4 = select ..YOUR COLUMNS.. from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype and p_post_name=$postname where pm_meta_id is not null order by pm_meta_id;
        $result4 = based on $query4;
        foreach($result4 as $field4) {
            $writer->enter('postmeta', Writer::TYPE_ARRAY); 
            // write an array item
            $writer->write(null, $field4);
        }
       $writer->leave(); 

        $query5 = select ..YOUR COLUMNS.. from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype and p_post_name=$postname where oi_order_item_id is not null order by oi_order_item_id;
        $result5 = based on $query5;
        foreach($result5 as $field5) {
            $writer->enter('woocommerce_order_items', Writer::TYPE_ARRAY); 
            // write an array item
            $writer->write(null, $field5);
        }
        $writer->leave(); 

        $query6 = select ..YOUR COLUMNS.. from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype and p_post_name=$postname where oim_meta_id is not null order by oim_meta_id;
        $result6 = based on $query6;
        foreach($result6 as $field6) {
            $writer->enter('woocommerce_order_itemmeta', Writer::TYPE_ARRAY); 
            // write an array item
            $writer->write(null, $field5);
        }
        $writer->leave(); 

    }
$writer->leave(); 
fclose($fh);

您可能必须开始将查询限制为10个,直到您做对了。因为上面的代码可能不仅仅是按原样运行。您应该能够以类似的方式阅读代码,因为同一个库有一个Reader类可以提供帮助。我测试了读者和作者,他们似乎工作得很好。

答案 1 :(得分:3)

创建文件

您的代码存在的问题是您正在尝试将整个数据集放入内存中,一旦数据库变得足够大,最终将会失败。要克服这个问题,你必须批量获取数据。

我们将多次生成查询,因此我将查询提取到一个函数中。我为了简洁而跳过了传递必需参数(或者将它们设为全局参数),所以你必须自己完成这项工作。

function generate_query($select, $limit = null, $offset = null) {
    $query = 'SELECT ' . $select . '
    FROM ' . $wpdb->posts . ' AS p
    INNER JOIN ' . $wpdb->postmeta . ' AS pm ON (pm.post_id = p.ID)
    LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_items AS oi ON (oi.order_id = p.ID)
    LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta AS oim ON (oim.order_item_id = oi.order_item_id)
    WHERE p.post_type = "shop_order"' . (!empty($exclude_post_statuses) ? ' AND p.post_status NOT IN ("' . implode('","', $exclude_post_statuses) . '")' : '') . (!empty($start_date) ? ' AND post_date >= "' . $start_date->format('Y-m-d H:i:s') . '"' : '') . (!empty($end_date) ? ' AND post_date <= "' . $end_date->format('Y-m-d H:i:s') . '"' : '') . '
    ORDER BY p.ID ASC';

    if ($limit && $offset) {
        $query .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
    }

    return $query;
}

现在我们将批量获取db的结果,我们定义批处理计数,即我们将加载到内存中的每次迭代的记录数。您可以稍后使用此值来查找速度足够快且不会导致PHP崩溃的值。请记住,我们希望尽可能减少数据库查询的数量:

define('BATCH_COUNT', 500);

在创建循环之前,我们需要知道我们将进行多少次迭代(数据库调用),因此我们需要总订单数。有了这个和批次计数,我们可以很容易地计算出这个值:

$orders_count = $wpdb->get_col(generate_query('COUNT(*)'));
$iteration_count = ceil($orders_count / BATCH_COUNT);

因此,我们希望在结果文件中包含一个巨大的JSON字符串。由于每次迭代我们将有一个包含对象数组的单独JSON,我们将简单地从JSON字符串的每一侧剥离[]并将该字符串放入文件中。

最终代码:

define('FILE', 'dump.json');
file_put_contents(FILE, '[');

for ($i = 0; $i < $iteration_count; $i++) {
    $offset = $i * BATCH_COUNT;

    $result = $wpdb->get_results(
        generate_query($select_data, BATCH_COUNT, $offset),
        ARRAY_A
    );

    // do additional work here, add missing arrays etc.
    // ...

    // I assume here the $result is a valid array ready for
    // creating JSON from it
    // we append the result file with partial JSON

    file_put_contents(FILE, trim(json_encode($result), '[]'), FILE_APPEND);
}

file_put_contents(FILE, ']', FILE_APPEND);

恭喜,您刚刚创建了第一个巨大的JSON转储;)您应该在命令行中运行此脚本,以便它可以获得所需的时间,从现在开始无需修改内存限制,因为我们希望永远不会达到极限。

发送文件

使用PHP流式传输大型文件非常简单且has already been answered多次使用。但是我个人不建议你在PHP中花费任何时间,因为它在命令行或文件服务器中都是一个长时间运行的过程。

我假设您使用的是Apache。您应该考虑使用SendFile并让Apache为您付出艰苦的努力。处理大文件时,此方法效率更高。这个方法非常简单,您只需将路径传递给标题中的文件:

header('X-Sendfile: ' . $path_to_the_file);

如果您使用Nginx,也可以获得XSendFile支持。

此方法不使用大量内存,不会阻止PHP进程。该文件也不需要在webroot中访问。我一直使用XSendFile为经过身份验证的用户提供4K视频。

答案 2 :(得分:0)

首先,您应该问自己一个问题:我是否需要自己编写数据库转储?

如果没有,那么您可以简单地使用一些能够为您完成工作的服务。 Mysqldump-php应该能够完成这项工作。

然后你可以简单地说:

include_once(dirname(__FILE__) . '/mysqldump-php-2.0.0/src/Ifsnop/Mysqldump/Mysqldump.php');
$dump = new Ifsnop\Mysqldump\Mysqldump('mysql:host=localhost;dbname=testdb', 'username', 'password');
$dump->start('storage/work/dump.sql');

这应创建.sql文件。但是,您需要json文件。但这不应该是一个问题。该工具将完成剩下的工作:http://www.csvjson.com/sql2json

您还可以在github上找到sql2json的源代码:https://github.com/martindrapeau/csvjson-app

答案 3 :(得分:0)

我相信你可能正在寻找Generators http://php.net/manual/en/language.generators.overview.php https://scotch.io/tutorials/understanding-php-generators

不是创建那个巨大的$json数组,而是迭代每个$order_query并对每次迭代执行操作,而无需将其存储在内存中。

答案 4 :(得分:0)

您的问题是,您从查询中获得大结果集,因为您有3个连接,所以很重要。

您可以定义限制并使用偏移量来获取中的数据,然后输出部分json。主要问题是以某种方式将json数据存储在内存中,然后将其访问为部分输出。

对于后者缓存或者可以使用nosql数据库。我的解决方案将使用缓存,特别是memcache:

class Cache {

    private $cache;

    public function __construct($cache)
    {
        $this->cache = $cache;
    }

    public function addPostName($postName)
    {
        $this->addKeyToJsonObject('postNames', $postName);
    }

    public function addKeyToJsonObject($rootName, $key)
    {
        $childNames = $this->cache->get($rootName);
        if($childNames === false) {
            $this->cache->set($rootName, [$key]);
        }
        else {
            $childNamesList = $childNames;
            // not found
            if(array_search($key, $childNamesList) === false) {
                $childNamesList[] = $key;
                $this->cache->set($rootName, $childNamesList);
            }
        }
    }

    public function getPostNames()
    {
        return $this->cache->get('postNames');
    }
    public function set($key, $value) {
        $this->cache->add($key, $value);
    }

    public function addPostIdsByNameAndType($postName, $type, $pid)
    {
        $this->addKeyToJsonObject($postName . '-' . $type, $pid);
    }

    public function getPostIdsByNameAndType($postName, $type)
    {
        return $this->cache->get($postName . '-' . $type);
    }

    public function addPostValueByNameTypeAndId($postName, $type, $pid, $value)
    {
        $this->cache->set($postName . '-' . $type . '-' . $pid, $value);
    }

    public function getPostValueByNameTypeAndId($postName, $type, $pid)
    {
        return $this->cache->get($postName . '-' . $type . '-' . $pid);
    }
}

然后:

$memcache = new Memcache();
$memcache->connect('127.0.0.1', 11211) or die ("Could not connect");

$memcache->flush();

$cache = new Cache($memcache);

header('Content-disposition: attachment; filename=file.json');
header('Content-type: application/json');
echo '{"shop_order":{';

function getResultSet($wpdb, $offset = 1) {
    return $wpdb->get_results('
    SELECT ' . $select_data . '
FROM ' . $wpdb->posts . ' AS p
INNER JOIN ' . $wpdb->postmeta . ' AS pm ON (pm.post_id = p.ID)
LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_items AS oi ON (oi.order_id = p.ID)
LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta AS oim ON (oim.order_item_id = oi.order_item_id)
WHERE p.post_type = "shop_order"' . (!empty($exclude_post_statuses) ? ' AND p.post_status NOT IN ("' . implode('","', $exclude_post_statuses) . '")' : '') . (!empty($start_date) ? ' AND post_date >= "' . $start_date->format('Y-m-d H:i:s') . '"' : '') . (!empty($end_date) ? ' AND post_date <= "' . $end_date->format('Y-m-d H:i:s') . '"' : '') . '
ORDER BY p.ID ASC LIMIT 1000 OFFSET ' . $offset, ARRAY_A);

}
$offset = 1;

$orders_query = getResultSet($wpdb, 1);
while(!empty($orders_query)) {
    cacheRowData($cache, $orders_query);
    $offset = $offset + 1000;
    $orders_query = getResultSet($wpdb, $offset);
}

outputRowData($cache);

function cacheRowData($cache, $orders_query)
{
    foreach($orders_query as $order_query) {

        if(empty($order_query)) { continue; }
        $cache->addPostName($order_query['p_post_name']);

        // posts
        if (!empty($order_query['p_ID'])) {
            $cache->addPostIdsByNameAndType($order_query['p_post_name'],'posts', $order_query['p_ID']);

            $value = array_filter($order_query, function($k) {
                $is_p = strpos($k, 'p_');
                return $is_p !== FALSE && empty($is_p);
            }, ARRAY_FILTER_USE_KEY);
            $cache->addPostValueByNameTypeAndId($order_query['p_post_name'],'posts', $order_query['p_ID'], $value);
        }
        if (!empty($order_query['pm_meta_id'])) {
        $cache->addPostIdsByNameAndType($order_query['p_post_name'],'postmeta', $order_query['pm_meta_id']);

        $value = array_filter($order_query, function($k) {
            $is_pm = strpos($k, 'pm_');
            return $is_pm !== FALSE && empty($is_pm);
        }, ARRAY_FILTER_USE_KEY);
        $cache->addPostValueByNameTypeAndId($order_query['p_post_name'],'postmeta', $order_query['pm_meta_id'], $value);
    }
        // here do the same for "woocommerce_order_items" and "woocommerce_order_itemmeta"
    }
}

function outputRowData($cache)
{

    $cachedPostNames = $cache->getPostNames();
    $firstRow = true;

    foreach($cachedPostNames as $postName) {

        if(empty($postName)) { continue; }

        if($firstRow === false) {
            echo ',';
        }
        $firstRow = false;

        echo '"' . $postName . '":{';
        $postIds = $cache->getPostIdsByNameAndType($postName, 'posts');
        if(!$postIds) {
            $postIds = [];
        }

        // generate posts
        $postValues = [];
        foreach ($postIds as $postId) {
            $postValues[$postId] = $cache->getPostValueByNameTypeAndId($postName, 'posts', $postId);
        }

        $postMetaIds = $cache->getPostIdsByNameAndType($postName, 'postmeta');
        if(!$postMetaIds) {
            $postMetaIds = [];
        }
        $postMetaValues = [];
        foreach ($postMetaIds as $postMetaId) {
            $postMetaValues[$postMetaId] = $cache->getPostValueByNameTypeAndId($postName, 'postmeta', $postMetaId);
        }
        // here do the same for "woocommerce_order_items" and "woocommerce_order_itemmeta"

        echo '"posts":' . json_encode($postValues) . ',';
        echo '"postmeta":' . json_encode($postMetaValues);
        echo '}';
        ob_flush();
        flush();   // flush the output to start the download
    }
}


echo '}}';

答案 5 :(得分:0)

所以你需要做很多事情才能做到这一点。我会记住我的所有观点。

通过WebServer终止

如果您使用Apache或Nginx / PHP-FPM,默认情况下两者都会被命中的网址超时。即使你已经使用了

ini_set('memory_limit', '-1');
ini_set('max_execution_time', '-1');
set_time_limit(0);

让脚本运行很长时间,但Apache,Nginx,PHP-FPM都有一个超时,不允许你的脚本工作。因此,您需要修复这些以使其正常工作。您从未提及您使用的服务器。但NGINX + PHP-FPM肯定会使用默认配置502。

内存使用

即使您已经使用过

ini_set('memory_limit', '-1');

如果您的内存需求增加,PHP可能会开始使用分页,您的代码可能会变慢。

PHP CLI或PHP Web?

不确定此处的执行频率是多少,但如果它很低,您可以认为您的数据转储脚本是通过PHP-CLI而不是HTTP运行的。这意味着您将直接通过终端运行PHP脚本将JSON转储到文件中,然后使用URL直接下载文件

使用X-Sendfile或X-Accel-Redirect

如果您使用的是apache,可以发送标题

header('X-Sendfile: /data/generated.json');

如果是Nginx,您可以发送

header('X-Accel-Redirect: /data/generated.json');

只有在您决定将脚本作为Web而不是CLI运行时,才会这样做。生成JSON后,您不希望脚本读取文件和服务器。您只需要网络服务器来处理它。

无缓冲查询而不是WPDB查询

https://core.trac.wordpress.org/browser/tags/4.9/src/wp-includes/wp-db.php#L2480

默认情况下,WPDB查询将所有数据提取到内存中。但是你可以使用无缓冲查询自己查询数据库,这不会淹没内存

Example #1 Unbuffered query example: mysqli

<?php
$mysqli  = new mysqli("localhost", "my_user", "my_password", "world");
$uresult = $mysqli->query("SELECT Name FROM City", MYSQLI_USE_RESULT);

if ($uresult) {
   while ($row = $uresult->fetch_assoc()) {
       echo $row['Name'] . PHP_EOL;
   }
}
$uresult->close();
?>
Example #2 Unbuffered query example: pdo_mysql

<?php
$pdo = new PDO("mysql:host=localhost;dbname=world", 'my_user', 'my_pass');
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

$uresult = $pdo->query("SELECT Name FROM City");
if ($uresult) {
   while ($row = $uresult->fetch(PDO::FETCH_ASSOC)) {
       echo $row['Name'] . PHP_EOL;
   }
}
?>
Example #3 Unbuffered query example: mysql

<?php
$conn = mysql_connect("localhost", "my_user", "my_pass");
$db   = mysql_select_db("world");

$uresult = mysql_unbuffered_query("SELECT Name FROM City");
if ($uresult) {
   while ($row = mysql_fetch_assoc($uresult)) {
       echo $row['Name'] . PHP_EOL;
   }
}
?>

https://secure.php.net/manual/en/mysqlinfo.concepts.buffering.php

PS:我现在脑子里可能还有更多的点,很快就会更新