← Back to team overview

maria-discuss team mailing list archive

Re: MariaDB 10.5.18: Performance-Problems with CONCAT

 

Hello William Edwards,
yes we are affected and had to revert to version 10.5.16 to keep rebuilding our lookup/search-tables at night.

Am 04.01.2023 um 19:58 schrieb William Edwards:
Op 4 jan. 2023 om 16:12 heeft Johannes Ody <j.ody@xxxxxxxxxxxxx> het volgende geschreven:

I'm german. Please excuse my bad english.
MariaDB version 10.5.18 is much slower than version 10.5.16 in string-manipulation (CONCAT).
Are you hit by MDEV-29988?

In version 10.5.16 my code in the bottom needs for 800 records in tmp_tartikel_live 1-2 seconds.
In version 10.5.18 my code needs for 800 records in tmp_tartikel_live 20-30 seconds.
In version 10.5.16 my code needs for 26000 records in tmp_tartikel_live 5-6 minutes. It is ok.
In version 10.5.18 my code needs for 26000 records in tmp_tartikel_live more than 2 hours and
was terminating by the system and was not ready.
The execution-time glows much more than linear.
The calling script is internal for maintenance-work at night.
The purpose of this code is to build our search-tables in MySQL (concordance).
The code must work for strings with special characters in the coding-systems html (&egrave;), ansi and utf-8.
In german and france we have the special characters:
     &szlig;
     &uuml;
     &ouml;
     &auml;&agrave;&aacute;
     &egrave;&eacute;
     and so on.

I have found other users with performance-problems in MariaDB version 10.5.18, and they also use CONCAT:
  https://dba.stackexchange.com/questions/284073/query-performance-drop-after-upgrade-to-mariadb-10-5-group-concat-join

My code is the following:
     /*#JODY_20221010
     Die Datenbank muss fuer diesen Code auf Kollektion=latin1_swedish_ci eingestellt sein.
         ALTER DATABASE d03a5b1c COLLATE latin1_swedish_ci;
     Das CHARACTER SET scheint nicht so wichtig zu sein.
     */

     /*
     #JODY_20211228 (ACHTUNG): Diese Datei muss ASCII-Codiert sein (nicht UTF8)
         Funktion zum Loeschen von HTML-Tags. Kann nur vom browser aus installiert werden.
     * /
     DROP PROCEDURE IF EXISTS mysql_log;*/
     DROP FUNCTION IF EXISTS mysql_striptags;
     DROP FUNCTION IF EXISTS mysql_replace_utf8_sonderzeichen;
     DROP FUNCTION IF EXISTS mysql_replace_single_char;
     DROP FUNCTION IF EXISTS mysql_replace_char;
     DROP FUNCTION IF EXISTS mysql_replace_umlaute;
     /*DROP FUNCTION IF EXISTS mysql_delete_fuellwoerter;*/

     DELIMITER $$

     /*CREATE PROCEDURE mysql_log(strText VARCHAR(255))
     BEGIN
         INSERT INTO debugout(cText) VALUES (strText);
     END$$*/

     CREATE FUNCTION mysql_striptags(strParam TEXT)
     RETURNS TEXT
     DETERMINISTIC
     BEGIN
         DECLARE str TEXT;
         DECLARE posOpen INT;
         DECLARE posClose INT;

         SET str = strParam;
         loop1: LOOP
             SET posOpen = LOCATE("<", str);
             SET posClose = LOCATE(">", str, posOpen);
                 /*#JODY_20211228 Wenn posOpen 0 ist, dann ist posClose auch immer 0.*/
             IF posClose = 0 THEN
                 LEAVE loop1;
             END IF;
             SET str = CONCAT(LEFT(str, posOpen - 1), " ", MID(str, posClose + 1));
         END LOOP loop1;
         RETURN str;
     END$$

     /*#JODY_20220201:
     Die Moeglichkeit zur URL-Erzeugung mit UTF8-Sonderzeichen besteht darin, dass ein Anwender Strings
     aus einem Eingabefeld in die Zwischenablage kopiert und dann im URL-Feld des Browsers einfuegt.
     Dann stehen in der URL z.B. fuer
         &auml; CONCAT(CHAR(195), CHAR(164))
     Das Problem ist also die Eingabe von Sonderzeichen im URL-Feld des Browsers.*/
     CREATE FUNCTION mysql_replace_utf8_sonderzeichen(strParam TEXT)
     RETURNS TEXT
     DETERMINISTIC
     BEGIN
         DECLARE str TEXT;
         DECLARE nPos INT;
         DECLARE nOrd INT;
         DECLARE strChar VARCHAR(2);

         SET str = strParam;
         SET nPos = 1;
         loop_for: LOOP
             SET nPos = LOCATE(CHAR(195), str, nPos); /*
                 ACHTUNG (#JODY_20220201):
                 Hier kommen auch Positionen auf ein einfaches a zurueck. Liegt an MySQL. Diese brauche
                 ich hier zwar nicht. Aber der Code kann damit umgehen. Durch das LOCATE spare ich mir aber
                 fuer die grosse Menge an Zeichen die Schleifendurchlaeufe.*/
             IF nPos = 0 THEN
                 LEAVE loop_for;
             END IF;
             SET strChar = MID(str, nPos, 1);
             SET nPos = nPos + 1;
             IF ORD(strChar) != 195 THEN
                 ITERATE loop_for;
             END IF;
             SET strChar = MID(str, nPos, 1);
             SET nOrd = ORD(strChar);
             IF nOrd >= 132 AND nOrd <= 188 THEN
                 SET str = REPLACE(str, CONCAT(CHAR(195), strChar), CHAR(nOrd + 64));
                 SET nPos = nPos + 1;
             END IF;
         END LOOP;

         RETURN str;
     END$$

     /*
     ACHTUNG (#JODY_20220120):
     Da strNewStr auch nur Blanks enthalten darf/kann, kann hier nicht mit CHAR(2) gearbeitet werden.
     Variablen vom Typ CHAR(x) waeren bei einem Wert von z.B. " " leer (Laenge 0), da die Laenge sich
     aus dem ersten Zeichen von rechts, da kein Blank ist, ergibt. Es sind gewissermassen Fortran-Strings,
     die ja auch so arbeiten. VARCHAR sind dagegen C-Strings, die 0-terminiert sind.
     */
     CREATE FUNCTION mysql_replace_single_char(strParam TEXT, nChr INT, strName CHAR(10), strNewStr VARCHAR(2))
     RETURNS TEXT
     DETERMINISTIC
     BEGIN
         DECLARE str TEXT;
         DECLARE strNewStrTmp VARCHAR(2);
         DECLARE strChar195 VARCHAR(2);
         DECLARE nPosEnd INT;

         SET strNewStrTmp = strNewStr;
             /*ACHTUNG (#JODY_20220120): Nach meinem aktuellen Kenntnisstand koennen Parameter nicht geaendert werden.*/
         IF strNewStrTmp = "<" THEN
             SET strNewStrTmp = LEFT(strName, 1);
         END IF;
         SET str = REPLACE(strParam, CONCAT("&", strName, ";"), strNewStrTmp);
         IF nChr <= 255 THEN
             SET str = REPLACE(str, CHAR(nChr), strNewStrTmp);
         END IF;
         SET str = REPLACE(str, CONCAT("&#", CONVERT(nChr, CHAR), ";"), strNewStrTmp);

         RETURN str;
     END$$

     CREATE FUNCTION mysql_replace_char(strParam TEXT, nChrMin INT, nChrMax INT, strNamesExt VARCHAR(40), strNewStr CHAR(1))
     RETURNS TEXT
     DETERMINISTIC
     BEGIN
         DECLARE str TEXT;
         DECLARE nChr INT;
         DECLARE strNames VARCHAR(40);
         DECLARE nPosStart INT;
         DECLARE nPosEnd INT;
         DECLARE bUseNames TINYINT;
         DECLARE strName VARCHAR(40);
         DECLARE strNewStrUpper CHAR(1);

         SET str = strParam;
         SET nChr = nChrMin;

         IF strNamesExt != "" THEN
             /*#JODY_20211229
             In MySQL gibt es in Stored Procedures keine Array-Unterstuetzung.
             s. https://forums.mysql.com/read.php?98,406197,406216*/
             SET strNames = CONCAT("grave;acute;circ;", strNamesExt);
                 /*#JODY_20220408 Dieser Code ist fuer die Sonderzeichen &agrave; usw. gedacht. Nicht fuer &grave;*/
             SET bUseNames = 1;
             SET strNewStrUpper = UPPER(strNewStr);
             SET nPosStart = 1;
         ELSE
             SET bUseNames = 0;
         END IF;

         loop_for: LOOP
             IF bUseNames != 0 THEN
                 SET nPosEnd = LOCATE(";", strNames, nPosStart);
                 SET strName = MID(strNames, nPosStart, nPosEnd - nPosStart + 1);
                     /*#JODY_20211229 Das ; will ich mit haben.*/
                 SET str = REPLACE(str, CONCAT("&", strNewStr, strName), strNewStr);
                 SET str = REPLACE(str, CONCAT("&", strNewStrUpper, strName), strNewStr);
                 /*SET strNames = MID(strNames, nPos + 1);*/
                 SET nPosStart = nPosEnd + 1;
                     /*#JODY_20211229 So ersprare ich mir dieses MID.*/
             END IF;

             SET str = REPLACE(str, CONCAT("&#", CONVERT(nChr, CHAR), ";"), strNewStr);
             SET str = REPLACE(str, CHAR(nChr), strNewStr);

             /*#JODY_20211229
             Diese Funktion wird nur fuer Umlaute und nur fuer die kleinen augerufen. Diese liegen zwischen
             &#224; und &#252;.*/
             SET str = REPLACE(str, CONCAT("&#", CONVERT(nChr - 32, CHAR), ";"), strNewStr);
             SET str = REPLACE(str, CHAR(nChr - 32), strNewStr);
             IF nChr >= nChrMax THEN
                 LEAVE loop_for;
             END IF;
             SET nChr = nChr + 1;
         END LOOP;

         RETURN str;
     END$$

     CREATE FUNCTION mysql_replace_umlaute(strParam TEXT)
     RETURNS TEXT
     DETERMINISTIC
     BEGIN
         DECLARE str TEXT;
         DECLARE nPos INT;
         DECLARE nChr INT;

         SET str = mysql_replace_utf8_sonderzeichen(strParam); /*
             ACHTUNG (#JODY_20220201):
             Die UTF8-Sonderzeichen muessen hier ganz oben behandelt werden. Sie werden in ASCII-Sonderzeichen
             konvertiert, die ja nachfolgend auch noch wieder konvertiert werden. So kann ich das aber allgemein
             gueltig fuer alle UTF8-Sonderzeichen abhandeln.*/

         SET str = REPLACE(LOWER(str), "make-up", "makeup");
             /*#JODY_20220315 Make-Up soll intern als ein Wort behandelt werden.*/

         /*#JODY_20220120
         Die vor- und nachlaufenden Blanks werden benoetigt, damit der
         Wortanfaeng/-ende richtig und leicht erkannt wird.
         */
         SET str = CONCAT(" ", mysql_striptags(str), " ");

         SET str = mysql_replace_char(str, 224, 230, "tilde;uml;ring;elig;", "a");
         SET str = mysql_replace_char(str, 232, 235, "uml;", "e");
         SET str = mysql_replace_char(str, 236, 239, "uml;", "i");
         SET str = mysql_replace_char(str, 242, 246, "tilde;uml;slash;", "o");
         /*#JODY_20211228
             So brauche ich den Ignore-Parameter in mysql_replace_char() nicht mehr.
             &#215; und &#247; sollen hier nicht beachtet werden.*/
         SET str = mysql_replace_single_char(str, 216, "Oslash", "o");
         SET str = mysql_replace_single_char(str, 248, "oslash", "o");
         SET str = mysql_replace_char(str, 249, 252, "uml;", "u");
         SET str = mysql_replace_single_char(str, 223, "szlig", "ss");

         SET str = REPLACE(str, "ae", "a");
         SET str = REPLACE(str, "oe", "o");
         SET str = REPLACE(str, "ue", "u");

         SET str = mysql_replace_single_char(str, 191, "iquest", ""); /*#JODY_20211228 fran&ccedil;ois*/
         SET str = mysql_replace_single_char(str, 199, "Ccedil", "<");/*#JODY_20211228 fran&ccedil;ois*/
         SET str = mysql_replace_single_char(str, 231, "ccedil", "<");/*#JODY_20211228 fran&ccedil;ois*/
         SET str = mysql_replace_single_char(str, 241, "ntilde", "<");/*#JODY_20211228 porte&#241;o*/
         SET str = mysql_replace_single_char(str, 215, "times", "x");
         SET str = mysql_replace_single_char(str, 160, "nbsp", " ");
         SET str = mysql_replace_single_char(str, 178, "sup2", "2");    /*#JODY_20211228 Hochgestellte 2 z.B. bei dem Hersteller dsquared&#178;*/
         SET str = mysql_replace_single_char(str, 179, "sup3", "3");    /*#JODY_20211228 Hochgestellte 3 z.B. "wella care&#179; balance clean anti"*/
         SET str = mysql_replace_single_char(str, 185, "sup1", "");    /*#JODY_20211228 Hochgestellte 1; wird aber nicht benoetigt.*/
         SET str = mysql_replace_single_char(str, 8216, "lsquo", "");/*#JODY_20211228 j&lsquo;adore*/
         SET str = mysql_replace_single_char(str, 8217, "rsquo", "");/*#JODY_20211228 j&rsquo;adore*/
         SET str = REPLACE(str, "'", "");        /*#JODY_20211228 l'oreal*/

         SET str = mysql_replace_single_char(str,   34/*"*/, "quot", "");/*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str,  169, "copy", " ");    /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str,  173, "shy", "");        /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str,  174, "reg", " ");        /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str,  176, "deg", " ");        /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str,  183, "middot", " ");    /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str, 8194, "ensp", " ");    /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str, 8218, "sbquo", " ");    /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str, 8226, "bull", " ");    /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str, 8230, "hellip", " ");    /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str, 8364, "euro", "euro");    /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str, 8482, "trade", " ");    /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str, 8722, "minus", " ");    /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = REPLACE(str, "&#776;", "");
             /*#JODY_20211228 Beispiel: "ascorbinsaure natu&#776;rliches aroma"; Bei "a&#776;" wird ein &auml; angezeigt.*/

         SET str = mysql_replace_single_char(str, 60, "lt", " ");    /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str, 62, "gt", " ");    /*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str, 171, "laquo", " ");/*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str, 187, "raquo", " ");/*#JODY_20211228 Ist fuer die Suche nicht relevant.*/
         SET str = mysql_replace_single_char(str, 189, "frac12", "");/*#JODY_20211228 "mit ca &frac12; liter"*/

         /*#JODY_20211228 (ACHTUNG):
         Prinzipiell soll &acute; durch ein Blank ersetzt werden.
         Nur bei loreal ist dies auf Grund des Eigennamens nicht erwuenscht.*/
         SET str = mysql_replace_single_char(str, 96, "grave", " ");/*#JODY_20220408"*/
         /*SET str = REPLACE(str, "l&acute;oreal", "loreal"); #JODY_20220408*/
         SET str = mysql_replace_single_char(str, 180, "acute", " ");/*#JODY_20211228 "save&acute;n" oder "rock&acute;n"*/

         SET str = REPLACE(str, "&amp;", " ");

         /*#JODY_20220119 Es exisztieren so viele Begriffe, die in [] eingeklammert sind. z.B. [iln48006]*/
         SET str = REPLACE(str, "[", " ");
         SET str = REPLACE(str, "]", " ");
         SET str = REPLACE(str, "#", " ");
         SET str = REPLACE(str, "*", " ");
         SET str = REPLACE(str, "&", " ");
         SET str = REPLACE(str, "%", " ");
         SET str = REPLACE(str, "|", " ");/*#JODY_20220316*/

         SET str = REPLACE(str, ".", " ");
         SET str = REPLACE(str, ":", " ");
         SET str = REPLACE(str, ",", " ");
         SET str = REPLACE(str, ";", " ");
         SET str = REPLACE(str, "?", " ");
         SET str = REPLACE(str, "!", " ");
         SET str = REPLACE(str, "/", " ");
         SET str = REPLACE(str, "\\", " ");
             /*#JODY_20220120 Macht mir z.B. beim Suchbegriff "wax\" die SQL-Anweisung kaputt.*/
         SET str = REPLACE(str, "-", " ");
         SET str = REPLACE(str, "+", " ");
         SET str = REPLACE(str, "=", " ");
         SET str = REPLACE(str, "\r", " ");
         SET str = REPLACE(str, "\n", " ");
         SET str = REPLACE(str, "\t", " ");

         SET str = REPLACE(str, "(", " ");
         SET str = REPLACE(str, ")", " ");

         /*
         #JODY_20220121
         Erstmal nicht. Die Anwender suchen meistens nach Woertern, wo ein loeschen dieser Buchstaben das
         Suchergebnis eigentlich verschlechtert, da weniger Zeichenuebereinstimmungen bei Teilwoertern
         gefunden werden. In Teilwoertern
         #JODY_20220120 Dies soll die Menge der Wortredundanzen verringern. z.B. zugewandte/zugewandt* /
         SET str = REPLACE(str, "em ", " ");
         SET str = REPLACE(str, "en ", " ");
         SET str = REPLACE(str, "es ", " ");
         SET str = REPLACE(str, "er ", " ");
         SET str = REPLACE(str, " sie ", " ");
             /*#JODY_20220120
             Dies ist so noetig, da "sie" durch das naechste REPLACE zu "si" wird und eine Gleichbehandlung
             von "sie" und "si" wegen der vielen Suchen nach "si" nicht erwunescht ist. Ausserdem macht die
             Suche nach "sie" eigentlich auch keinen Sinn.* /
         SET str = REPLACE(str, "e ", " ");
         SET str = REPLACE(str, "ig ", " ");*/

         /*#JODY_20211229 Bisher koennen durchaus mehrere Leerzeichen hintereinander sein. Diese jetzt zu einem zusammenstreichen.*/
         loop1: LOOP
             SET nPos = LOCATE("  ", str);
             IF nPos = 0 THEN
                 LEAVE loop1;
             END IF;
             SET str = REPLACE(str, "  ", " ");
         END LOOP loop1;

         SET str = TRIM(str);
             /*#JODY_20220120
             Die vor- und nachlaufenden Blanks wieder loeschen. Wurde nur eingefuegt,
             damit der Wortanfaeng/-ende richtig und leicht erkannt wird.
             */

         RETURN str;
     END$$

     DELIMITER ;
     CREATE TABLE tmp_tartikel_live (
         kArtikel            INT UNSIGNED,
         cArtNr                VARCHAR(31),
         cSeo                VARCHAR(255),
         cSeoU                TEXT,
         cBeschreibung        TEXT,
         cBeschreibungU        TEXT,
         cName                VARCHAR(255),
         cSuchbegriffe        TEXT,
         cKurzBeschreibung    TEXT,
         kurztyp                TEXT,
         kurzserie            TEXT,
         kurzname            TEXT
     ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
     SELECT
         tmp_tartikel_live.kArtikel,
         tmp_tartikel_live.cArtNr,
         tmp_tartikel_live.cSeo,
         tmp_tartikel_live.cBeschreibung,
         CONCAT(" ", mysql_replace_umlaute(tmp_tartikel_live.cName), " ") AS cNameU,
         CONCAT(" ", mysql_replace_umlaute(tmp_tartikel_live.cSuchbegriffe), " ") AS cSuchbegriffeU,
         CONCAT(" ", mysql_replace_umlaute(tmp_tartikel_live.cKurzBeschreibung), " ") AS cKurzBeschreibungU,
         CONCAT(" ", mysql_replace_umlaute(tmp_tartikel_live.cBeschreibung), " ") AS cBeschreibungU,
         CONCAT(" ", mysql_replace_umlaute(tmp_tartikel_live.cSeo), " ") AS cSeoU,
         CONCAT(" ", mysql_replace_umlaute(tmp_tartikel_live.kurztyp), " ") AS kurztypU,
         CONCAT(" ", mysql_replace_umlaute(tmp_tartikel_live.kurzserie), " ") AS kurzserieU,
         CONCAT(" ", mysql_replace_umlaute(tmp_tartikel_live.kurzname), " ") AS kurznameU
     FROM tmp_tartikel_live

This code prepare article-text for search in. Is this problem known or is it new.

I hope, I do it right in this way.

Johannes Ody


_______________________________________________
Mailing list: https://launchpad.net/~maria-discuss
Post to     : maria-discuss@xxxxxxxxxxxxxxxxxxx
Unsubscribe : https://launchpad.net/~maria-discuss
More help   : https://help.launchpad.net/ListHelp


References