General error: 1615 Prepared statement needs to be re-prepared in

mysql

#1

Salutare!

Primesc mereu eroarea:

[05-Apr-2018 17:33:46 UTC] PHP Fatal error:  Uncaught PDOException: SQLSTATE[HY000]: General error: 1615 Prepared statement needs to be re-prepared in /home/user/public_html/private/Model/DB.php:111
Stack trace:
#0 /home/user/public_html/private/Model/DB.php(111): PDOStatement->execute()
#1 /home/user/public_html/private/Model/DB.php(90): DB::query('update', 'update emag_cat...', Array)
#2 /home/user/public_html/private/Controller/Cron/EMAG_categories.php(140): DB::update('\r\n\t\t\t\t\t\t\tUPDATE...', Array)
#3 /home/user/public_html/private/Model/Dom.php(97): require_once('/home/user/publ...')
#4 /home/user/public_html/index.php(28): Dom::show()
#5 {main}
  thrown in /home/user/public_html/private/Model/DB.php on line 111

Problema am găsit-o pe tot netul, chiar și într-un subiect de tip bug pe forumul mysql, dar o rezolvare clară eu n-am găsit.

Cea mai răspândită soluție este:

ATTR_EMULATE_PREPARES => true

Eu am setat ATTR_EMULATE_PREPARES pe false în momentul de față.

Întrebarea este, ce implică setarea pe false pentru ATTR_EMULATE_PREPARES ? Devin mai vulnerabil ?

Iar dacă devin mai vulnerabil, atunci de ce este soluția asta pe tot netul ?

Nici nu știu unde să mă uit. Primesc eroarea în niște cron-job-uri (mai multe) care rulează aproape într-una. Dar chiar dacă se întâmplă asta, nu văd probleme în cod.

Există altă rezolvare pentru problema mea ?


(Ionuț Staicu) #2

Nu ne arăți și ce ai în zona asta?

thrown in /home/user/public_html/private/Model/DB.php on line 111

Unii zic că e un bug mysql:


#3

La linia 111 am:

$pdo ->execute();

Tot fișierul:

<?php
/* The DB class - Contains a few methods to manipulate the sql database using PDO */
class DB
{
	use Debug;
	
	public static $pdo = null;
	public static $id = 0;
	
	public static $db_connect = null;
	
	# Connect to database #
	public static function connect($DB_host, $DB_name, $DB_user_name, $DB_user_pass)
	{
		self::$pdo = new PDO('mysql:dbname='.$DB_name.';host='.$DB_host.';charset=utf8', $DB_user_name, $DB_user_pass);
		self::$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
		self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		self::$pdo->setAttribute(PDO::ATTR_AUTOCOMMIT,0);
		self::$pdo->exec("SET CHARACTER SET utf8");
		
		self::$db_connect = [
			'db_host'	=> $DB_host,
			'db_name'	=> $DB_name,
			'db_user'	=> $DB_user_name,
			'db_pass'	=> $DB_user_pass,
		];
	}
	
	# Reconnect #
	public static function reconnect()
	{
		if(self::$db_connect)
		{
			self::$pdo = null;
			
			self::connect(
				self::$db_connect['db_host'],
				self::$db_connect['db_name'],
				self::$db_connect['db_user'],
				self::$db_connect['db_pass']
			);
		}
	}
	
	# Start transaction #
	public static function begin()
	{
		self::$pdo->beginTransaction();
	}
	
	# End transaction #
	public static function commit()
	{
		self::$pdo->commit();
	}
	
	# Rollback transaction #
	public static function rollback()
	{
		self::$pdo->rollBack();
	}
	
	# Get last inserted row ID #
	public static function last()
	{
		return self::$pdo->lastInsertId();
	}
	
	# Select #
	public static function select($query, $query_fields = array())
	{
		return DB::query('select', $query, $query_fields);
	}
	
	# Count #
	public static function count($query, $query_fields = array())
	{
		return DB::query('count', $query, $query_fields);
	}
	
	# Insert #
	public static function insert($query, $query_fields = array())
	{
		return DB::query('insert', $query, $query_fields);
	}
	
	# Update #
	public static function update($query, $query_fields = array())
	{
		return DB::query('update', $query, $query_fields);
	}
	
	# Delete #
	public static function delete($query, $query_fields = array())
	{
		return DB::query('delete', $query, $query_fields);
	}
	
	# Query #
	public static function query($query_type, $query, $query_fields)
	{
		$query = preg_replace('/\s+/', ' ', $query);
		$query = trim($query);
		$query = mb_strtolower($query);
		
		$pdo = self::$pdo->prepare($query);
		foreach($query_fields as $field_key => $field_value)
		{
			$pdo->bindValue(($field_key+1), $field_value);
		}
		$pdo ->execute();

		# If select #
		if($query_type == 'select')
		{
			$results = [];
			while($result = $pdo->fetch(PDO::FETCH_ASSOC))
			{
				$results[] = $result;
			}
			return $results;
		}
		
		# Else, if count #
		elseif($query_type == 'count')
		{
			return $pdo->fetchColumn();
		}
		
		# Else, if delete, insert or update #
		else
		{
			self::$id = self::last();
			return $pdo->rowCount();
		}
	}
}


#4

Haideți băieți și cu soluție pentru mine…

Ultimul log de erori:

[06-Apr-2018 01:30:13 UTC] PHP Fatal error:  Uncaught PDOException: SQLSTATE[HY000]: General error: 1615 Prepared statement needs to be re-prepared in /home/user/public_html/private/Model/DB.php:111
Stack trace:
#0 /home/user/public_html/private/Model/DB.php(111): PDOStatement->execute()
#1 /home/user/public_html/private/Model/DB.php(72): DB::query('select', 'select category...', Array)
#2 /home/user/public_html/private/Controller/Cron/NOD_categories.php(126): DB::select('\r\n\t\t\t\t\tSELECT\r\n...', Array)
#3 /home/user/public_html/private/Model/Dom.php(97): require_once('/home/user/publ...')
#4 /home/user/public_html/index.php(28): Dom::show()
#5 {main}
  thrown in /home/user/public_html/private/Model/DB.php on line 111
[06-Apr-2018 01:30:59 UTC] PHP Fatal error:  Uncaught PDOException: SQLSTATE[HY000]: General error: 1615 Prepared statement needs to be re-prepared in /home/user/public_html/private/Model/DB.php:111
Stack trace:
#0 /home/user/public_html/private/Model/DB.php(111): PDOStatement->execute()
#1 /home/user/public_html/private/Model/DB.php(72): DB::query('select', 'select category...', Array)
#2 /home/user/public_html/private/Controller/Cron/EMAG_categories.php(105): DB::select('\r\n\t\t\t\t\t\tSELECT\r...', Array)
#3 /home/user/public_html/private/Model/Dom.php(97): require_once('/home/user/publ...')
#4 /home/user/public_html/index.php(28): Dom::show()
#5 {main}
  thrown in /home/user/public_html/private/Model/DB.php on line 111
[06-Apr-2018 01:40:05 UTC] PHP Fatal error:  Uncaught PDOException: SQLSTATE[HY000]: General error: 1615 Prepared statement needs to be re-prepared in /home/user/public_html/private/Model/DB.php:111
Stack trace:
#0 /home/user/public_html/private/Model/DB.php(111): PDOStatement->execute()
#1 /home/user/public_html/private/Model/DB.php(90): DB::query('update', 'update site_pro...', Array)
#2 /home/user/public_html/private/Controller/Cron/NOD_products_update.php(98): DB::update('\r\n\t\tUPDATE\r\n\t\t\t...', Array)
#3 /home/user/public_html/private/Model/Dom.php(97): require_once('/home/user/publ...')
#4 /home/user/public_html/index.php(28): Dom::show()
#5 {main}
  thrown in /home/user/public_html/private/Model/DB.php on line 111
[06-Apr-2018 05:20:32 UTC] PHP Fatal error:  Uncaught SoapFault exception: [soap:Client] Server was unable to process the request in /home/user/public_html/private/Controller/Cron/ABC_categories.php:33
Stack trace:
#0 /home/user/public_html/private/Controller/Cron/ABC_categories.php(33): SoapClient->__call('GetPriceListHie...', Array)
#1 /home/user/public_html/private/Model/Dom.php(97): require_once('/home/user/publ...')
#2 /home/user/public_html/index.php(28): Dom::show()
#3 {main}
  thrown in /home/user/public_html/private/Controller/Cron/ABC_categories.php on line 33
[06-Apr-2018 05:30:09 UTC] PHP Fatal error:  Uncaught PDOException: SQLSTATE[HY000]: General error: 1615 Prepared statement needs to be re-prepared in /home/user/public_html/private/Model/DB.php:111
Stack trace:
#0 /home/user/public_html/private/Model/DB.php(111): PDOStatement->execute()
#1 /home/user/public_html/private/Model/DB.php(90): DB::query('update', 'update nod_cate...', Array)
#2 /home/user/public_html/private/Controller/Cron/NOD_categories.php(196): DB::update('\r\n\t\t\t\t\t\tUPDATE\r...', Array)
#3 /home/user/public_html/private/Model/Dom.php(97): require_once('/home/user/publ...')
#4 /home/user/public_html/index.php(28): Dom::show()
#5 {main}
  thrown in /home/user/public_html/private/Model/DB.php on line 111
[06-Apr-2018 05:33:42 UTC] PHP Fatal error:  Uncaught PDOException: SQLSTATE[HY000]: General error: 1615 Prepared statement needs to be re-prepared in /home/user/public_html/private/Model/DB.php:111
Stack trace:
#0 /home/user/public_html/private/Model/DB.php(111): PDOStatement->execute()
#1 /home/user/public_html/private/Model/DB.php(72): DB::query('select', 'select category...', Array)
#2 /home/user/public_html/private/Controller/Cron/EMAG_categories.php(105): DB::select('\r\n\t\t\t\t\t\tSELECT\r...', Array)
#3 /home/user/public_html/private/Model/Dom.php(97): require_once('/home/user/publ...')
#4 /home/user/public_html/index.php(28): Dom::show()
#5 {main}
  thrown in /home/user/public_html/private/Model/DB.php on line 111

Un fișier în urma căruia primesc erori (EMAG_categories.php):

<?php
### Check if we can make updates ###
$get_one_updated_data = DB::select("
	SELECT
		category_id
	FROM
		emag_categories
	WHERE
		AddDate(category_last_update, INTERVAL 10 MINUTE) > ?
	LIMIT 1
", [
	DT::get()
]);

### If updates found in last 10 minutes, then exit ###
if(count($get_one_updated_data) == 1)
{
	exit();
}

### Start date ###
$start_date_of_insert_or_update = DT::get();

### Get emag categories ###
EMAG::connect('/category/read');

### If error on API ###
if(EMAG::$is_error)
{
	error_log("Something went wrong. Getting emag categories results failed! Error: ".EMAG::$error_msg);
}
### If no emag result ###
elseif(!EMAG::$results)
{
	error_log("Something went wrong. Getting emag categories failed!");
}
### Else, all seems to be good until now ###
else
{
	# If the results is not an array or is empty #
	if(!is_array(EMAG::$results) || empty(EMAG::$results))
	{
		error_log("Something went wrong. Getting emag categories results failed!");
	}
	# Else, the results is in correct format #
	else
	{
		# For each category #
		foreach(EMAG::$results as $category)
		{
			# Get emag category again #
			EMAG::connect('/category/read', 'GET', ['id' => $category['id']]);
			
			# If error on API #
			if(EMAG::$is_error)
			{
				error_log("Something went wrong. Getting emag category ".$category['id']." results failed! Error: ".EMAG::$error_msg);
			}
			# If no emag result #
			elseif(!EMAG::$results)
			{
				error_log("Something went wrong. Getting emag category ".$category['id']." failed!");
			}
			# Else, all seems to be good until now #
			else
			{
				# If the results is not an array or is empty #
				if(!is_array(EMAG::$results) || count(EMAG::$results) != 1)
				{
					error_log("Something went wrong. Getting emag category ".$category['id']." results is empty or bigger than 1!");
				}
				# Else, the results is in correct format #
				else
				{
					# Category #
					$category = EMAG::$results[0];
					
					# If is_allowed field not set #
					if(!isset($category['is_allowed']) || !Useful::check_int($category['is_allowed']))
					{
						$category['is_allowed'] = 0;
					}
					
					# If parent_id field not set #
					if(!isset($category['parent_id']) || !Useful::check_int($category['parent_id']))
					{
						$category['parent_id'] = 0;
					}
					
					# Sanitize filters #
					$category['characteristics'] = serialize($category['characteristics']);

					# Remove empty spaces from category id #
					$category['id'] = trim($category['id']);
					
					# Check category if is in our database #
					$get_category_if_exists = DB::select("
						SELECT
							category_id
						FROM
							emag_categories
						WHERE
							category_external_id = ?
					", [
						$category['id'],
					]);
					
					# If category found in database more than once #
					if(count($get_category_if_exists) > 1)
					{
						error_log("Something went wrong. Emag category ".$category['id']." is in database more than once!");
					}
					
					# Else, if category found once #
					elseif(count($get_category_if_exists) == 1)
					{
						# Begin transaction #
						DB::begin();
						
						# Update category #
						$update_category = DB::update("
							UPDATE
								emag_categories
							SET
								category_name = ?,
								category_external_parent_id = ?,
								category_is_allowed = ?,
								category_is_ean_mandatory = ?,
								category_filters = ?,
								category_last_update = ?
							WHERE
								category_id = ?
						", [
							$category['name'],
							$category['parent_id'],
							$category['is_allowed'],
							$category['is_ean_mandatory'] ? 1 : 0,
							$category['characteristics'],
							DT::get(),
							$get_category_if_exists[0]['category_id'],
						]);
						
						# If category updated #
						if($update_category == 1)
						{
							DB::commit();
						}
						# Else, if category NOT updated #
						else
						{
							DB::rollback();
							error_log("Can't update Emag category with ID ".$get_category_if_exists[0]['category_id'].".");
						}
					}
					
					# Else, category not found #
					else
					{
						# Begin transaction #
						DB::begin();
						
						# Insert category #
						$insert_category = DB::insert("
							INSERT INTO
								emag_categories
								(
									category_name,
									category_external_id,
									category_external_parent_id,
									category_is_allowed,
									category_is_ean_mandatory,
									category_filters,
									category_last_update
								)
							VALUES
							(
								?, ?, ?, ?, ?, ?, ?
							)
						", [
							$category['name'],
							$category['id'],
							$category['parent_id'],
							$category['is_allowed'],
							$category['is_ean_mandatory'] ? 1 : 0,
							$category['characteristics'],
							DT::get()
						]);
						
						# If category inserted #
						if($insert_category == 1)
						{
							DB::commit();
						}
						# Else, if category NOT inserted #
						else
						{
							DB::rollback();
							error_log("Can't insert emag category in cron.");
						}
					}
				}
			}
		}
	}
}
		
### Count affected emag categories ###
$check_categories = DB::count("
	SELECT
		COUNT(*)
	FROM
		emag_categories
	WHERE
		category_last_update >= ?
", [
	$start_date_of_insert_or_update
]);

### If there was less than 5 categories affected, something went bad ###
if($check_categories < 5)
{
	error_log("Something went very bad on emag categories. Less than 5 categories inserted/updated !");
}
### Else, we can delete categories where update or insert wasn't made - This means that categories were not found anymore in emag marketplace ###
else
{
	# Delete categories #
	DB::begin();
	$deleted = DB::delete("
		DELETE FROM
			emag_categories
		WHERE
			category_last_update < ?
	", [
		$start_date_of_insert_or_update
	]);
	DB::commit();
	
	# Leave a message to admin #
	if($deleted > 0)
	{
		Logs::insert($deleted." emag ".($deleted == 1 ? 'category was' : 'categories were')." deleted from database. This is because you are not allowed anymore to insert products in ".($deleted == 1 ? 'this' : 'those')." categories, or ".($deleted == 1 ? 'it was' : 'were')." removed from Emag Marketplace.");
	}
}

Ce să fie, ce să fie ? :frowning:


#5

Print pe tabel…


(Ionuț Staicu) #7

Eu unul nu am experiență cu PDO, deci eu nu aș putea să-ți dau prea multe idei. Dar, încearcă să izolezi problema în câteva linii de cod sau măcar într-un bloc de cod executabil. Ce ai pus tu aici nu ajută foarte mult, pentru că:

  1. Sunt sute de linii de cod;
  2. Singurul mod în care te-ar putea ajuta cineva ar fi să citească toate aceste linii

Linia problematică de la tine este aici:

      $get_category_if_exists = DB::select("
        SELECT
          category_id
        FROM
          emag_categories
        WHERE
          category_external_id = ?
      ", [
        $category['id'],
      ]);

Dar nu putem afla ce este $category, dacă există $category['id'] șamd. Pune ori un breakpoint ori un var_dump și vezi ce date trimiți spre query. Poate lucrurile nu stau chiar așa cum te aștepți :slight_smile:


#8

Păi, problema e că nu primesc mereu erori. Numai uneori.

Treaba asta se întâmplă numai în cron-uri, iar cron-urile rulează destul de mult. E posibil ca uneori chiar să intre unul peste altul.

Apoi, $category este un șir de elemente cu valori de tip string sau int. Primesc eroarea în mai multe fișiere. Fiecare e diferit în felul lui. În fiecare se extrag date din locuri diferite.

Și da, problema pare să fie la select. Ce problemă e acolo, eu nu-mi dau seama. Și apoi, $category[‘id’] putea să fie orice. E irelevant pentru query. Nu avea motiv să-mi arunce o eroare. Nu din câte știu eu. Și NULL dacă era $category[‘id’] sau array, tot nu cred că-mi genera eroarea aia.

Și repet, nu apar erorile chiar mereu, numai uneori și numai în cron-uri.

Sunt pe host shared și eroarea asta o am și pe alte site-uri din același host. M-am tot gândit că poate fac o greșeală pe care o tot repet… dar nu cred.

Am rămas fără idei :frowning:


(Ionuț Staicu) #9

Faci un try/catch. În catch pui error_log(print_r($category, true));.

Cât timp nu izolezi problema, te vei baza mai mult pe noroc.

Clasa EMAG este disponibilă undeva? Ce face? Scrape la paginile emag? Dacă da, ia în considerare și că serverele lor îți bagă captcha după vreo 30-40 request-uri într-un interval prea scurt.


#10

Clasa emag e făcută de mine. Da, este un scraper, dar acceptat de emag. Funcționează pe bază de username/parolă. Adică e un api modest în care se extrag date cu autentificare înainte.

În sfârșit, am pus blocul de try/catch, dar acum nu-mi mai generează eroare. Mai aștept.

Eu cred totuși că problema e puțin în altă parte.

Am impresia că dacă se umblă într-un tabel (se face update/insert) și în același timp, dintr-un alt script se umblă la același tabel (poate un simplu select), atunci apare eroarea.

Nu sunt sigur, mi-e greu să fac debug pe treaba asta, dar asta cred.

Păi, apare eroarea numai în cron-uri. Și cum se face că cron-urile rulează într-una, și în multe din ele se fac verificări pe aceleași tabele, pentru că na, există o legătură strânsă între tabele.

Am mai spus mai sus. Eu sunt acum pe un host shared, al clientului, și eroarea asta cu “prepared statement needs…” o primesc și în alte site-uri de pe același server.

Deci, sunt 2 variante mari și late. Fie eu fac o greșeală de programare (peste tot), fie e o setare sau o regulă în sql pe care o ignor pentru că n-o știu.

În caz că mai are cineva vreo idee, vă rog să-mi spuneți. Mă mulțumesc și cu idei / sfaturi.


(Ionuț Staicu) #11

Atunci îi las pe ăia mai pricepuți decât mine :blush:

Poți detalia treaba asta? (aici sau în privat) Ți-au dat ei acceptul și acces la un api?


(Cosmin Popescu) #12

Stiu ca poti pune un LOCK[1] pe tabel :slight_smile:

Api-ul simplu pe care l-ai facut nu te influenteaza. Adica Emag-ul sa nu te lase sa faci anumite chestii ?

De ce nu opresti cron job-urile si investighezi manual ?

Iti returneaza ceva daca hardcodezi acel $category['id'] ?

Eu as merge pe tot lantul:

  • scrapper
  • codul prezentat aici
  • etc (daca mai este ceva)

Emag nu are un api care ti-ar pune la dispozitie acele date pe care le obtii facand scarping ?
Desi un SELECT nu este ceva ce modifica un table

Sper ca ceea ce am scris aici te ajuta. Macar putin

BTW: Din link-ul postat de Ionut zice:

They suggest upping the value of table_definition_cache.

  1. https://dev.mysql.com/doc/refman/5.7/en/lock-tables.html

(Georgiana Gligor) #13

Eu as fi incercat cateva idei, insa m-am oprit cand am citit rugamintea specifica de mai jos:


(Eduard-Dan Stanescu) #14

:rofl:

Eu zic ca este tot de la table_definition_cache, incearca sa-i cresti valoarea.
Multi spun ca dupa ce au schimbat host-ul, au scapat de eroarea asta, deci mai mult ca sigur este din config.


(Ionuț Staicu) #15

Rulezi cron în bash sau doar faci wget pe un url? Pe unele (toate? majoritatea?) servere php.ini este unul pentru cli, altul pentru serverul web. Mergând pe ideea asta, s-ar putea să ai unele limite ce ar putea să-ți dea de furcă.


#16

@Cosmin_Popescu

Nu știu cum funcționează LOCK pe tabel, dar oare dacă fac lock, se mai poate face select din acel tabel ? Că dacă nu, atunci nu e bine. Există și o interfață a utilizatorului unde se extrag categoriile din acel tabel. Se văd lucruri. Dar o să mă interesez ce face funcția asta LOCK.

API-ul nu mă influențează. Eu de pe urma API-ului doar primesc date. Mai departe rămâne la aprecierea mea ce fac cu ele. Și bine înțeles că le inserez în baza mea de date.

Manual nu prea primesc eroare. Nici când rulez scripturile din cron nu-mi trimite mereu eroare. Numai uneori.

Da, $category[‘id’] mi-a returnat un număr (INT). Adică exact ce trebuia să returneze.

Emag nu-mi dă rezultatele, dar la sfatul lui Ionuț am zis să pun și acel try/catch. Și array-ul arată exact așa cum ar trebui să arate:

[06-Apr-2018 13:40:20 UTC] Array
(
    [id] => 1492
    [parent_id] => 0
    [characteristics] => a:0:{}
    [family_types] => Array
        (
        )

    [name] => Climatizare & accesorii
    [is_ean_mandatory] => 
    [is_allowed] => 0
)

Da. Am primit eroarea la un select, dar și la update sau insert.

@tekkie

M-am exprimat prost. Asta e…

@iamntz

Wget pe url. Pe toate.

Nu mă limitează php-ul. Link-urile care sunt accesate prin cron citesc același php.ini. Există pe deasupra niște verificări pe care le fac în php, iar dacă nu corespund cu ce e în php.ini, atunci se blochează tot. Eu ajungând până la query-uri, lucrul e clar, se citește php.ini (mă rog, user.ini în cazul meu).

PS:

Acum mă pun să citesc despre table_definition_cache. Am găsit documentație pe mysql.com: https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html

Pare că ar avea legătură. Mai grav e că sunt pe shared, iar clientul are un WHM de unde îmi setează resursele. Oare o merge să schimb table_definition_cache de la el ? Că de la mine nu cred că merge…

Mulțumesc pentru răspunsuri !


(Adrian) #17

Pare un bug in MySQL. Esti cu ultima versiune? Daca nu, update. Daca da, treci la MariaDB.