Мне нужно экспортировать данные из mysql в csv. Я должен выбрать данные из нескольких таблиц, помещая их в массивы, затем обрабатывать их и возвращать их в браузер как .csv. Я заметил, что массивы потребляют огромное количество рядов. Например, я импортировал .csv в базу данных, которая составляет 1.8M, затем я пытаюсь экспортировать эти данные из базы данных в .csv. Memory_get_peak_usage () отображает более 128M для хранения массивов с данными.
Например, этот малый массив занимает более 700 байт:
$startMemory = memory_get_usage(); //get constant fields of the subscriber $data = array(array('subscriber_id' => 1315444, 'email_address' => 'test0@gmail.com', 'first_name' => 'Michael', 'last_name' => 'Allen')); echo memory_get_usage() - $startMemory;
Таким образом, экспортируя даже несколько мегабайт данных, требуется сотни мегабайт памяти в php-скрипте. Есть ли способ решить эту проблему? Таблицы:
CREATE TABLE `subscribers` ( `subscriber_id` int(10) unsigned NOT NULL auto_increment, `list_id` int(10) unsigned NOT NULL, `account_id` int(10) unsigned NOT NULL, `email_address` varchar(100) collate utf8_unicode_ci NOT NULL, `first_name` varchar(50) collate utf8_unicode_ci NOT NULL default '', `last_name` varchar(50) collate utf8_unicode_ci NOT NULL default '', `ip` int(10) unsigned default NULL COMMENT '\nThe ip address of the subscriber that we can get when he opens the \nthe email or subscribe using subsribe form.\nTheoretically it can be used to segment by Location (which is not correct if someone uses proxy).', `preferred_format` tinyint(4) NOT NULL default '0' COMMENT 'Preferred format of \n0 - HTML, \n1 -Text,\n2 - Mobile', `state` tinyint(4) NOT NULL default '1' COMMENT '1 - subscribed\n2 - unsubscribed\n3 - cleaned\n4 - not confirmed, it means the user subscribed but has not confirmed it yet.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', `cause_of_cleaning` tinyint(4) NOT NULL default '0' COMMENT '\nThis field is the cause of moving the subscriber to the \n0 - not used\n1 - spam complaint\n2 - hard bounce\n3 - several soft bounces', `date_added` datetime NOT NULL COMMENT 'The data when the subscriber was added. I suppose this field can be used in the conditions forming the segment', `last_changed` datetime NOT NULL, PRIMARY KEY (`subscriber_id`), UNIQUE KEY `email_list_id` (`email_address`,`list_id`), KEY `FK_list_id` (`list_id`), CONSTRAINT `FK_list_id` FOREIGN KEY (`list_id`) REFERENCES `lists` (`list_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB CREATE TABLE `subscribers_multivalued` ( `id` int(10) unsigned NOT NULL auto_increment, `subscriber_id` int(10) unsigned NOT NULL, `field_id` int(10) unsigned NOT NULL, `value` varchar(100) collate utf8_unicode_ci NOT NULL, `account_id` int(10) unsigned NOT NULL COMMENT '\nThe identifier of the account', PRIMARY KEY (`id`), KEY `subscriber_fk` (`subscriber_id`), KEY `field_fk` (`field_id`), CONSTRAINT `field_fk_string_multivalued` FOREIGN KEY (`field_id`) REFERENCES `custom_fields` (`field_id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `subscriber_fk_multivalued` FOREIGN KEY (`subscriber_id`) REFERENCES `subscribers` (`subscriber_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB CREATE TABLE `subscribers_custom_data_string` ( `subscriber_id` int(10) unsigned NOT NULL, `field_id` int(10) unsigned NOT NULL, `value` varchar(255) collate utf8_unicode_ci NOT NULL, `account_id` int(10) unsigned NOT NULL COMMENT '\nThe identifier of the account', PRIMARY KEY (`subscriber_id`,`field_id`), KEY `subscriber_fk` (`subscriber_id`), KEY `field_fk` (`field_id`), CONSTRAINT `field_fk_string` FOREIGN KEY (`field_id`) REFERENCES `custom_fields` (`field_id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `subscriber_fk_string` FOREIGN KEY (`subscriber_id`) REFERENCES `subscribers` (`subscriber_id`) ON DELETE CASCADE ON UPDATE CASCADE )
Существуют и другие таблицы для полей, похожих на таблицу со строками для чисел, дат. Для них первичный ключ – абонент_ид, field_id.
Когда запрос завершается с ошибкой (например, у нас есть несколько настраиваемых полей):
SELECT. email_address
, subscribers
. first_name
, subscribers
. last_name
, GROUP_CONCAT (t1.value SEPARATOR '|') AS Colors, GROUP_CONCAT (t2.value SEPARATOR '|') AS Языки ОТ subscribers
LEFT JOIN subscribers_multivalued
AS t1
ON подписчики.subscriber_id = t1.subscriber_id И t1.field_id = 112 ВЛЕВО subscription_multivalued AS t2
ON подписчики.subscriber_id = t2.subscriber_id И t2.field_id = 111 ГДЕ (list_id = 40) subscribers
GROUP BY. email_address
, subscribers
. first_name
, subscribers
. last_name
Это вернет это:
test1000@gmail.com Мишель Буш Красный | Красный | Синий | Синий Английский | Испанский | Английский | Испанский вместо test1000@gmail.com Мишель Буш Красный | Синий Английский | Испанский
Спасибо за любую информацию.
Используя только две таблицы:
Ваш исходный запрос:
SELECT subscribers.email_address, subscribers.first_name, subscribers.last_name, t1.value AS Languages FROM subscribers LEFT JOIN (SELECT subscriber_id, field_id, GROUP_CONCAT(value SEPARATOR '|') AS value FROM subscribers_multivalued WHERE field_id=37 GROUP BY subscriber_id, field_id ) AS t1 ON subscribers.subscriber_id=t1.subscriber_id AND t1.field_id=37 WHERE (list_id=49) AND (state=1)
дает план объяснения:
id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY subscribers ref FK_list_id FK_list_id 4 const 2 Using where 1 PRIMARY <derived2> ALL NULL NULL NULL NULL 5 2 DERIVED subscribers_multivalued ALL field_fk field_fk 4 11 Using filesort
Мое предложение:
SELECT subscribers.email_address, subscribers.first_name, subscribers.last_name, GROUP_CONCAT(t1.value SEPARATOR '|') AS Languages FROM subscribers LEFT JOIN subscribers_multivalued t1 ON subscribers.subscriber_id=t1.subscriber_id AND t1.field_id=37 WHERE (list_id=49) AND (state=1) GROUP BY subscribers.email_address, subscribers.first_name, subscribers.last_name
дает план объяснения:
id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE subscribers ref FK_list_id FK_list_id 4 const 2 Using where; Using filesort 1 SIMPLE t1 ref subscriber_fk,field_fk subscriber_fk 4 test.subscribers.subscriber_id 1
Хотя я только заполнял эти две таблицы с очень небольшим объемом данных, это говорит о том, что моя версия запроса будет работать более эффективно против базы данных, потому что она не использует производную таблицу, которую генерирует ваш запрос.
Другие таблицы могут быть связаны с запросом почти таким же образом, и весь результат буферизируется непосредственно в файл csv, а не анализируется далее с помощью PHP.
Это должно дать вам возможность работать быстрее и эффективнее.
РЕДАКТИРОВАТЬ
SELECT subscribers.email_address, subscribers.first_name, subscribers.last_name, GROUP_CONCAT(DISTINCT t1.value SEPARATOR '|') AS Colors, GROUP_CONCAT(DISTINCT t2.value SEPARATOR '|') AS Languages FROM subscribers LEFT JOIN subscribers_multivalued AS t1 ON subscribers.subscriber_id=t1.subscriber_id AND t1.field_id=112 LEFT JOIN subscribers_multivalued AS t2 ON subscribers.subscriber_id=t2.subscriber_id AND t2.field_id=37 WHERE (list_id=49) GROUP BY subscribers.email_address, subscribers.first_name, subscribers.last_name
Обратите внимание на использование DISTINCT в функции GROUP_CONCAT ()
Если это возможно по вашей бизнес-логике, вы можете сделать преобразования в mysql и выполнить
SELECT * from table INTO OUTFILE 'file_name.csv'
он имеет те же параметры, что и LOAD DATA INFILE, файл, который вы пишете, не должен существовать.
Рефакторинг вашего кода в единую функцию, которая считывает данные для N- й строки, обрабатывает ее, выводит эту строку без буферизации вывода и отбрасывает все временные данные. Повторно вызовите эту функцию. Это должно сократить использование памяти только для того, что необходимо для данной линии, вместо того, чтобы обрабатывать все линии вместе.
Как только это будет сделано, вы можете расширить его, чтобы читать произвольное количество строк одновременно, чтобы настроить использование памяти и компромисс производительности.
Похоже, что для предотвращения сумеречной группировки можно использовать DISTINCT в совокупной функции GROUP_CONCAT:
SELECT `subscribers`.`email_address`, `subscribers`.`first_name`, `subscribers`.`last_name`, GROUP_CONCAT(DISTINCT t1.value SEPARATOR '|') AS Colors, GROUP_CONCAT(DISTINCT t2.value SEPARATOR '|') AS Languages FROM `subscribers` LEFT JOIN `subscribers_multivalued` AS `t1` ON subscribers.subscriber_id=t1.subscriber_id AND t1.field_id=49 LEFT JOIN `subscribers_multivalued` AS `t2` ON subscribers.subscriber_id=t2.subscriber_id AND t2.field_id=48 WHERE (list_id=63) GROUP BY `subscribers`.`email_address`, `subscribers`.`first_name`, `subscribers`.`last_name`
Таким образом, экспортируя даже несколько мегабайт данных, требуется сотни мегабайт памяти в php-скрипте. Есть ли способ решить эту проблему?
Поскольку другая должность, задающая тот же вопрос, была отмечена как дубликат этого, я дам ответ на эту часть вашего вопроса, не обращая внимания ко всем другим конкретным деталям, которые вы задали.
Чтобы уменьшить объем памяти, который требуется PHP в любое время, вы можете получать строки с сервера MySQL по одному за раз и передавать их браузеру клиента (или некоторому файлу на сервере) без буферизации.
Чтобы извлекать строки по одному, добавьте MYSQLI_USE_RESULT
в качестве параметра MYSQLI_USE_RESULT
в вызов mysqli::query
, чтобы перебирать результаты по одной строке за раз, не передавая их всем PHP в одну группу. См. Также документацию по mysqli::use-result
.
Убедитесь, что вы не используете буферизацию вывода PHP , так что вам не понадобится память для всего документа. Если вы пишете контент в файл на сервере (например, с помощью fwrite
), вы можете позже передать этот файл клиенту, используя readfile
или аналогичный. Вы можете использовать этот файл в качестве кеша, если вам нужно передать один и тот же результат несколько раз.
Если у вас есть привилегия FILE
, вы можете выдать запрос SELECT … INTO OUTFILE …
чтобы сервер MySQL записывал результат непосредственно в некоторый (временный) файл на сервере.
Затем вы можете перенаправить клиента в этот файл, чтобы веб-сервер служил в качестве обычного статического файла, или вы сами можете передать содержимое клиенту с помощью readfile
. Последнее имеет то преимущество, что вы можете удалить файл сразу после его передачи клиенту, в тех случаях, когда вы знаете, что он вам больше не понадобится.