За последние 24 часа нас посетили 17936 программистов и 1650 роботов. Сейчас ищут 1780 программистов ...

Из-за большого количества конектов к БД упал сервер. Как уменьшить кол-во запросов.

Тема в разделе "PHP и базы данных", создана пользователем victort, 10 фев 2018.

  1. victort

    victort Активный пользователь

    С нами с:
    8 сен 2010
    Сообщения:
    86
    Симпатии:
    2
    Здравствуйте, столкнулся вот с какой проблемой при работе с PHP7. Из-за большого кол-ва подключений к БД упал сервер - хотя это было всего треть из необходимого. Мне нужно было из csv файла залить в БД данные с админки. Разбор csv файла на строки для таблицы я сделал но когда стал прогонять запись в БД сервер упал. Причем упал вот из-за этого кода:
    Код (Text):
    1. function metki_insert ($profesia,$metki) {
    2. $link_db=con_db(); // коннект с БД
    3.  
    4.     $q_m = "SELECT `id`, `metki` FROM `professia` WHERE `name`='".$profesia."' OR `nami`='".$profesia."'"; //print $q_m; // берем id из БД для того чтобы знать куда записывать
    5.     $result_m=mysqli_query($link_db,$q_m); // выполняем запрос
    6.     $res_m = mysqli_fetch_all($result_m, MYSQLI_ASSOC); // получаем массив
    7.     $ro_m=count($res_m); // кол-во данных в массиве
    8.     if ($ro_m!=0){ // если не пустой
    9.     $id_pm = $res_m[0]['id']; // id
    10.     $pm = $res_m[0]['metki']; // записанная метка в поле
    11.  
    12.     $s1=explode(", ", $metki);
    13.     $s2=explode(", ", $pm); // преобразуем строки в массив
    14.     $rs1=array_diff($s1,$s2); // сравниваем два массива
    15.     if (count($rs1)!=0) { // если есть отличие
    16.         $srs=implode(",", $rs1); // преобразуем в строку
    17.         if ($pm!=="") { $pm=$pm.' ,'.$srs; }
    18.     if ($pm!==="") { $pm=$srs; }
    19. // добавляем к уже имеющимя меткам новые метки. Не всегда корректо работае else поэтому через if
    20.     $qz="UPDATE `professia` SET `metki`='".$pm."' WHERE `id`='".$id_pm."' LIMIT 1"; // запрос на обновление записи
    21.     mysqli_query($link_db,$qz); // выполнить запрос
    22.     } }
    23.     if ($ro_m==0){ // не нашлась запись
    24.     $q_mz ="INSERT INTO `professia` ( `id` , `name` , `nami` , `metki`) VALUES ('', '$profesia', '', '$metki')"; // запрос на создание записи
    25.     mysqli_query($link_db,$q_mz); // выполнить запрос
    26.     }
    27. mysqli_close($link_db); // закрыть соединение
    28. };
    Перед тем как выполнить данную функцию я проверяю а переданны ли метки если да то выполнить функцию:
    Код (Text):
    1.     $metki=trim($metki); // убираем лишние пробелы
    2.     $metki=str_replace('  ',' ',$metki); // проверяю на двойные пробелы внутри записи
    3.     if ($metki!='' && $metki!=' ') { metki_insert ($profesia,$metki); } // если значение не пустое и не пробел то выполнить функцию.
    В итоге я проверял тестовый файл с кол-вом записей равное 490 строк. Внутри файла 19 столбцов из них 7 столбцов получают id c таблиц БД для того чтоб не дублировались значения и места меньше занимали.
    Может чтоб уменьшить кол-во обращение к БД сделать потоковую обработку запросов на запись. Скажем создать листинг запросов (массив sql-запросов). Я так понимаю это вложенные запросы друг в дружку сделать тогда запросов а точнее конектов к БД можно уменьшить. В 4 версии все было намного проще один раз подключился выполнил что хотел и отключил БД а тут (в PHP7) постоянный коннект с каждым запросом получается.
     
  2. Алекс8

    Алекс8 Активный пользователь

    С нами с:
    18 май 2017
    Сообщения:
    1.730
    Симпатии:
    359
    зачем?? сделайте сингтон и в рамках жизни скрипта пользуйтесь одним подключением к БД.... 490 строк не много.. 490 подключений может быть и не мало))
     
  3. victort

    victort Активный пользователь

    С нами с:
    8 сен 2010
    Сообщения:
    86
    Симпатии:
    2
    Алекс8 а подскажи пожалуйста как - выложи любой пример. У меня же из-за вот этой команды
    Код (Text):
    1. mysqli_query($link_db,$q_m);
    такое большое кол-во подключений получается
     
  4. Алекс8

    Алекс8 Активный пользователь

    С нами с:
    18 май 2017
    Сообщения:
    1.730
    Симпатии:
    359
    не.. у Вас не изза этой команду.. тут Вы просто запрос отправляете на сервер..
    соединение у Вас вот тут инициируется $link_db=con_db(); // коннект с БД
    если функция function metki_insert запускается многократно и потом не закрывается соединение то вполне может быть что вот тут и получается много соединений с БД.. каждый раз новое, в то время когда старое еще висит..
    выложите тут листинг функции con_db .. посмотрим)
     
  5. victort

    victort Активный пользователь

    С нами с:
    8 сен 2010
    Сообщения:
    86
    Симпатии:
    2
    Алекс8 соединение с БД у меня в начале функции и в конце функции стоит mysqli_close($link_db); Но сервер остановился при вызове данной функции. Когда позвонил в техподдержку хостинга мне сказали что у меня много запросов к БД.
     
  6. Алекс8

    Алекс8 Активный пользователь

    С нами с:
    18 май 2017
    Сообщения:
    1.730
    Симпатии:
    359
    если у Вас эта функция где то вызывается в цикле, тогда надо продумать как объединять много запросов в один.. к примеру инсертить одной строкой...
    только эта функция мало о чем говорит..
     
  7. victort

    victort Активный пользователь

    С нами с:
    8 сен 2010
    Сообщения:
    86
    Симпатии:
    2
    Ну я в начале темы это и написал. Что может пытаться создать массив на запросы а выполнить его потом методом склейки
    JOIN или можно попытаться написать вида SELECT; SELECT; SELECT;...- что посоветуете. А данная функция вызывается в цикле при прохождение по полученным данным из файла. Причем я ее вызов тоже вверху прописал в итоге там вызовов этой функции так как не во всех столбцах есть значение около 100 наберется.
    --- Добавлено ---
    Я нашел что в mysql можно использовать функции IN() хотя возникает вопрос а сколько запросов можно отправить сразу. Как узнать максимальное кол-во запросов за один раз?
     
  8. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.797
    Симпатии:
    1.331
    Адрес:
    Лень
    например?
     
  9. victort

    victort Активный пользователь

    С нами с:
    8 сен 2010
    Сообщения:
    86
    Симпатии:
    2
    MouseZver я у вас совета и прошу как лучше это сделать чтоб получился запрос в БД вида SELECT; SELECT; SELECT;... Просто почитал заодно что есть в интернете. Но естественно у меня возникает вопрос а какое кол-во я таких запросов могу отправить сразу. Как это узнать - чтоб опять сервер не упал. Или может вообще GROUP BY использовать. Понимаю что в таком случае надо будет использовать AS для присвоения результата из БД к тем 7 параметрам что берут значение из БД и для запроса вида UPDATE. В общем есть с чем эксперементировать но хотелось бы обезопасить себя объемом отправленных данных или кол-вом за один раз на обработку. Мне ведь еще надо будет отправлять запросы INSERT и UPDATE. А искал я в гугле по запросу вложенные запросы в mysql.
     
  10. ADSoft

    ADSoft Старожил

    С нами с:
    12 мар 2007
    Сообщения:
    3.861
    Симпатии:
    751
    Адрес:
    Татарстан
    1. Проблема не в php7 и вообще не в php - а в жутком коде
    2. Было бы гораздо правильнее и логичнее вынести все метки в отдельную таблицу, а так же создать кросс таблицу для соответствия меток и профессий. Тогда весь процесс добавления меток уместился бы один SQL запрос типа INSERT INTO ..... ON IGNORE
    3. правильно сказали - ненужно 100500 раз открывать и закрывать соединение БД, в начале скрипта открыли - в конце закрыли - все...
     
  11. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.797
    Симпатии:
    1.331
    Адрес:
    Лень
    подожди
    --- Добавлено ---
    @victort выпиши сюда sql строки ПОЛНЫЕ
    --- Добавлено ---
    а не SELECT; SELECT; SELECT;...
     
  12. victort

    victort Активный пользователь

    С нами с:
    8 сен 2010
    Сообщения:
    86
    Симпатии:
    2
    ADSoft метки и профессии итак вынесены в отдельную таблицу. Каждая метка относится к конкретной профессии. Поэтому и идет запрос в БД с передачей профессии после чего происходит проверка а есть ли такая метка у профессии если какой-то метки нет то создается новый список меток с добавлением в него несуществующих в нем меток. После чего новый список меток добавляется в таблицу.
    MouseZver - задача такая дается csv файл с 19 столбцами которые записываются в таблицу vakansii. Перед тем как сделать записать идет запрос в БД по профессиям если есть профессия то берется ее id если нету то создается запись в таблицу профессия с новой профессией. После чего идет запрос в БД чтоб взять id. Так как есть вероятность что в какой-то момент может так получиться что id не будет следующим за последним. А именно может оказаться что в профессию вкралась ошибка и чтоб небыло дублежа ее удалили руками отдав ее вакансии в правильную строку с профессией. Вот код проверки:
    Код (Text):
    1. $pro=proverit ($profesia, "professia");
    - первый параметр в функции переменная второй имя таблицы. Вот сама функция:
    Код (Text):
    1. function proverit ($pr, $tab){
    2.     $link_db=con_db();
    3.         $q_pro = "SELECT * FROM `".$tab."` WHERE `name`='".$pr."'";
    4.         if ($tab=="professia") {$q_pro.=" OR `nami`='".$pr."'";}
    5.         $res_pro = mysqli_query($link_db,$q_pro);
    6.         $res_pro_pl = mysqli_fetch_all($res_pro, MYSQLI_ASSOC);
    7.         $ro=count($res_pro_pl);
    8.         $id=0;
    9.         if ($ro>0) {
    10.         $id=$res_pro_pl[0]['id']; //$id = mysql_result($res, 0, 'id');
    11.         if ($tab=="new_professia") { $nm=$res_pro_pl[0]['nami']; //$nm = mysql_result($res, 0, 'nami');
    12.             if ($pr=="$nm") { $id=$id+0.1; }
    13.             }
    14.         }
    15.     mysqli_close($link_db);
    16.     return $id;
    17. };
    из данной функции видно что у профессии есть дополнительное поле nami оно отвечает за написание профессии во множественном числе. Если значение равно во множественном числе то возвращается id с плавающей точкой чтоб показать при отображение записи из основной таблицы что надо брать значение из второго поля. По такому же принципу проверяется поле рубрики куда попадает вакансия
    Код (Text):
    1. $rub=proverit ($rubrika, "rubriki");
    и столбец параметры
    Код (Text):
    1. $par=proverit ($parametri, "parametri");
    если значения в данных столбцах отличаются от записи в файле а это означает что id=0 то в этом случае идет запрос в эти таблицы по получению списка и выдачи на экран чтобы пользователь выбрал из уже имеющего списка правильное значение это осуществляется функцией
    Код (Text):
    1. select_vibor("rubriki",$n);
    Вот сама функция:
    Код (Text):
    1. function select_vibor ($tab,$i){
    2.  
    3.     $link_db=con_db();
    4.         $q_vib = "SELECT * FROM `".$tab."` ORDER BY `name` ASC";
    5.         $res_vib = mysqli_query($link_db,$q_vib); //$res = mysql_query($q);
    6.         $res_vib_pl = mysqli_fetch_all($res_vib, MYSQLI_ASSOC);
    7.         $ro_vib=count ($res_vib_pl); //$ro_vib=mysql_num_rows($res_vib_pl);
    8.     $spp='<select size="1" name="'.$tab.'['.$i.']">';
    9.     for($n=0; $n<$ro_vib; $n++){
    10.         $id=$res_vib_pl[$n]['id']; //$id = mysql_result($res, $n, 'id');
    11.         $nam=$res_vib_pl[$n]['name']; //$nam = mysql_result($res, $n, 'name');
    12.     if ($tab=='professia') { $nami=$res_vib_pl[$n]['nami']; /*$nami = mysql_result($res, $n, 'nami');*/ }
    13.     $spp.='<option value="'.$id.'">'.$nam.'</option>';
    14.     if ($tab=='professia' && $nami!='') { $spp.='<option value="'.$id.'">'.$nami.'</option>'; }
    15.         }
    16.     $spp.='</select>';
    17.     mysqli_close($link_db);
    18.     return conv ($spp);
    19. };
    Ну кстати этот же список предоставляется и при проходе через переменную профессия. Но я добавляю чекбокс чтоб пользователь мог указать что профессия новая а значит ее надо добавить в таблицу профессий. Остальные переменные проходят через разные команды не связанные с обращением к БД на проверку записей (такие как trim) Это первая стадия обработки файла на экран пользователя выйдут сообщения об неточностях в конкретных строках. После того как пользователь указал все что необходимо он нажимает на кнопку продолжить загрузку.
    В первой строке происходит запись новых профессий и получение их id:
    Код (Text):
    1. $_POST[new_professia][$i]=insert_danie_prof($_POST[new_professiaN][$i]
    Вот сама функция:
    Код (Text):
    1. function insert_danie_prof ($dan){
    2. $link_db=con_db();
    3.  
    4.     $dan = htmlspecialchars($dan, ENT_QUOTES);
    5.         $q = "SELECT * FROM `professia` WHERE `name`='".$dan."' OR `nami`='".$dan."'"; //print $q;
    6.                 $result_p=mysqli_query($link_db,$q) ?? '';
    7.         $ro=mysqli_num_rows($result_p);
    8.  
    9.     if ($ro==0){
    10.     $q_z ="INSERT INTO `new_professia` ( `id` , `name` , `nami` , `metki`) VALUES ('', '$dan', '', '')";
    11.         $q_p = "SELECT * FROM `new_professia` WHERE `name`='".$dan."' OR `nami`='".$dan."'"; //print $q_p;  
    12.         $result_p=mysqli_query($link_db,$q_z);
    13.  
    14.         $result_pp=mysqli_query($link_db,$q_p);
    15.         $res_pp = mysqli_fetch_all($result_pp, MYSQLI_ASSOC);
    16.         $ro_pp=count($res);
    17.  
    18.         for ($i=0; $i<$ro_pp; $i++) {
    19.         $id = $res_pp[$i]['id'];
    20.         } };//if
    21. mysqli_close($link_db);
    22.     return $id;
    23. };
    Ну и происходит сама запись всех столбцов в таблицу:
    Код (Text):
    1. $q="INSERT INTO `vakansii_2017` ( `id` , `id_rub` , `id_prof` , `id_pred` , `id_param`, `graf_rab` , `op_rab`, `zp`, `zp_c`, `treb`, `usl`, `id_raion`, `id_obrz`, `inf`, `adr`, `tel`, `e-mail`, `sait`, `socseti`, `flag`, `data_p`, `time_p`, `flag_v`, `data_okn_v`, `time_okn_v`, `data_okn_p`, `time_okn_p`, `count`, `tek_count`)
    2. VALUES ('', '".$_POST[new_rubriki][$i]."', '".$_POST[new_professia][$i]."', '".$_POST[predpriatie][$i]."', '".$_POST[new_parametr][$i]."', '".$_POST[grafik][$i]."', '".$_POST[opit][$i]."', '".$_POST[zp][$i]."', '".$_POST[zpc][$i]."', '".$_POST[treb][$i]."','".$_POST[usl][$i]."','".$_POST[raion][$i]."','".$_POST[obraz][$i]."','".$_POST[inf][$i]."','".$_POST[adr][$i]."','".$_POST[tel][$i]."','".$_POST[email][$i]."','".$_POST[sait][$i]."','".$_POST[socseti][$i]."', '2', '".$a_dat[0]."', '".$a_dat[1]."', '', '', '', '', '', '', '')";
    3. print $q; print '</br>';
    4.  
    5.  
    6. //$p=mysqli_query($link_db,$q) ? $z1 : $z2; // записываем в БД
    Как видно из кода отправка в БД запроса закоментирована - просто перед тем как ее раскоментировать я хотел убедиться что все строки созданы и можно отправлять на запись но до этого не дошло сервер упал.
    Ну и тот кто посмотрит файл увидит вверху файла проверку пользователя.
    Может я и перемудрил с код :( . Но надеюсь я смог понятно описать что я пытался сделать и для чего это нужно. Кстати в файле закоментированы команды mysql все по той же причине что я перебираюсь с 4 версии в 7. И как только все отлажу тогда можно будет и удалять. Так что извините за мусор.
    И еще я как говориться не успел но было такое пожелание чтоб перед записью я проверял а есть ли такое объявление в БД если есть то не записывать. В файле вы увидете функцию которая удаляет все вакансии из таблицы. Так вот я думал сделать функцию которая будет проверять если есть идентичная запись то менять у нее флаг 2 ну скажем на 7 из массива на запись данную строчку удалять а потом запустить функцию очищения таблицы и сделать запись после чего все флаги с 7 опять поменять на 2.
     

    Вложения:

    • export2.txt
      Размер файла:
      16,2 КБ
      Просмотров:
      0
    #12 victort, 10 фев 2018
    Последнее редактирование: 10 фев 2018
  13. victort

    victort Активный пользователь

    С нами с:
    8 сен 2010
    Сообщения:
    86
    Симпатии:
    2
    Блин меня осенило или я не прав. Можно ведь сделать одно подключение вначале файла и потом этот параметр передавать в функцию как переменную а закрыть коннект в конце файла. Тогда будет одно подключение к БД.
     
  14. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.797
    Симпатии:
    1.331
    Адрес:
    Лень
    это одна из проблем была решена, теперь нужно оптимизировать запросы.
    >
    записываем новые профессии, не создавая дубликаты
    Код (Text):
    1. INSERT IGNORE INTO `prof_table` ( `column1`, `column2` ) VALUES ( 'инженер', 555 ), ( 'водитель', 555 ), ( 'механик', 555 ), ( 'армировщик', 555 ), ( 'электрик', 555 )
    PHP:
    1. <?php # php version 7.2
    2.  
    3. error_reporting ( E_ALL );
    4.  
    5. # из csv данные
    6. $arr = [
    7.    [ 'инженер', 555 ],
    8.    [ 'водитель', 555 ],
    9.    [ 'механик', 555 ],
    10.    [ 'армировщик', 555 ],
    11.    [ 'электрик', 555 ],
    12. ];
    13.  
    14. $new = [];
    15.  
    16. foreach ( $arr AS $item )
    17. {
    18.    $new[] = sprintf ( '( \'%s\', %d )', ...$item );
    19. }
    20.  
    21. unset ( $arr, $item );
    22.  
    23. echo sprintf ( 'INSERT IGNORE INTO `prof_table` ( `column1`, `column2` ) VALUES %s', implode ( ', ', $new ) );
     
    victort нравится это.
  15. victort

    victort Активный пользователь

    С нами с:
    8 сен 2010
    Сообщения:
    86
    Симпатии:
    2
    MouseZver перед тем как использовать твой код решил потестить его через phpMyAdmin
    Код (Text):
    1. INSERT IGNORE INTO `professia` ( `name`, `nami`, `metki` ) VALUES ( 'Инженер', 'Инженеры' ,'' )
    Данная запись в таблице есть но он всеравно ее добавил в конец таблицы. Разъясни пожалуйста где ошибка.
     
  16. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.797
    Симпатии:
    1.331
    Адрес:
    Лень
    поле профессии должно быть уникальным
    --- Добавлено ---
    ALTER TABLE `test`.`table` ADD UNIQUE `r` (`r`);
     
    #16 MouseZver, 11 фев 2018
    Последнее редактирование: 11 фев 2018
  17. victort

    victort Активный пользователь

    С нами с:
    8 сен 2010
    Сообщения:
    86
    Симпатии:
    2
    MouseZver сделал - тест пройден. Объявил поле `name` уникальным.
     
    MouseZver нравится это.
  18. victort

    victort Активный пользователь

    С нами с:
    8 сен 2010
    Сообщения:
    86
    Симпатии:
    2
    MouseZver данный метод не подойдет из за того что в данный массив попадают вот какие значения:
    Код (Text):
    1. $arr = [
    2.    [ 'инженер', 555 ],
    3.    [ 'инженеры', 555 ],
    4.    [ 'водитель', 555 ],
    5.    [ 'механик', 555 ],
    6.    [ 'механики', 555 ],
    7.    [ 'армировщик', 555 ],
    8.    [ 'электрик', 555 ],
    9. ];
    то есть не только в единственном числе но и во множественном. Извини что сразу не написал об этом. Поэтому я и использую запрос SELECT * FROM `professia` WHERE `name`='".$dan."' OR `nami`='".$dan."'"; Да в таком подходе есть минус что множественное значение запишется в поле предназначенное для единственного числа. Но это можно исправить. Указав при загрузке а точнее при прохождении проверки на ошибки чтоб это значение было записано во второе поле а в первое поле вписать как будет в единственном числе.
     
  19. marsik

    marsik Активный пользователь

    С нами с:
    30 дек 2008
    Сообщения:
    246
    Симпатии:
    17
    А что лог мускуля говорит?
    Наверняка `name` и `nami` без индексов а ресурсы ограниченные
     
  20. victort

    victort Активный пользователь

    С нами с:
    8 сен 2010
    Сообщения:
    86
    Симпатии:
    2
    То что не правильно я пользовался подключением к БД. А дойдя до этого кода просто ресурсы у сервера иссякли. Теперь я это исправил. В таблице есть индексное поле 'id' называется, а поле 'name' еще и как уникальное прописано. Мне MouseZver предложил как оптимизировать запись но к сожалению в моем случае без первичного запроса это сделать не получиться. Надо вначале узнать есть ли такое значение в поле 'name' или 'nami' из поданного массива а потом уже делать запись если нету или взять 'id' раз есть.