En ymmärrä miksi JavaScript ja erityisesti jQuery tuottaa minulle aina niin paljon vaikeuksia käyttää. Oma osaamiseni on enemmän html:n ja php:n puolella.
Käytän Canvasjs kirjastoa piirtääkseni sivulle pylväskaavion ja saan piirrettyä sen hyvin ilman mitään ongelmia. Ongelma on kuitenkin siinä, että joskus kaavion piirtämisessä kestää kauan aikaa, koska kymmenien tuhansien rivien hakeminen (90 kertaa) kymmeniä miljoonia rivejä sisältävästä taulusta kestää kauan. Niinpä yritän käyttää jQueryä siten, että sivu latautuisi valmiiksi ilman kaaviota ja jQuery hakisi datan erikseen ja näyttäisi kaavion sitten sivulla, kun haku on valmis. En vain millään saa tätä toimimaan, enkä käsitä missä on ongelma.
Tässä on toimiva koodi ilman jQueryä:
<!DOCTYPE HTML> <html> <head> <title>Data chart of student activity</title> <link rel="stylesheet" href="style.css" type="text/css" /> <script src="canvasjs.min.js"></script> <script type="text/javascript"> window.onload = function () { var chart = new CanvasJS.Chart("chartContainer", { theme: "light2", animationEnabled: true, title: { text: "Hits on our website by the student", fontSize: 30 }, exportFileName: "Student name", exportEnabled: true, axisY: { scaleBreaks: { autoCalculate: true, collapsibleThreshold: "40%", lineColor: "black" } }, axisX: { stripLines: [ { value: 1586088000000, label: "REGISTERED", labelFontColor: "rgba(48, 48, 48, 1)", labelFontSize: 16, labelFontWeight: "bold", showOnTop: true, labelPlacement: "inside", thickness: 4, color: "rgba(0, 177, 106, 1)" }, { value: 1591876800000, label: "SUSPENDED", labelFontColor: "red", labelFontSize: 16, labelFontWeight: "bold", showOnTop: true, labelPlacement: "inside", thickness: 4, color: "red" }, { startValue: 1591876800000, endValue: 1596196800000, showOnTop: true, color: "rgba(255, 0, 0, 0.3)" } ] }, data: [ { type: "column", toolTipContent: "{x}: <strong>{y} hits</strong>", color: "rgba(135, 86, 146, 255)", xValueType: "dateTime", dataPoints:[{"y":0,"x":1585699200000},{"y":0,"x":1585785600000},{"y":0,"x":1585872000000},{"y":0,"x":1585958400000},{"y":0,"x":1586044800000},{"y":0,"x":1586131200000},{"y":0,"x":1586217600000},{"y":0,"x":1586304000000},{"y":0,"x":1586390400000},{"y":0,"x":1586476800000},{"y":0,"x":1586563200000},{"y":0,"x":1586649600000},{"y":0,"x":1586736000000},{"y":0,"x":1586822400000},{"y":0,"x":1586908800000},{"y":0,"x":1586995200000},{"y":0,"x":1587081600000},{"y":0,"x":1587168000000},{"y":0,"x":1587254400000},{"y":0,"x":1587340800000},{"y":0,"x":1587427200000},{"y":0,"x":1587513600000},{"y":0,"x":1587600000000},{"y":0,"x":1587686400000},{"y":0,"x":1587772800000},{"y":0,"x":1587859200000},{"y":0,"x":1587945600000},{"y":0,"x":1588032000000},{"y":0,"x":1588118400000},{"y":0,"x":1588204800000},{"y":0,"x":1588291200000},{"y":0,"x":1588377600000},{"y":0,"x":1588464000000},{"y":0,"x":1588550400000},{"y":0,"x":1588636800000},{"y":0,"x":1588723200000},{"y":0,"x":1588809600000},{"y":0,"x":1588896000000},{"y":0,"x":1588982400000},{"y":0,"x":1589068800000},{"y":0,"x":1589155200000},{"y":0,"x":1589241600000},{"y":0,"x":1589328000000},{"y":0,"x":1589414400000},{"y":0,"x":1589500800000},{"y":0,"x":1589587200000},{"y":0,"x":1589673600000},{"y":304,"x":1589760000000},{"y":67,"x":1589846400000},{"y":0,"x":1589932800000},{"y":39,"x":1590019200000},{"y":0,"x":1590105600000},{"y":0,"x":1590192000000},{"y":0,"x":1590278400000},{"y":0,"x":1590364800000},{"y":218,"x":1590451200000},{"y":64,"x":1590537600000},{"y":0,"x":1590624000000},{"y":0,"x":1590710400000},{"y":0,"x":1590796800000},{"y":0,"x":1590883200000},{"y":0,"x":1590969600000},{"y":0,"x":1591056000000},{"y":0,"x":1591142400000},{"y":0,"x":1591228800000},{"y":0,"x":1591315200000},{"y":0,"x":1591401600000},{"y":0,"x":1591488000000},{"y":0,"x":1591574400000},{"y":0,"x":1591660800000},{"y":0,"x":1591747200000},{"y":0,"x":1591833600000},{"y":0,"x":1591920000000},{"y":0,"x":1592006400000},{"y":0,"x":1592092800000},{"y":0,"x":1592179200000},{"y":0,"x":1592265600000},{"y":0,"x":1592352000000},{"y":0,"x":1592438400000},{"y":0,"x":1592524800000},{"y":0,"x":1592611200000},{"y":0,"x":1592697600000},{"y":0,"x":1592784000000},{"y":0,"x":1592870400000},{"y":0,"x":1592956800000},{"y":0,"x":1593043200000},{"y":0,"x":1593129600000},{"y":0,"x":1593216000000},{"y":0,"x":1593302400000},{"y":0,"x":1593388800000},{"y":0,"x":1593475200000},{"y":0,"x":1593561600000},{"y":0,"x":1593648000000},{"y":0,"x":1593734400000},{"y":0,"x":1593820800000},{"y":0,"x":1593907200000},{"y":0,"x":1593993600000},{"y":0,"x":1594080000000},{"y":0,"x":1594166400000},{"y":0,"x":1594252800000},{"y":0,"x":1594339200000},{"y":0,"x":1594425600000},{"y":0,"x":1594512000000},{"y":0,"x":1594598400000},{"y":0,"x":1594684800000},{"y":0,"x":1594771200000},{"y":0,"x":1594857600000},{"y":0,"x":1594944000000},{"y":0,"x":1595030400000},{"y":0,"x":1595116800000},{"y":0,"x":1595203200000},{"y":0,"x":1595289600000},{"y":0,"x":1595376000000},{"y":0,"x":1595462400000},{"y":0,"x":1595548800000},{"y":0,"x":1595635200000},{"y":0,"x":1595721600000},{"y":0,"x":1595808000000},{"y":0,"x":1595894400000},{"y":0,"x":1595980800000},{"y":0,"x":1596067200000},{"y":0,"x":1596153600000}] } ] }); chart.render(); if(chart.axisY[0].get("maximum") < 100) { chart.axisY[0].set("maximum", 100, false); chart.axisY[0].set("interval", 10, false); chart.axisY[0].scaleBreaks.set("autoCalculate"); } if(chart.axisY[0].get("interval") < 1) { chart.axisY[0].set("interval", 1); } } </script> </head> <body> <div class=content> <span> <h1>Student activity on third term online</h1> <div id="chartContainer" style="height: 600px; width: 100%;"></div> <div class="next"> <button onclick="location.href='/Activity?class=5A2&student=15';" class="nbut">Next student</button> </div> <div class="previous"> <button onclick="location.href='/Activity?class=5A2&student=13';" class="pbut">Previous student</button> </div> </span> </div> </body> </html>
Tässä on ei-toimiva koodi jQueryn kanssa. Sivu lataa ja näyttää kaiken muun sisällön ongelmitta, mutta kaavio ei tule näkyviin.
<!DOCTYPE HTML> <html> <head> <title>Data chart of student activity</title> <link rel="stylesheet" href="style.css" type="text/css" /> <script language="javascript" type="text/javascript" src="jquery-3.5.1.js"></script> <script src="canvasjs.min.js"></script> </head> <body> <div class=content> <span> <h1>Student activity on third term online</h1> <div id="chartContainer" style="height: 600px; width: 100%;"></div> <div class="next"> <button onclick="location.href='/Activity?class=5A2&student=15';" class="nbut">Next student</button> </div> <div class="previous"> <button onclick="location.href='/Activity?class=5A2&student=13';" class="pbut">Previous student</button> </div> </span> </div> <script id="source" language="javascript" type="text/javascript"> $(function () { $.ajax({ url: "charts.php", data: "class=5A2&student=14", dataType: "html", success: function(data) { alert("result: " + data); $(document.head).append(data); } }); }); </script> </body> </html>
alert("result: " + data) rivi näyttää mitä charts.php tiedosto syöttää ulos ja sieltä tulee seuraava javascriptin pätkä ulos niin kuin kuuluukin (<script type="text/javascript"> ja </script> tagien kanssa. Otin ne vain tästä pois, kun postaukseni ei muuten olisi lähtenyt eteenpäin). Olen koettanut tätä myös dataType: "json" ja dataType: "script" kanssa, mutta silti ei toimi.
window.onload = function () { var chart = new CanvasJS.Chart("chartContainer", { theme: "light2", animationEnabled: true, title: { text: "Hits on our website by the student", fontSize: 30 }, exportFileName: "Student name", exportEnabled: true, axisY: { scaleBreaks: { autoCalculate: true, collapsibleThreshold: "40%", lineColor: "black" } }, axisX: { stripLines: [ { value: 1586088000000, label: "REGISTERED", labelFontColor: "rgba(48, 48, 48, 1)", labelFontSize: 16, labelFontWeight: "bold", showOnTop: true, labelPlacement: "inside", thickness: 4, color: "rgba(0, 177, 106, 1)" }, { value: 1591876800000, label: "SUSPENDED", labelFontColor: "red", labelFontSize: 16, labelFontWeight: "bold", showOnTop: true, labelPlacement: "inside", thickness: 4, color: "red" }, { startValue: 1591876800000, endValue: 1596196800000, showOnTop: true, color: "rgba(255, 0, 0, 0.3)" } ] }, data: [ { type: "column", toolTipContent: "{x}: <strong>{y} hits</strong>", color: "rgba(135, 86, 146, 255)", xValueType: "dateTime", dataPoints:[{"y":0,"x":1585699200000},{"y":0,"x":1585785600000},{"y":0,"x":1585872000000},{"y":0,"x":1585958400000},{"y":0,"x":1586044800000},{"y":0,"x":1586131200000},{"y":0,"x":1586217600000},{"y":0,"x":1586304000000},{"y":0,"x":1586390400000},{"y":0,"x":1586476800000},{"y":0,"x":1586563200000},{"y":0,"x":1586649600000},{"y":0,"x":1586736000000},{"y":0,"x":1586822400000},{"y":0,"x":1586908800000},{"y":0,"x":1586995200000},{"y":0,"x":1587081600000},{"y":0,"x":1587168000000},{"y":0,"x":1587254400000},{"y":0,"x":1587340800000},{"y":0,"x":1587427200000},{"y":0,"x":1587513600000},{"y":0,"x":1587600000000},{"y":0,"x":1587686400000},{"y":0,"x":1587772800000},{"y":0,"x":1587859200000},{"y":0,"x":1587945600000},{"y":0,"x":1588032000000},{"y":0,"x":1588118400000},{"y":0,"x":1588204800000},{"y":0,"x":1588291200000},{"y":0,"x":1588377600000},{"y":0,"x":1588464000000},{"y":0,"x":1588550400000},{"y":0,"x":1588636800000},{"y":0,"x":1588723200000},{"y":0,"x":1588809600000},{"y":0,"x":1588896000000},{"y":0,"x":1588982400000},{"y":0,"x":1589068800000},{"y":0,"x":1589155200000},{"y":0,"x":1589241600000},{"y":0,"x":1589328000000},{"y":0,"x":1589414400000},{"y":0,"x":1589500800000},{"y":0,"x":1589587200000},{"y":0,"x":1589673600000},{"y":304,"x":1589760000000},{"y":67,"x":1589846400000},{"y":0,"x":1589932800000},{"y":39,"x":1590019200000},{"y":0,"x":1590105600000},{"y":0,"x":1590192000000},{"y":0,"x":1590278400000},{"y":0,"x":1590364800000},{"y":218,"x":1590451200000},{"y":64,"x":1590537600000},{"y":0,"x":1590624000000},{"y":0,"x":1590710400000},{"y":0,"x":1590796800000},{"y":0,"x":1590883200000},{"y":0,"x":1590969600000},{"y":0,"x":1591056000000},{"y":0,"x":1591142400000},{"y":0,"x":1591228800000},{"y":0,"x":1591315200000},{"y":0,"x":1591401600000},{"y":0,"x":1591488000000},{"y":0,"x":1591574400000},{"y":0,"x":1591660800000},{"y":0,"x":1591747200000},{"y":0,"x":1591833600000},{"y":0,"x":1591920000000},{"y":0,"x":1592006400000},{"y":0,"x":1592092800000},{"y":0,"x":1592179200000},{"y":0,"x":1592265600000},{"y":0,"x":1592352000000},{"y":0,"x":1592438400000},{"y":0,"x":1592524800000},{"y":0,"x":1592611200000},{"y":0,"x":1592697600000},{"y":0,"x":1592784000000},{"y":0,"x":1592870400000},{"y":0,"x":1592956800000},{"y":0,"x":1593043200000},{"y":0,"x":1593129600000},{"y":0,"x":1593216000000},{"y":0,"x":1593302400000},{"y":0,"x":1593388800000},{"y":0,"x":1593475200000},{"y":0,"x":1593561600000},{"y":0,"x":1593648000000},{"y":0,"x":1593734400000},{"y":0,"x":1593820800000},{"y":0,"x":1593907200000},{"y":0,"x":1593993600000},{"y":0,"x":1594080000000},{"y":0,"x":1594166400000},{"y":0,"x":1594252800000},{"y":0,"x":1594339200000},{"y":0,"x":1594425600000},{"y":0,"x":1594512000000},{"y":0,"x":1594598400000},{"y":0,"x":1594684800000},{"y":0,"x":1594771200000},{"y":0,"x":1594857600000},{"y":0,"x":1594944000000},{"y":0,"x":1595030400000},{"y":0,"x":1595116800000},{"y":0,"x":1595203200000},{"y":0,"x":1595289600000},{"y":0,"x":1595376000000},{"y":0,"x":1595462400000},{"y":0,"x":1595548800000},{"y":0,"x":1595635200000},{"y":0,"x":1595721600000},{"y":0,"x":1595808000000},{"y":0,"x":1595894400000},{"y":0,"x":1595980800000},{"y":0,"x":1596067200000},{"y":0,"x":1596153600000}] } ] }); chart.render(); if(chart.axisY[0].get("maximum") < 100) { chart.axisY[0].set("maximum", 100, false); chart.axisY[0].set("interval", 10, false); chart.axisY[0].scaleBreaks.set("autoCalculate"); } if(chart.axisY[0].get("interval") < 1) { chart.axisY[0].set("interval", 1); } }
Missä jQuery koodissani on virhe niin, ettei kaavio näy sivulla?
Ainakin yksi selvä virhe on siinä, että tuossa viimeisessä koodissa teet funktion window.onload, mutta tietenkin onload-tapahtuma on lauennut jo aikaisemmin ja koodin lisääminen siihen AJAXilla ei enää muuta mitään. Eli ladattavaa koodia pitäisi muuttaa niin, että se ajetaan heti.
// Ennen: window.onload = function () { // ... } // Jälkeen: (function () { // ... })();
Kiitos. Arvasin, että jotain yksinkertaisia virheitä tuolta löytyy, mutta valitettavasti tuo ei silti ratkaissut ongelmaa. Sivu avautuu ongelmitta, mutta kaavio ei vieläkään näy sivulla.
Edit: Pahoittelut, katsoin tuon "jälkeen" kohdan vähän huonosti ja ainoastaan poistin window.onload lisäämättä noita sulkeita tarvittaviin paikkoihin. Nyt se toimii juuri niin kuin haluan! Kiitos!
Ongelmani Javascriptin ja jQueryn kanssa on, etten oikein tiedä miten niitä debugataan oikeaoppisesti. PHP:stä on helppo löytää virheet tulostamalla välituloksia var_dump:n avulla, mutta muut ohjelmointi-kielet...
Vaikka näin:
Tuloksen näet selaimesi console-ikkunassa (Inspectorissa)
console.log(["-- tulostan foo ja bar -muuttujat --",foo,bar])
Kiitos Lebe80! Pitääpä muistaa tuo seuraavalla kerralla Javascriptin kanssa pelatessa.
Minusta tuntuu, että jos sinun pitää syöttää selaimelle kymmeniä tuhansia rivejä jotain graafia varten, niin teet hyvin pitkälti jotain väärin. Ihminen ei pysty käsittelemään päässään tuollaista määrää dataa, joten yleensä kuvaajiin ei syötetä tuollaisia määriä lukuarvoja vaan ehkä muutamia kymmeniä tai sata. Myös graafin renderöinti selaimessa on paljon nopeampaa, kun laskentakohteita on vähemmän.
Kannattaisi siis laskea tieto mahdollisimman pitkälle jo backendissa ja sen jälkeen syöttää selaimelle kuvaajaa varten lasketut lukuarvot. Tällöin voit mahdollisesti myös hyödyntää tietokantamoottorin ominaisuuksia ja nopeuttaa kyselyitä, kun kymmenien tuhansien rivien sijaan kannasta haetaan aggregoitu tulos.
En minä selaimelle syötäkään kymmeniä tuhansia rivejä vaan tietokanta käsittelee niitä. Taisin ensimmäisessä viestissäni selittää tilanteen hiukan harhaanjohtavasti. Pahoittelut tästä. Tietokannan taulussa on siis kymmeniä miljoonia rivejä (periaatteessa jokaisen käyttäjän jokainen klikkaus sivulla viimeisen kolmen kuukauden aikana) ja haen tästä taulusta tietyin ehdoin yhden käyttäjän yhden päivän klikkausten määrän, joka voi joissain tapauksissa olla kymmeniä tuhansia. Kun minulla on n. 90 päivää, niin haen saman kyselyn n. 90 kertaa. Syötän selaimelle siis n. 90 riviä graafia varten, en kymmeniä tuhansia, niin kuin ensimmäisessä viestissä annoin virheellisesti ymmärtää. Näiden 90 kyselyn suorittaminen saattaa joidenkin käyttäjien kohdalla kestää jopa >20 sekuntia, kun toiset lataavat <.5s. Olenkin pohtinut, että olisi varmasti nopeampi hakea nuo kaikkien päivien klikkausten määrät yhdellä kyselyllä, mutta en ole osannut rakentaa toimivaa MySQL kyselyä, joka sen tekisi. Jos voitte antaa tällaisen kyselyn, niin olisin äärimmäisen kiitollinen! Tässä kysely, jota nyt käytän:
SELECT COUNT(*) AS hits FROM user_logs WHERE userid = ? AND edulevel = 2 AND realuserid IS NULL AND timecreated BETWEEN ? AND ?
Periaatteessa ongelmani on kuinka jakaa timecreated (timestamp) päiviin ja ryhmitellä tulokset sen mukaan.
AkeMake kirjoitti:
Periaatteessa ongelmani on kuinka jakaa timecreated (timestamp) päiviin ja ryhmitellä tulokset sen mukaan.
Etsimäsi ratkaisu on varmaan DATE(timecreated).
SELECT COUNT(*) AS hits, DATE(timecreated) AS day FROM user_logs WHERE userid = ? AND edulevel = 2 AND realuserid IS NULL AND timecreated BETWEEN ? AND ? GROUP BY DATE(timecreated)
Hienoa, kiitos! Tämä ei ihan suoraan toiminut, koska timecreated olikin määritelty bigint eikä timestamp, mutta sain tuon ansiosta kuitenkin ideasta kiinni ja onnistuin luomaan halutunlaisen kyselyn. Lisäksi hiukan kikkailin, jotta sain ulos timestamp arvon jokaisen päivän alkuun, ettei päivämäärää tarvitse PHP:n puolella muuttaa takaisin timestamp:ksi.
SELECT h.hits, h.date, UNIX_TIMESTAMP(STR_TO_DATE(h.date, '%Y.%m.%d')) AS timestamp FROM ( SELECT COUNT(*) AS hits, DATE_FORMAT(FROM_UNIXTIME(timecreated), '%Y.%m.%d') AS date FROM user_logs WHERE userid = ? AND edulevel = 2 AND realuserid IS NULL GROUP BY date) h ORDER BY h.date
Toivottavasti tämä nopeuttaa nyt sivunlatausta merkittävästi. Kiitos!!
Edit1: Toki tämä kysely ei palauta niitä päiviä, jolloin käyttäjä ei ole ollut aktiivinen sivulla, mutta tämä on nopea korjata PHP:llä.
Edit2: Tämä tekikin sivusta kerralla kolme kertaa nopeamman. Kun aiemmin kaavio saattoi ladata tuskallisen hitaasti 45 sekunnissa, niin nyt se lataa jo siedettävässä 15 sekunnissa. Mahtavaa!
Pistää vieläkin miettimään, että oletko osannut käyttää indeksejä oikein. Ei minkään kyselyn pitäisi kestää 15 sekuntia eikä varsinkaan tuota 45 sekuntia...
Ja ota nyt ihmeessä tuo nestattu kysely pois tuosta edellisessä viestissäsi näyttämästä SQL:stä. Se on täysin turha. Kaiken lisäksi vielä konvertoit ensimmäisessä kyselyssä haetun päivämäärän takaisin unixstampiksi jostain syystä. Täysin turhaa työtä. Teet kaiken työn jo nestatussa kyselyssä ja se riittää yksin.
AkeMake kirjoitti:
Kun aiemmin kaavio saattoi ladata tuskallisen hitaasti 45 sekunnissa, niin nyt se lataa jo siedettävässä 15 sekunnissa. Mahtavaa!
Itselle tästä tulee lähinnä mieleen että yksiköstä jäänyt etuliite pois. Tuskallisen hidas 45ms ja siedettävä 15ms kuulostaisi ihan loogiselta.
Hmm... Toki tämä edellinen kysely näyttää vähän sekavalta. Pyöräytin sen nyt noin, kun olin valmiiksi kikkailemassa MySQL:n kanssa ja halusin saada timestamp:t ulos jokaisesta päivästä. Pohdin, että kun tuo sisäinen kysely palauttaa sen n. 90 riviä koodia ja sen jälkeen ulkoinen kysely käsittelee ainoastaan näitä 90 riviä, ettei niiden 90 rivin käsitteleminen voi niin kauhean raskasta ja aikaavievää olla. Olin tieten väärässä. Fiksasin nyt tämän kyselyn ja hain vastaavat timestamp:t PHP:llä ja näin kaavion piirtäminen nopeutui vielä 20% lisää.
SELECT COUNT(*) AS hits, DATE_FORMAT(FROM_UNIXTIME(timecreated), '%d.%m.%Y') AS timestamp FROM user_logs WHERE userid = ? AND edulevel = 2 AND realuserid IS NULL GROUP BY timestamp
Huomaan, että koodissani on silti vielä huomattavasti optimoimisen varaa. phpMyAdmin lataa kahdessa sekunnissa datan, jonka kaavioksi piirtämiseen koodini tuhlaa 14 sekuntia. Mutta ei tässä kuitenkaan millisekunteihin voi päästä, koska jopa phpMyAdmin käyttää kaksi sekuntia datan hakemiseen 25 miljoonaa riviä (4.5GB) sisältävästä taulusta..
Koetinpa sitten tutkia, että kauanko koodillani kestää hakea tieto kannasta, mutta sain hiukan yllättävän tuloksen ulos. En tiedä kuinka tätä nyt pitäisi lukea
$time = -hrtime(true); $hits = $this->db->prepare("SELECT COUNT(*) AS hits, DATE_FORMAT(FROM_UNIXTIME(timecreated), '%d.%m.%Y') AS timestamp FROM user_logs WHERE userid = ? AND edulevel = 2 AND realuserid IS NULL GROUP BY timestamp"); $hits->execute(array($this->user['id'])); $hits = $hits->fetchAll(PDO::FETCH_ASSOC); $time = +hrtime(true); echo "Time used for query: " . ($time/1e+9) . " seconds."; //Time used for query: 43499.057854035 seconds.
Pitänee vain etsiä lisää kohtia, joissa voin optimoida koodiani.
Edit: No enpä minä osaa tästä enempää tuota optimoida. Koodi on aika simppeli, mutta jostain syystä kaavion piirtäminen silti kestää melko kauan silloin kun käyttäjä on ollut hyvin aktiivinen sivulla.
AkeMake kirjoitti:
Hmm... Toki tämä edellinen kysely näyttää vähän sekavalta. Pyöräytin sen nyt noin, kun olin valmiiksi kikkailemassa MySQL:n kanssa ja halusin saada timestamp:t ulos jokaisesta päivästä. Pohdin, että kun tuo sisäinen kysely palauttaa sen n. 90 riviä koodia ja sen jälkeen ulkoinen kysely käsittelee ainoastaan näitä 90 riviä, ettei niiden 90 rivin käsitteleminen voi niin kauhean raskasta ja aikaavievää olla.
Tietokantamoottorit yrittävät automaattisesti tehdä monimutkaisia optimointeja kyselyihin ja joskus viattomankin näköinen muutos voi sotkea heuristiikan ja suorituskyky putoaa.
Minusta yksi triviaali tapa optimoida kyselyä suurille tulosjoukoille on se, että suodit pois ns. liian vanhat rivit. Hae tulos vain esimerkiksi viimeiselle 30 tai 45 päivälle. Vaikka kyselyn lopputuloksena on vain 90 riviä, niin ilman aikasuodinta kanta joutuu käsittelemään tasan jokaisen rivin, jonka se löytää kyseiselle käyttäjälle. (Indeksin lisääminen timecreated-sarakkeeseen voi myös tällöin auttaa.)
Samoin kanta joutuu suorittamaan date_format-käsittelyn jokaiselle riville, tosin en usko että siitä tulee hirveän montaa millisekuntia lisää. Mutta se on huono asia, että käytät tätä lennosta laskettua arvoa GROUP BY -ehdossa, koska tietokanta ei kaiketi voi optimoida ryhmittelyä millään tavoin. Tästäkin syystä tuo edellä mainittu aikasuodin voisi auttaa paljon.
Ja jossain vaiheessa sitä vaan joutuu myöntämään, että taulussa on liian paljon rivejä jopa huolellisesti valituilla hakuehdoilla, jotta reaaliaikaisten tilastojen laskemisessa olisi mitään järkeä. Silloin on parempi luoda tilastoja varten uusi taulu ja laskea tilastoitavia asioita sinne etukäteen.
Kuten aiemmin jo kysyttiin, onko taulussa oikea indeksi kyselyyn?
Keittokirjassa sisällysluettelosta näkee aihepiirit (keitot, kakut, keksit), jolloin voi selata sopivaa kohtaa kirjasta. Viimeisten sivujen hakemistosta löytyy vielä tarkempi lista, millä sivuilla on juuri porkkanakakun resepti, jolloin voi selata suoraan oikealle sivulle. Aivan sama periaate pätee tietokantaan: indeksi on tärkein yksittäinen optimoinnin väline, jotta ei tarvitse selata koko taulua läpi vaan voi hypätä suoraan oikeille riveille. Tätä varten pitää tehdä indeksi niistä sarakkeista, jotka parhaiten rajaavat hakua.
Jos juuri on tarkoitus saada nopeaksi haku tietyn käyttäjän tietyn aikavälin asioista, pitää tehdä indeksi käyttäjästä ja aikaleimasta. Indeksin avulla haku ohittaa suoraan kaikki väärät käyttäjät ja väärät ajankohdat, ja myös aikojen ryhmittely luultavasti toimii nopeammin. Jossain tilanteessa ehkä kannattaa laittaa tauluun valmiiksi myös se päivämäärä (DATE), jos juuri sen nopea käsittely on tärkeää. (En ole ekspertti siinä, miten MySQL tarkalleen käyttää indeksejä eli riittääkö aikaleiman indeksointi tässä.)
Päivämäärälle järkevä muoto tiedon käsittelyssä on se YYYY-mm-dd, jota funktiot ja ehdot oletuksena ymmärtävät. Kyselyssä kannattaa pyrkiä DATE-tyyppiin eikä mielivaltaiseen tekstiin, eli mieluummin DATE(aikaleima) kuin DATE_FORMAT. Missään tapauksessa ei kannata käyttää suomalaista päivämäärämuotoa muualla kuin tiedon tulostamisen yhteydessä, koska tuollainen päivämäärä on tietokoneelle vain tekstiä ja sen käsittely on hitaampaa esimerkiksi DATE-tietotyypin (joka on melko varmasti sisäisesti jonkinlaisena lukuna ilmaistu).
Minä en usko, että sillä on mitään väliä, missä muodossa päivämäärä on jos, jos joka tapauksessa käsitellään merkkijonoja. YYYY-MM-DD-muodossa on toki se etu, että sitä voi käyttää suoraan aakkosjärjestyksen luomiseen (ja suuruusvertailuihin) ilman ylimääräistä konversiota, jos jostain syystä on pakko käyttää merkkijonomuotoista päivämäärää.
Mä uskon että ton kyselyn sais pyörähtämään kirkkaasti alle sekunnin jos indeksit ja tietotyypit on kohdillaan ja kysely on järjellinen.
Ihan jo pelkästään sillä, että käyttäjätunnus ja aika on indeksoitu, jää tietokannalle haun rajaamisen jälkeen (yhden käyttäjän viimeisen 90 päivän aikana tekemät klikkaukset) niin naurettavan pieni rivimäärä, että sitä ei voi jauhaa montaa sadasosasekuntia.
Sama pätisi vaikka tietokannassa olisi aika yyyy-mm-dd... tekstimuodossa.
Koetin muuttaa koodiani näiden ohjeiden mukaan. Se ei kovin paljoa nopeuttanut koodin suoritusta, joten lähdin tutkimaan mikä osa koodista oikein vie näin paljon aikaa. Löysin toisen yksinkertaisen hakukyselyn, jonka suorittaminen vei yli 90% kaavion piirtämiseen käytetystä ajasta. Tämä paljasti minulle, ettei indeksit tosiaankaan olleet tietokannassa oikein. Minulla on ollut hiukan virheellinen käsitys indekseistä, kun ei ole koskaan ollut tarvetta pelata isojen tietokantojen kanssa ja niinpä ne on jääneet vähemmälle huomiolle. Tuon korjattuani kaavio piirtyy nyt jopa kaikista aktiivisimman käyttäjän (23.000 lokimerkintää) kohdalla alle 2 sekunnissa. Koodin suoritus on nyt niin nopea, ettei minulla ole tämän projektin osalta tarvetta tätä nopeampaan tulokseen.
Kiitos avusta optimoinnissa, Metabolix, The Alchemist ja Grez!
Aihe on jo aika vanha, joten et voi enää vastata siihen.