Etykiety

linux (14) php (14) Laravel (9) mysql (9) Hardware (8) Windows (6) sieci (5) PowerShell (4) software (4) MariaDB (3) apache (3) html (3) Microsoft (2) bezpieczeństwo LAN (2) cygwin (2) Akcesoria (1) CMS (1) Laptop (1) Open Office (1) drupal 7 (1) gpg (1) hosting (1) jquery (1) sieci LAN (1) xml (1) zabezpieczenie sieci LAN (1)

poniedziałek, 24 kwietnia 2017

PHP a połączenie z serwerem MySQL

Php i MySQL stanowią podstawę funkcjonalności większości rozbudowanych aplikacji web oraz firmowych witryn internetowych. Każdy zawodowy twórca aplikacji web dobrze wie jak ważny jest wybór odpowiedniej platformy bazodanowej dla swoich projektów. Kluczowe znaczenie w tym zakresie ma także sposób łączenia się z serwerem baz danych z poziomu skryptów php, tak aby interakcja użytkowników z tabelami danych była efektywna i nie stanowiła zagrożeń.

PHP a połączenie z serwerem MySQL
PHP a połączenie z serwerem MySQL

Z poziomu skryptów php można ustanowić połączenie z serwerem bazodanowym MySQL na kilka sposobów. W tym artykule postaram się omówić zalety i wady każdej z tych metod. Przedstawię również porównanie podstawowych cech każdego z tych rozwiązań.

W celach dydaktycznych przygotowałem kilka przykładowych skryptów, które pojawiają się w tym artykule. Jeśli któryś z czytelników będzie miał ochotę przetestować ich działanie, do czego oczywiście gorąco zachęcam, dołączam wspólny fragment skryptów, zawierający zmienne dotyczące nazwy hosta, użytkownika, hasła i nazwy bazy danych, które należy ustawić w pierwszych liniach kodu wedle własnych potrzeb:

$host = 'localhost' ; // nazwa hosta $user = 'uzytkownik' ; // użytkownik $password = 'haslo' ; // hasło $db = 'bazadanych '; // baza danych

Z góry zaznaczam, że zawarte w artykule skrypty mają charakter czysto szkoleniowy i nie są opatrzone w zabezpieczenia charakterystyczne dla środowisk produkcyjnych.

Połączenie z serwerem MySQL za pomocą funkcji mysql_connect() - nie stosować


Łączenie się z serwerem MySQL za pomocą funkcji php mysql_connect() to na dziś dzień już archaiczny sposób uzyskania tego typu połączenia z poziomu skryptu php. Niestety spora ilość nieświadomych entuzjastów proceduralnego PHP wciąż z niego korzysta. Poniżej przedstawiony szkoleniowy skrypt php jest prostym przykładem użycia funkcji mysql_connect(). Skrypt ustanawia połączenie z serwerem, wybiera bazę danych oraz wykonuje prostą kwerendę dopasowującą użytkownika i hasło. Tego typu procedura bywa często stosowana w celach związanych z uwierzytelnianiem:

$conn = mysql_connect($host, $user, $password); if (!$conn) { die('Nie połączono: ' . mysql_error()); } $db = mysql_select_db($db, $conn); if (!$db) { die ('Nie można wybrać bazy danych: ' . mysql_error()); } $query = sprintf("SELECT * FROM users WHERE login='%s' AND pwd='%s'", mysql_real_escape_string($login), mysql_real_escape_string($pwd));

Metoda ta, choć dziś raczej przestarzała, ma jedną wielką zaletę, a mianowicie stanowi świetny materiał historyczno-szkoleniowy dla początkujących programistów. Czemu? Otoż zapoznanie się ze starymi metodami ochrony przed atakami sql-injection może nie tylko okazać się niemałą ciekawostką, lecz może być bardzo przydatne w zrozumieniu natury tych ataków. Metoda ta ma bowiem jedną poważną wadę: nie udostępnia bezpośrednio narzędzi programistycznych chroniących przed atakami typu sql-injection, przez co jej stosowanie w środowisku produkcyjnym a priori odpada. Programista musi tutaj zawsze pamiętać o zabezpieczaniu serwera MySQL poprzez funkcje mysql_real_escape_string(), co może okazać się bardzo uciążliwe, a zarazem nie zupełnie skuteczne, szczególnie przy dużej ilości kodu związanego z zapytaniami. Kolejną wadą tej funkcji jest konieczność stosowania proceduralnego kodu, co samo w sobie stanowi wielką przeszkodę w tworzeniu profesjonalnych rozwiązań.

Połączenie z serwerem MySQL za pomocą klasy PDO


Stosowanie klasy PDO w celu łączenia się z serwerem MySQL z poziomu php gwarantuje ochronę przed atakami typu sql-injection, pod warunkiem stosowania metod tej klasy zgodnie z zaleceniami podręcznika. Metody klasy PDO są łatwe do zrozumienia i bardzo proste w użyciu. Duża elastyczność tej klasy względem obsługi różnorodnych serwerów baz danych, możliwość bezpiecznego wiązania parametrów zapytań z określeniem typu parametru PDO oraz szybkość działania metod, mogą być decydującymi argumentami w kwestii wyboru odpowiedniej opcji dla projektowanej aplikacji.

Możliwość obsługi serwera bazodanowego innego niż MySQL to mocny atut elastyczności, umożliwiający generalnie bezproblemową migrację serwera bazy danych, bez konieczności dużych ingerencji w kod php aplikacji klienckiej. Głównie ze względu na tą cechę moim wyborem, szczególnie w przypadku złożonych aplikacji php, jest zawsze PDO.

Na chwilę obecną klasa php PDO wspiera sterowniki obsługujące następujące serwery bazodanowe:
  • CUBRID
  • MS SQL Server
  • Firebird
  • IBM
  • Informix
  • MySQL
  • MS SQL Server
  • Oracle
  • ODBC i DB2
  • PostgreSQL
  • SQLite
  • 4D

Jedynym mankamentem klasy PDO, oczywiście jeśli można taką cechę uznać za mankament, jest brak proceduralnego API, co może być sporym utrudnieniem dla początkujących programistów. Aby korzystać z klasy PDO programista musi mieć doświadczenie z programowaniem obiektowym. Jednak stabilność i elastyczność działania miewają przeważnie swoją cenę, a ewentualny wkład w naukę programowania obiektowego ze strony nowicjuszy php pragnących rozpocząć przygodę z PDO przyniesie tak czy inaczej ogromną satysfakcję i wielkie korzyści.

Poniżej przedstawiam typowy skrypt php wykorzystujący klasę PDO do nawiązania połączenia z bazą danych serwera MySQL w celu zapisania prostego wiersza danych do tabeli. Zobaczmy jak to się robi obiektowo za pomocą metod klasy PDO:

// ## Połączenie php PDO MySql i prosty zapis wiersza danych try { $conn = new \PDO("mysql:host={$host};dbname={$db}", $user, $password, array( PDO::MYSQL_ATTR_INIT_COMMAND=>"SET NAMES utf8", PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ) ); $stm = $conn->prepare("insert into kontrahenci (imie, nazwisko, kodKraju) values (:imie, :nazwisko, :kodKraju) "); $stm->bindValue(':imie', $_POST['imie'], PDO::PARAM_STR); $stm->bindValue(':nazwisko', $_POST['nazwisko'], PDO::PARAM_STR); $stm->bindValue(':kodKraju', $_POST['kodKraju'], PDO::PARAM_INT); $stm->execute(); } catch (PDOException $e) { echo "Wystąpił błąd PDO"; }


Połączenie z serwerem MySQL za pomocą MySQLi


MySQLi jest nie tylko świetnym rozwiązaniem dla początkujących programistów, ze względu na możliwość wyboru zarówno obiektowego jak i proceduralnego API, ale zapewnia optymalną ochronę przed atakami sql-injection, podobnie do klasy PDO. Kolejnym atutem MySQLi jest jego szybkość i w tym zakresie MySQLi ma przewagę nad PDO. Niestety MySQLi jest o wiele trudniejsze do opanowania niż PDO. Dodatkowym mankamentem, w przeciwieństwie do możliwości jakie oferuje klasa PDO, jest tutaj brak możliwości wiązania parametrów zapytań po nazwach. Przykładowy skrypt pokazuje jak nawiązać połączenie korzystając z MySQLi w zakresie API obiektowego oraz jak wygląda tutaj kwestia wiązania parametrów kwerend:

mysqli_report(MYSQLI_REPORT_STRICT); try { $conn = new mysqli($host, $user, $password, $db) ; $stm = $conn->prepare(" insert into kontrahenci (imie, nazwisko, kodKraju) values (?, ?, ?) "); $stm->bind_param('Jan', 'Kowalski', 1); $stm->execute(); } catch (Exception $e ) { echo "Wystąpił błąd Mysqli"; exit; }


Co wybrać


Najlepszy wybór zazwyczaj wywodzi się z doświadczenia. Aby jednak czytelnikom ułatwić zrozumienie generalnych możliwości każdego z omawianych tutaj rozwiązań, przygotowałem poniższą tabelę:
Porównanie cech mysql_connect(), PDO oraz MySQLi
Cecha mysql_connect() PDO MySQLi
Ochrona przed sql-injection NIE TAK  TAK 
Styl proceduralny  TAK  NIE  TAK
 Styl obiektowy NIE  TAK  TAK 
Różne sterowniki (nie tylko MySQL) NIE  TAK NIE
 Wartości parametrów po nazwie NIE  TAK  NIE 
 Łatwość użytkowania TAK  TAK  NIE 
 Przygotowywanie zapytań po stronie klienta NIE  TAK  NIE 
 Przygotowywanie zapytań po stronie serwera NIE  TAK  TAK 
 PHP 5.x TAK  TAK  TAK 
 PHP 7.x NIE  TAK  TAK 


Test szybkości kwerend oraz kody php


Osoby pragnące sprawdzić szybkość działania opisanych tutaj rozwiązań, zachęcam do przetestowania mojego skryptu, który mierzy czas wykonania dowolnej kwerendy oraz czas wykonania kodu php odpowiedzialnego za nawiązanie połączenia z serwerem, ustawienie bazy danych, wykonanie zapytania i zwrócenie wyników w 3 omawianych wariantach łączenia się z serwerem MySQL:

// Porównanie szybkości odczytu danych z serwera MySQL // dla mysql_connect(), PDO oraz MySQLi // Autor: informatyka-porady.blogspot.com // Licencja: friko // // Tutaj należy ustawić odpowiednie dla własnego serwera wartości $host = ""; // np. localhost lub 127.0.0.1 $user = ""; $password = ""; $db = ""; echo "<pre>"; // Kwerenda odczytu lub zapisu danych bez wiązania parametrów zapytania // Można klasycznie użyć np. "SELECT BENCHMARK(1000000,ENCODE('hello',RAND()))" // Lub zastosować dowolną kwerendę... // W celach testowych warto wyłączyć caching // Stosując w kwerendach frazę SQL_NO_CACHE: np. "SELECT SQL_NO_CACHE * from abc" $query = ""; // mysql_connect() $time_start = microtime(true); $easyconn = mysql_connect($host, $user, $password); if (!$easyconn) { die('Nie udało się nawiązać połączenia: ' . mysql_error()); } $easyconn_db = mysql_select_db($db, $easyconn); if (!$easyconn_db) { die ('Nie można wybrać bazy danych: ' . mysql_error()); } mysql_query('set profiling=1'); mysql_query($query); $q = mysql_query('show profiles'); $result = mysql_fetch_assoc($q); $time_end = microtime(true); echo "Połączenie mysql_connect(). Czas wykonania zapytania: ".$result['Duration']."\n"; echo "Czas wykonania skryptu php zapytania sql:".(($time_end - $time_start)/60)."\n\n"; $easyconn = null; try { // PDO $time_start = microtime(true); $conn = new \PDO("mysql:host={$host};dbname={$db}", $user, $password, array( PDO::MYSQL_ATTR_INIT_COMMAND=>"SET NAMES utf8", PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ) ); $conn->query('set profiling=1'); $conn->query($query); $data = $conn->query('show profiles'); $result = $data->fetchAll(PDO::FETCH_ASSOC); $time_end = microtime(true); echo "Połączenie PDO. Czas wykonania zapytania:".$result[0]['Duration']."\n"; echo "Czas wykonania skryptu php zapytania sql:".(($time_end - $time_start)/60)."\n\n"; $conn = null; // MySQLi mysqli_report(MYSQLI_REPORT_STRICT); $time_start = microtime(true); $conn = new mysqli($host, $user, $password, $db) ; $conn->query('set profiling=1'); $conn->query($query); $data = $conn->query('show profiles'); $result = $data->fetch_assoc(); $time_end = microtime(true); echo "Połączenie MySQLi. Czas wykonania zapytania:".$result['Duration']."\n"; echo "Czas wykonania skryptu php zapytania sql:".(($time_end - $time_start)/60)."\n\n"; $conn = null; } catch (PDOException $e) { echo "Nie udało się nawiązać połączenia PDO"; } echo "</pre>";

Na moim serwerze wykonanie powyższego skryptu z zapytaniem SQL

SELECT BENCHMARK(1000000,ENCODE('hello',RAND()))

poskutowało wyświetleniem poniżej przedstawionych wyników:

Połączenie mysql_connect(). Czas wykonania zapytania: 17.36685392 Czas wykonania skryptu php zapytania sql:0.28945664962133 Połączenie PDO. Czas wykonania zapytania:17.36803179 Czas wykonania skryptu php zapytania sql:0.28948030074437 Połączenie MySQLi. Czas wykonania zapytania:17.38116807 Czas wykonania skryptu php zapytania sql:0.28969765106837

A więc MySQLi okazało się najbardziej powolne :-)

Czy któryś z czytelników zna inny sposób na wykonanie tego typu testów, oczywiście z poziomu skryptu PHP? Jeśli tak, to będę wdzięczny za komentarze.

Brak komentarzy:

Prześlij komentarz

Dodaj komentarz