Avatar billede dmg Nybegynder
13. december 2012 - 01:16 Der er 9 kommentarer og
1 løsning

Hjaelp mit system laver flere tusinde TCP forbindelser til min mysql database

Sorry er paa US keyboard.


Jeg bruger en OOP infrastruktur til lettere at holde styr paa min kode. Problemet er den er for langsom og laver for mange tcp forbindelser til databasen.
Til hvert object anvender jeg 2 klasser fordelt i 2 filer og 1 fil hvor disse klasser og metoder bliver andvendt og en database klasse til at forbinde til databasen.

F.eks. Objektet Department vil bestor af disse 4 filer:

database.php ---> forbinder til databasen, returnere resultatet og lukker forbindelsen.
department.php ---> get og set'ere.
departmenthandler.php ---> Inkludere database.php klassen og indeholder metoder til at hente data ind og ud af databasen.
DepartmentViewer.php ---> Denne fil bruger departmenthandler.php til at hente data ud af databasen og derefter kan man formatere outputtet som man vil.


DepartmentHandler'en:
Hvis jeg henter ET ID ud af databasen via readDepartmentById($iId) laver den bare 1 forbindelse og gemmer data'en i et array og lukker forbindelsen.

Problemet er hvis jeg henter mere en 2 ID'er ud af databasen f.eks. readAllDepartments() som bruger metoden executeMultiRowQuery i filen Database.php eksekveres een SQL query som gemmer ID'erne i et array. Derefter i foreach lykken for hvert ID kalder jeg readDepartmentById($iId) som let og struktureret anvender mine getter og setter metoder i filen deaprtment.php til at danne et object array som bliver returneret. Men for hvert ID i foreach lykken bliver metoden executeSingleRowQuery i Database.php kaldt, som oabner en forbindelse, eksekvere en query og lukker forbindelsen.
 

Problemet er at hvis der er flere tusinde id'er bliver siden langsom og hvis jeg monitorer mine TCP forbindelser via en kommand prompt "netstat -n -a |findstr 3306" er der flere tusinde forbindelser der bliver obnet og lukket paa meget kort tid. Eftersom windows som standard kun kan ha' 5000 forbindelser korende paa samme tid, blev jeg nodt til at modificerer registrerings-databasen til nu 65000 tcp forbindelser kan kore paa samme tid.
Det virker men selvfolgelig er dette ikke optimalt og det er hovedorsagen til at jeg meget gerne vil ha hjalp til at fixe dette.

Problemet er at systemet allerede er meget stort og jeg vil helst undgaa at lave for mange rettelser. Alle klasserne er lavet paa samme moede.
Jeg hoeber der er en moede hvorpaa selve klasse structuren kan forblive den samme saa jeg ikke behoever at kalde min klasse paa en anden moede.

Jeg har haft svaert ved at forklare problemet saa jeg undskylder hvis det lyder meget forvirende. Er villig til at oppe points til een der kan give et kode eksempel paa en loesning for har virkelig brug for en loesning hurtigst muligt.

________________________________________________________________________________________________________________
****************
* database.php *
****************

class Database
{
        private $sHost;
        private $sUser;
        private $sPassword;
        private $sDatabase;
        private $link;

        function __construct()
        {
                $this->configReader();
        }

        // executeMultiRowQuery bruges til at returnere et two-demisionelle array
        public function executeMultiRowQuery($sSql)
        {
                $this->openConn();
                $aaResult = mysql_query($sSql);
                if (!$aaResult)
                {
                        die('<p>Invalid query: '.mysql_error()."</p>");
                }
                while ($aRow = mysql_fetch_array($aaResult, MYSQL_BOTH))
                {
                        $aaEndResult[] = $aRow;
                }
                $iResultCount = mysql_num_rows($aaResult);
                mysql_free_result($aaResult);
                $this->closeConn();
                if ($iResultCount > 0)
                        return $aaEndResult;
                return null;
        }

        // executeSingleRowQuery bruges til at returnere et array.
        public function executeSingleRowQuery($sSql)
        {
                $this->openConn();
                $aResult = mysql_query($sSql);
                if (!$aResult)
                {
                        die('<p>Invalid query: '.mysql_error()."</p>");
                }
                while ($aRow = mysql_fetch_array($aResult, MYSQL_BOTH))
                {
                        $aEndResult = $aRow;
                }
                $iResultCount = mysql_num_rows($aResult);
                mysql_free_result($aResult);
                $this->closeConn();
                if ($iResultCount > 0)
                        return $aEndResult;
                return null;
        }

        // executeNonQuery returnerer ikke noget, men bruges til insert, update og delete.
        public function executeNonQuery($sSql)
        {
                $this->openConn();
                $aResult = mysql_query($sSql);
                if (!$aResult)
                {
                        die('<p>Invalid query: '.mysql_error()."</p>");
                }
                $iAffectedRows = mysql_affected_rows($this->link);
                $this->closeConn();
                return $iAffectedRows;
        }

        // safeString bruges til at køre strengen igennem med. Det er for sikkerhedsmæssige årsager at denne bruges,
        // så man ikke kan lave SQL injection attack.
        public function safeString($value)
        {
                if (get_magic_quotes_gpc())
                {
                        $value = stripslashes($value);
                }
                if (!is_int($value))
                {
                        $this->openConn();
                        $value = "'".mysql_real_escape_string($value)."'";
                        $this->closeConn();
                }
                return $value;
        }

        // openConn bruges til at åbne forbindelse til databasen.
        private function openConn()
        {
                $this->link = mysql_connect($this->sHost, $this->sUser, $this->sPassword) or die("<p>Could not connect : ".mysql_error()."</p>");
                mysql_select_db($this->sDatabase) or die("<p>Could not select database</p>");
        }

        // closeConn lukker forbindelsen til serveren.
        private function closeConn()
        {
                mysql_close($this->link);
        }

        // Læser konfigurationsfilen conf.php
        private function configReader()
        {
                include $_SERVER["DOCUMENT_ROOT"] ."/conf.php";
                $this->sHost = $sDbHost;
                $this->sUser = $sDbUser;
                $this->sPassword = $sDbPassword;
                $this->sDatabase = $sDbDatabase;
        }
}

______________________________________________________________________________________________________________________________________________________________________________________________


******************
* department.php *
******************

class Department
{

    private $iDepartment_Id;
    private $sDepartment_Name;
   
    function __construct()
    {
   
    }
   
    public function getDepartment_Id() {return $this->iDepartment_Id;}
    public function setDepartment_Id($value) {$this->iDepartment_Id = $value;}
   
    public function getDepartment_Name() {return $this->sDepartment_Name;}
    public function setDepartment_Name($value) {$this->sDepartment_Name = $value;}
}
________________________________________________________________________________________________________


*************************
* DepartmentHandler.php *
*************************


include_once "classes/database.php";
include_once "classes/departments/department.php";
class DepartmentHandler

{
        private $oDepartment;
        private $oaDepartments;
        private $oDb;

        function __construct()
        {
                $this->oDepartment = new Department();
                $this->oDb = new Database();
        }


        public function readDepartmentById($iId)
        {
                $this->oDepartment = new Department();

                $sSql = "SELECT * FROM configuration_departments WHERE department_id = ".$iId."";
                $aResult = $this->oDb->executeSingleRowQuery($sSql);

                $this->oDepartment->setDepartment_Id($iId);
                $this->oDepartment->setDepartment_Name($aResult['department_name']);

               
                return $this->oDepartment;
        }


        public function readAllDepartments()
        {
                $this->oaDepartments=array();
                $sSql = "SELECT department_id FROM configuration_departments ORDER BY department_name";
                $aaDepartmentIds = $this->oDb->executeMultiRowQuery($sSql);
        if($aaDepartmentIds)
        {
                foreach ($aaDepartmentIds as $aDepartmentId)
                    {
                            $this->oaDepartments[] = $this->readDepartmentById($aDepartmentId['department_id']);
                    }
        }
                return $this->oaDepartments;
        }
}
_____________________________________________________________________________________________________________________________________________________________


************************
* DepartmentViewer.php *
************************


include_once "classes/departments/departmenthandler.php";

print "<table border='1'><tr><td><b>Department</b></td><td><b>Email Address</b></td></tr>";

$DepartmentHandler = new DepartmentHandler();
foreach($DepartmentHandler->readAllDepartments() as $value)
{
    print "<tr><td>".$value->getDepartment_Name()."</td></tr>";
}

print "</table>";

_______________________________________________________________________________________________________________
Avatar billede arne_v Ekspert
13. december 2012 - 02:03 #1
Der er flere loesninger.

Den som kraever faerrest rettelse maa vaere at bruge en connection pool (som hedder persistent connection i PHP terminologi fordi visse maade at integrere PHP med web server ikke kan lave en aegte connection pool).

http://php.net/manual/en/function.mysql-pconnect.php

Eller bedre en af:

http://php.net/manual/en/mysqli.persistconns.php

http://php.net/manual/en/pdo.connections.php  (PDO::ATTR_PERSISTENT)
Avatar billede arne_v Ekspert
13. december 2012 - 02:06 #2
Den loesning som vil give bedst performance er lade readAllDepartments() eller en ny readAllDepartmentsWithFullInfo() hente alle alle data.
Avatar billede arne_v Ekspert
13. december 2012 - 02:07 #3
Den loesning som vil give dig bedst mulighed for at skyde dig selv i foden er at holde en enkelt connection aaben mellem flere kald.
Avatar billede arne_v Ekspert
13. december 2012 - 02:09 #4
Og hvis nogen undrer sig over at #2 ikke var #1: det er vist ret simpelt i dette tilfaelde, men hvis man skal have data fra M tabeller ind i instanser af N klasser med M!=N og man gerne vil have mulighed for at lazyloade noget, saa kan det nemt blive noget ret komplekst kode.
Avatar billede dmg Nybegynder
14. december 2012 - 00:34 #5
Hej arne_v

Mange tak for hintet og options.
Jeg har modificeret koden som du sagde ved at hente alle alle data via readAllDepartmentsFULL_INFO() uden at bruge readDepartmentById() og det virker meget meget bedre. Tusind tak!

public function readAllDepartmentsFULL_INFO()
{
    $this->oaDepartments=array();
    $sSql = "SELECT * FROM configuration_departments ORDER BY department_name";
    $aaDepartments = $this->oDb->executeMultiRowQuery($sSql);
    if($aaDepartments)
    {
        foreach ($aaDepartments as $aDepartment)
        {
            $this->oDepartment = new Department();
                       
            $this->oDepartment->setDepartment_Id($aDepartment['department_id']);
            $this->oDepartment->setDepartment_Name($aDepartment['department_name']);
           
            $this->oaDepartments[] = $this->oDepartment;
        }
    }
    return $this->oaDepartments;
}

Men jeg glemte en vigtig factor.
Min database structur er bygget op paa id'er.

Department Tabel:
department_id 
department_name

Row Tabel:
row_id
row_name

Rack Tabel:
rack_id
rack_name

Manufacture og Supplier Tabel:
ms_id
ms_name
ms_address
ms_contact_details

Equipment Tabel: ---> dette er hoved tabellen som samler alt data'en.
equipment_id
equipment_name
equipment_row_id
equipment_rack_id
equipment_ms_id
equipment_department_id

Noer jeg soeger efter en liste med hundrede vis af equipments, anvender jeg en foreach lykke som kalder metoden readXXXbyId() for hver column id. readXXXbyId() virker paa samme moede ligesom readDepartmentById metoden.
exempel:

foreach($SqlSearch as $Equipment)
{
    $Equipment_id = $Equipment->getEquipment_id();
    $Equipment_name = $Equipment->getEquipment_Name();
    $Equipment_Row = $RowHandler->readRowById($Equipment->getEquipment_Row_id()); // oebner og lukker en forbindelse
    $Equipment_Rack = $RackHandler->readRackById($Equipment->getEquipment_Rack_id()); // oebner og lukker en forbindelse
    $Equipment_MS = $MS->readMSById($Equipment->getEquipment_Manufacture_id()); // oebner og lukker en forbindelse
    $Equipment_Department = $Department->readDepartmentById($Equipment->getEquipment_Department_id()); // oebner og lukker en forbindelse
}

Dvs hvis jeg henter en liste med 500 equipments anvender jeg kun en TCP forbindelse nu istedet for 500 ved at bruge din loesning. Men paa min HTML presentations side bruger jeg foreach lykken foroven til at udtraekke hvad
disse id'er betyder. Saa i eksemplet foroven er der faktisk 5 forbindelser. Hvis der er 500 equipments vil der i eksemplet foroven vaere 2001 forbindelser.
Jeg kan slet ikke se hvordan dette kan loeses nemt.... Men dette er klart hovedoarsagen til de mange forbindelser.
Hoeber du har en go ide :-)
Avatar billede dmg Nybegynder
14. december 2012 - 01:10 #6
Sad lige og soegte lidt rundt paa google og fandt mysqli man kan bruge istedet for mysql extension i PHP.

http://se2.php.net/manual/en/mysqli.multi-query.php

Det ser ud til at man kan lave multiple queries. Er det evt noget du kender til eller vil anbefale?
Avatar billede arne_v Ekspert
14. december 2012 - 01:18 #7
Hvis data er i flere tabeller saa loader du stadigvaek det hele med en enkelt query.

SELECT * FROM t1 JOIN t2 ON t1.x=t2.x JOIN t3 on t1.y=t3.y

Og saa returnerer du et array af instanser af klassen T1 som har et field med en instans af type T2 og et field med en instans af type T3.
Avatar billede arne_v Ekspert
14. december 2012 - 01:19 #8
multi_query kan muligvis spare lidt, men jeg tror at det er marginalt.
Avatar billede dmg Nybegynder
14. december 2012 - 19:34 #9
Hej arne_v,

Så jeg på iPhone og har dansk tastatur :-)
Først og fremmest kan slet ik beskrive hvor imponerende din løsning er. Jeg har bygget mine systemer på denne måde i flere år og har altid ønsket jeg kunne løse dette problem og din hjælp har fixet min hovedpine.
Så tusind mange tak.

Jeg skal på ferie i en uge og vil først ha tid til at teste derefter.
Men jeg fik modificeret min query til at oversætte id'erne ved hjælp af left outer join som jeg husker. Har stadig et par spørgsmål men kan først stille dem mår jeg kommer tilbage.
Eftersom du har løst hovedspørgsmålet vil du fortrække jeg gi'r points og lukker eller ville det være ok hvis jeg ventede en uge og stillede et par ekstra :-)

Igen mange tak!
Avatar billede arne_v Ekspert
15. december 2012 - 01:26 #10
svar
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Vi tilbyder markedets bedste kurser inden for webudvikling

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester