LINUX.ORG.RU

Mysql ASYNC проблема с количеством коннектов.

 , ,


0

1

Есть проблема парсинга JSON. Есть класс на PHP, который в CLI режиме, через cron парсит JSON файл (выгрузка с другого микросервиса) с записями. Записей в среднем 150к. Задача заносить их в базу Mysql. Суть работы метода: Беру файл, читаю, преобразую через JSON_DECODE в массив, затем перебором по циклу заношу записи в базу. Для каждого запроса Insert создается новый линк в базу (mysqli_connect к базе), затем сначала БЕЗ параметра метода mysqli_query(MYSQLI_ASYNC) записи добавлялись в течении 10 минут. С данным параметром записи добавляются в течении минуты, но плодятся тысячи запросов в секунду, но в методе после INSERT запроса соединение должно разрываться (присутствует mysqli_close($asyncLink), но в некоторых случаях этого не происходит, и выловить почему не могу. В итоге проблема с тем, что количество запросов превышает максимально допустимый в конфиге. Количество оперативной памяти нет смысла увеличивать(пробовал, VPS), т.к. кол-во запросов будет плодиться больше и проблема оттянется на пару дней, но не решится, и придется снова лезть на сервак и тушить mysql сервер, что не гуд. Все должно работать автоматически (файлик упал, сервер увидел, прочитал, занес, удалил). В общем кусок кода:

 private function parseJson()
    {
        $start = microtime(true);
  
        $json = file_get_contents($this->jsonPath);
        $data = json_decode($json, true);

        $i = 0;
        foreach ($data as $entry) {
            foreach ($entry as $good) {
       
                $asyncLink[$i] = new mysqli('localhost', 'root', 'pass');
                $shopx = mysqli_real_escape_string($asyncLink[$i],$good['shop']);
                $goodx = mysqli_real_escape_string($asyncLink[$i],$good['goods']);
                $amoutx = (int)$good['count'];
                $pricex = mysqli_real_escape_string($asyncLink[$i],$good['price']);
                $pricex = (int)$pricex;
                $query = "INSERT INTO goods.goods (shop,good,amout,price) VALUES('$shopx','$goodx','$amoutx','$pricex');";
                $result = mysqli_query($asyncLink[$i], $query, MYSQLI_ASYNC) or die(mysqli_error($asyncLink[$i]));
                mysqli_close($asyncLink[$i]);
                $i++;
            }
        }

Проблема: MySQL Error “Too many connections” (При этом кол-во активных соединений 4 - 4.5к, добавлено 45к+ записей

Версии:

PHP 7.4.3 (cli) (built: Feb 23 2020 07:24:28) ( NTS )

SQL Ver 14.14 Distrib 5.7.29, for Linux (x86_64)

Есть предложения?


Есть предложения?

Да — свали из кодинга.

Я серьёзно:

перебором по циклу заношу записи в базу

Запросы в цикле. Классика. А не, погоди:

foreach ($data as $entry) {
            foreach ($entry as $good) {
Да ты просто бог! Даже классику уделал. На порядок.

Для каждого запроса Insert создается новый линк в базу

Этого не достаточно. Правильнее будет так: для каждого элемента в цикле пыха, устанавливай мускуль, создавай пользака, БД, делай коннект, делай вставку, удаление БД, удаление пользака, дисконнект, останов и удаление мускуля.

плодятся тысячи запросов в секунду

Не стесняйся — докупи серверов, запили реплики и шардинг. Это ж хайлоад! А как ты хотел?

Количество оперативной памяти нет смысла увеличивать

Правильно — 64 килобайта хватит всем.

mysqli_real_escape_string

И конечно, ты на 146% уверен, что заэкранировался. Ну а что — это ж РЕАЛьное экранирование.

deep-purple ★★★★★ ()

Тоже хочу поугадывать о чём так нервничает deep-purple )

Даже классику уделал. На порядок.

Это наверное имеется в виду, что запрос на INSERT внутри цикла, который и сам внутри цикла.

Вообще такие вещи обычно делаются отдельными порциями, которые подбираются не по входным данным (не по размеру массивов), а по оптимальности для конфигурации вашего сервера. Делаете счётчик chunkCount какой-нибудь и складываете в одну строку VALUES столько, сколько вашему серверу наиболее удобно съесть за один раз. Как только chunkCount достигает вашего максимального значения - сбрасываете накопленные значения одним запросом INSERT. Ну, допустим по 1000 записей попробовать для начала. Померить. Поменять.

для каждого элемента в цикле пыха, устанавливай мускуль

Это видимо сарказм. Устанавливать каждый раз соединение на каждый запрос - это действительно выглядит очень странным, если вы конечно не понимаете зачем делать именно так.

64 килобайта хватит всем

Это я не понял. Может хватит, а может и не хватит. Кто ж знает, пока не проверит.

ты на 146% уверен, что заэкранировался

Это наверное имеется в виду, что надо тщательнее проверить - достаточно ли только это экранировать. Если вы уверены, что других спец.символов на входе у вас быть не может - возможно и этого достаточно.

А про MYSQLI_ASYNC - не надо его на INSERT делать вообще. $result or die тут совсем ничего не делает. Асинхронный запрос всегда true будет на стороне клиента.

Как-то так. Наверное.

Toxo2 ★★☆ ()
Ответ на: комментарий от Toxo2

INSERT внутри цикла, который и сам внутри цикла

+

chunkCount

+

И зависит это от того какой максимальный размер тела запроса может быть отправлен клиентом в мускуль и настраивается это в мускуле.

сарказм

+

64 килобайта

Просто к слову пришлось. Полусарказм. В последнее время многим и гигабайтов не хватает.

надо тщательнее проверить

-

Подумай ещё.

MYSQLI_ASYNC тут не нужен

+

И что-то я не увидел твой комментарий насчёт «докупить серверов» — ты думал что тут нет сарказма?

deep-purple ★★★★★ ()
Последнее исправление: deep-purple (всего исправлений: 1)
Ответ на: комментарий от deep-purple

Подумай ещё.

Аж вспотел. Сдаюсь.

Максимум что придумал - я бы, возможно, вместо пустых строк исходных данных вставлял в БД NULL. Только это вроде не про экранирование. Так, для красоты.

Посмотрел у себя в последнем рабочем: там где хочется понадежней - там pg_query_params; там где ужас как хочется руками слепить большую строку из запросов - pg_escape_literal отдельно на каждое значение. Думал, что mysqli_real_escape_string полный аналог в MySQL. Разве нет?

А какой правильный ответ? По секрету. Никому не скажу. На троечку-то угадал же )

Toxo2 ★★☆ ()
Ответ на: комментарий от Toxo2

А какой правильный ответ?

Что mysqli::escape_string можно обойти, используя странные кодировки.

Следует всегда использовать prepared statements.
Одна проблема: с MYSQLI_ASYNC никак.

Darth_Revan ★★★★★ ()
Последнее исправление: Darth_Revan (всего исправлений: 1)

Есть предложения?

Перед mysqli_close($asyncLink[$i]) вставь sleep(3). Получится законченное техническое решение.

no-such-file ★★★★★ ()
Ответ на: комментарий от Darth_Revan

По вашей ссылке:

The following examples are safe:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");

Vulnerables там описаны для каких-то уж очень специфичных выстрелов себе в ногу. Мне так кажется.

Всё-таки хотелось бы услышать ответ deep-purple, если не трудно.

Toxo2 ★★☆ ()

ты поехавший чтоли? коннект надо делать 1 (один), инсерты в цикле анроллить хотя б по 100 штук и использовать транзакции.

Anoxemian ★★★★★ ()
Ответ на: комментарий от Anoxemian

Ага, разом 150к не пролезет, я тоже думал про предварительное разбиение на чанки когда писал

cobold ★★★★ ()
Ответ на: комментарий от Toxo2

хотелось бы услышать ответ deep-purple

Darth_Revan прав. И ссылку нашёл очень показательную.

Vulnerables уж очень специфичных

Главная вульнерабля — не указать вообще никакой чарсет. Что мы и имеем в коде у ТС — он не понимает даже что неправильно использует этот реал эскейп, который требует конкретного чарсета, что явно даже красноватым блоком выделено в документации пыха.

Всё это сравнимо с тем, что используя $_GET['x'] мало кто задумывается о том, что этот «x» может быть и не строкой:

/?x=test
/?x[]=test

Понятно, что при заглушеных ошибках, там не будет даже варнинга, а просто произойдёт конверсия куда надо, причём успешная или нет — зависит от типа данных к которому идёт каст...

Короче, не проверять тип данных на входе — тоже очень плохая практика.

deep-purple ★★★★★ ()
Ответ на: комментарий от dari0n

Лучше прикрою тему

Лучше возьми ноги в руки и бегом исправлять все свои касяки!

deep-purple ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.