MSIN-4482 — Implement batched deletion functions to optimize database operations and prevent MySQL locking issues during imports. Update version code to 4.72.958 to reflect these changes.
This commit is contained in:
@ -2857,15 +2857,98 @@ function fxImportResultatsResetPreview($intEveId) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4482 — DELETE par lots (évite monolithe qui verrouille MySQL / met le site down).
|
||||
* @param string $strSqlBase SQL sans LIMIT, ex. "DELETE FROM t WHERE …"
|
||||
* @param int $intChunk taille du lot
|
||||
* @param int $intPauseUs microsecondes entre lots
|
||||
* @return int lignes affectées cumulées
|
||||
*/
|
||||
function fxImportResultatsDeleteBatchedLimit($strSqlBase, $intChunk = 250, $intPauseUs = 40000) {
|
||||
global $objDatabase;
|
||||
|
||||
$intChunk = max(50, intval($intChunk));
|
||||
$intPauseUs = max(0, intval($intPauseUs));
|
||||
$intTotal = 0;
|
||||
$intGuard = 0;
|
||||
$intMaxLoops = 20000; // filet anti-boucle
|
||||
|
||||
do {
|
||||
@set_time_limit(120);
|
||||
$sql = rtrim((string)$strSqlBase) . ' LIMIT ' . $intChunk;
|
||||
if ($objDatabase->fxQuery($sql) === false) {
|
||||
break;
|
||||
}
|
||||
$intAff = intval($objDatabase->affected_rows);
|
||||
$intTotal += $intAff;
|
||||
$intGuard++;
|
||||
if ($intAff > 0 && $intPauseUs > 0) {
|
||||
usleep($intPauseUs);
|
||||
}
|
||||
} while ($intAff > 0 && $intGuard < $intMaxLoops);
|
||||
|
||||
return $intTotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4482 — DELETE … WHERE col IN (ids) par chunks PHP.
|
||||
* @param int[] $arrIds
|
||||
* @param string $strExtraWhere clause AND additionnelle déjà sécurisée (ex. "eve_id = 12")
|
||||
*/
|
||||
function fxImportResultatsDeleteBatchedIn($strTable, $strCol, $arrIds, $intChunk = 200, $intPauseUs = 40000, $strExtraWhere = '') {
|
||||
global $objDatabase;
|
||||
|
||||
$strTable = preg_replace('/[^a-z0-9_]/i', '', (string)$strTable);
|
||||
$strCol = preg_replace('/[^a-z0-9_]/i', '', (string)$strCol);
|
||||
if ($strTable === '' || $strCol === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$arrIds = array_values(array_unique(array_filter(array_map('intval', (array)$arrIds), function ($n) {
|
||||
return $n > 0;
|
||||
})));
|
||||
if (count($arrIds) < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$intChunk = max(50, intval($intChunk));
|
||||
$intPauseUs = max(0, intval($intPauseUs));
|
||||
$strExtraWhere = trim((string)$strExtraWhere);
|
||||
$intTotal = 0;
|
||||
$intN = count($arrIds);
|
||||
|
||||
for ($i = 0; $i < $intN; $i += $intChunk) {
|
||||
@set_time_limit(120);
|
||||
$arrChunk = array_slice($arrIds, $i, $intChunk);
|
||||
if (count($arrChunk) < 1) {
|
||||
continue;
|
||||
}
|
||||
$sql = 'DELETE FROM ' . $strTable
|
||||
. ' WHERE ' . $strCol . ' IN (' . implode(',', $arrChunk) . ')';
|
||||
if ($strExtraWhere !== '') {
|
||||
$sql .= ' AND (' . $strExtraWhere . ')';
|
||||
}
|
||||
if ($objDatabase->fxQuery($sql) !== false) {
|
||||
$intTotal += intval($objDatabase->affected_rows);
|
||||
}
|
||||
if ($intPauseUs > 0 && ($i + $intChunk) < $intN) {
|
||||
usleep($intPauseUs);
|
||||
}
|
||||
}
|
||||
|
||||
return $intTotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4482 — efface UNIQUEMENT les données d'import (par_origine=import_fichier) de l'événement.
|
||||
* Ne touche pas aux inscriptions en ligne / panier réel / inscriptions_questions.
|
||||
* Exige confirm = "RESET".
|
||||
* Soft sur le serveur : lots + micro-pauses (pas un DELETE géant qui couche MySQL).
|
||||
*/
|
||||
function fxImportResultatsReset($intEveId, $strConfirm) {
|
||||
global $objDatabase;
|
||||
|
||||
@set_time_limit(300);
|
||||
@set_time_limit(600);
|
||||
|
||||
$intEveId = intval($intEveId);
|
||||
if ($intEveId <= 0) {
|
||||
@ -2878,11 +2961,16 @@ function fxImportResultatsReset($intEveId, $strConfirm) {
|
||||
);
|
||||
}
|
||||
|
||||
$arrPrev = fxImportResultatsResetPreview($intEveId);
|
||||
if (($arrPrev['state'] ?? '') !== 'ok') {
|
||||
return $arrPrev;
|
||||
}
|
||||
if (intval($arrPrev['participants'] ?? 0) < 1) {
|
||||
$strOrig = fxImportResultatsDbEsc(MSIN_IMPORT_RESULTATS_ORIGIN);
|
||||
|
||||
// Comptage léger seulement (pas le preview multi-JOIN avant le gros travail)
|
||||
$tabCnt = $objDatabase->fxGetResults(
|
||||
"SELECT COUNT(*) AS n FROM resultats_participants"
|
||||
. " WHERE eve_id = " . $intEveId
|
||||
. " AND par_origine = '" . $strOrig . "'"
|
||||
);
|
||||
$intParCnt = is_array($tabCnt) ? intval($tabCnt[1]['n'] ?? 0) : 0;
|
||||
if ($intParCnt < 1) {
|
||||
return array(
|
||||
'state' => 'ok',
|
||||
'message' => 'Rien à effacer — aucun participant import_fichier sur cet événement.',
|
||||
@ -2892,68 +2980,70 @@ function fxImportResultatsReset($intEveId, $strConfirm) {
|
||||
);
|
||||
}
|
||||
|
||||
$strOrig = fxImportResultatsDbEsc(MSIN_IMPORT_RESULTATS_ORIGIN);
|
||||
$intDelQue = 0;
|
||||
$intDelPar = 0;
|
||||
$intDelPec = 0;
|
||||
|
||||
// Sous-requête IDs participants import (évite JOIN DELETE lents / gros IN PHP)
|
||||
$strSubPar = "SELECT pid FROM ("
|
||||
. " SELECT par_id AS pid FROM resultats_participants"
|
||||
. " WHERE eve_id = " . $intEveId . " AND par_origine = '" . $strOrig . "'"
|
||||
. " UNION"
|
||||
. " SELECT par_id_original AS pid FROM resultats_participants"
|
||||
. " WHERE eve_id = " . $intEveId . " AND par_origine = '" . $strOrig . "'"
|
||||
. " AND par_id_original > 0"
|
||||
. ") _ms1_imp_par";
|
||||
$strSubPec = "SELECT pid FROM ("
|
||||
. " SELECT pec_id AS pid FROM resultats_participants"
|
||||
. " WHERE eve_id = " . $intEveId . " AND par_origine = '" . $strOrig . "'"
|
||||
. " AND pec_id > 0"
|
||||
. ") _ms1_imp_pec";
|
||||
|
||||
// 1) Réponses questions des participants import seulement
|
||||
$sqlDelQue = "DELETE FROM resultats_questions WHERE par_id IN (" . $strSubPar . ")"
|
||||
. " OR pec_id IN (" . $strSubPec . ")";
|
||||
if ($objDatabase->fxQuery($sqlDelQue) !== false) {
|
||||
$intDelQue = intval($objDatabase->affected_rows);
|
||||
// Charger les IDs en mémoire (SELECT léger) puis DELETE par lots sur PK/index
|
||||
$tabRows = $objDatabase->fxGetResults(
|
||||
"SELECT par_id, par_id_original, pec_id FROM resultats_participants"
|
||||
. " WHERE eve_id = " . $intEveId
|
||||
. " AND par_origine = '" . $strOrig . "'"
|
||||
);
|
||||
$arrParIds = array();
|
||||
$arrPecIds = array();
|
||||
if (is_array($tabRows)) {
|
||||
$intMax = fxcountpatch($tabRows);
|
||||
for ($i = 1; $i <= $intMax; $i++) {
|
||||
$intParId = intval($tabRows[$i]['par_id'] ?? 0);
|
||||
$intParOrig = intval($tabRows[$i]['par_id_original'] ?? 0);
|
||||
$intPecId = intval($tabRows[$i]['pec_id'] ?? 0);
|
||||
if ($intParId > 0) {
|
||||
$arrParIds[] = $intParId;
|
||||
}
|
||||
if ($intParOrig > 0) {
|
||||
$arrParIds[] = $intParOrig;
|
||||
}
|
||||
if ($intPecId > 0) {
|
||||
$arrPecIds[] = $intPecId;
|
||||
}
|
||||
}
|
||||
}
|
||||
$arrParIds = array_values(array_unique($arrParIds));
|
||||
$arrPecIds = array_values(array_unique($arrPecIds));
|
||||
|
||||
// Lots modestes : ~200 IDs / 40 ms — laisse InnoDB digérer sans saturer le pool
|
||||
$intChunk = 200;
|
||||
$intPause = 40000;
|
||||
|
||||
// 1) Questions (souvent le plus gros volume) — deux passes indexées, pas de OR monolithe
|
||||
$intDelQue = fxImportResultatsDeleteBatchedIn('resultats_questions', 'par_id', $arrParIds, $intChunk, $intPause);
|
||||
$intDelQue += fxImportResultatsDeleteBatchedIn('resultats_questions', 'pec_id', $arrPecIds, $intChunk, $intPause);
|
||||
|
||||
// 2) Produits éventuels
|
||||
@$objDatabase->fxQuery(
|
||||
"DELETE FROM resultats_produits_new WHERE par_id IN (" . $strSubPar . ")"
|
||||
. " OR pec_id IN (" . $strSubPec . ")"
|
||||
fxImportResultatsDeleteBatchedIn('resultats_produits_new', 'par_id', $arrParIds, $intChunk, $intPause);
|
||||
fxImportResultatsDeleteBatchedIn('resultats_produits_new', 'pec_id', $arrPecIds, $intChunk, $intPause);
|
||||
|
||||
// 3) Coquilles pec (avant participants) — toujours borné à eve_id
|
||||
$strEveWhere = 'eve_id = ' . $intEveId;
|
||||
$intDelPec = 0;
|
||||
$intDelPec += fxImportResultatsDeleteBatchedIn('resultats_epreuves_commandees', 'pec_id', $arrPecIds, $intChunk, $intPause, $strEveWhere);
|
||||
$intDelPec += fxImportResultatsDeleteBatchedIn('resultats_epreuves_commandees', 'pec_id_original', $arrPecIds, $intChunk, $intPause, $strEveWhere);
|
||||
$strPrefix = 'I' . $intEveId . 'X%';
|
||||
$intDelPec += fxImportResultatsDeleteBatchedLimit(
|
||||
"DELETE FROM resultats_epreuves_commandees WHERE eve_id = " . $intEveId
|
||||
. " AND no_panier LIKE '" . fxImportResultatsDbEsc($strPrefix) . "'",
|
||||
$intChunk,
|
||||
$intPause
|
||||
);
|
||||
|
||||
// 3) Coquilles pec liées à l'import (avant delete participants)
|
||||
$sqlDelPec = "DELETE FROM resultats_epreuves_commandees WHERE eve_id = " . $intEveId
|
||||
. " AND pec_id IN (" . $strSubPec . ")";
|
||||
if ($objDatabase->fxQuery($sqlDelPec) !== false) {
|
||||
$intDelPec += intval($objDatabase->affected_rows);
|
||||
}
|
||||
// Aussi pec_id_original = pec participant
|
||||
$sqlDelPecOrig = "DELETE FROM resultats_epreuves_commandees WHERE eve_id = " . $intEveId
|
||||
. " AND pec_id_original IN (" . $strSubPec . ")";
|
||||
if ($objDatabase->fxQuery($sqlDelPecOrig) !== false) {
|
||||
$intDelPec += intval($objDatabase->affected_rows);
|
||||
}
|
||||
$strPrefix = 'I' . $intEveId . 'X%';
|
||||
$sqlDelPec2 = "DELETE FROM resultats_epreuves_commandees WHERE eve_id = " . $intEveId
|
||||
. " AND no_panier LIKE '" . fxImportResultatsDbEsc($strPrefix) . "'";
|
||||
if ($objDatabase->fxQuery($sqlDelPec2) !== false) {
|
||||
$intDelPec += intval($objDatabase->affected_rows);
|
||||
}
|
||||
|
||||
// 4) Participants import
|
||||
$sqlDelPar = "DELETE FROM resultats_participants WHERE eve_id = " . $intEveId
|
||||
. " AND par_origine = '" . $strOrig . "'";
|
||||
if ($objDatabase->fxQuery($sqlDelPar) !== false) {
|
||||
$intDelPar = intval($objDatabase->affected_rows);
|
||||
}
|
||||
// 4) Participants import — LIMIT pour ne pas locker la table entière d’un coup
|
||||
$intDelPar = fxImportResultatsDeleteBatchedLimit(
|
||||
"DELETE FROM resultats_participants WHERE eve_id = " . $intEveId
|
||||
. " AND par_origine = '" . $strOrig . "'",
|
||||
$intChunk,
|
||||
$intPause
|
||||
);
|
||||
|
||||
return array(
|
||||
'state' => 'ok',
|
||||
'message' => 'Reset import OK — participants ' . $intDelPar
|
||||
'message' => 'Reset import OK (par lots) — participants ' . $intDelPar
|
||||
. ', pec ' . $intDelPec
|
||||
. ', réponses questions ' . $intDelQue
|
||||
. '. Catalogue questions événement intact. Inscriptions en ligne intactes.',
|
||||
@ -4450,7 +4540,7 @@ function fxImportResultatsRenderEventPanel($intEveId) {
|
||||
$('#ms1_import_reset_data').prop('disabled', true);
|
||||
setBusy(true,
|
||||
'Reset import en cours…',
|
||||
'Suppression de milliers de lignes — 30 s à 2 min selon la charge. Le chrono tourne = ça travaille.'
|
||||
'Suppression par petits lots (serveur protégé) — peut prendre 1–3 min. Le chrono tourne = OK.'
|
||||
);
|
||||
showResetFlash('warning', 'Reset en cours — ne fermez pas la page.');
|
||||
$.ajax({
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
* Constantes *
|
||||
*
|
||||
**************/
|
||||
define('_VERSION_CODE', '4.72.957'); // MSIN-4482 — Actualiser compteurs import (SQL preview rapide)
|
||||
define('_VERSION_CODE', '4.72.958'); // MSIN-4482 — reset import par lots (ne plus saturer MySQL)
|
||||
define('_DATE_CODE', '2026-08-04');
|
||||
//MSIN-4290
|
||||
define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');
|
||||
|
||||
Reference in New Issue
Block a user