Long long ago, I studied the difference between mysqlnd and libmysql, the connection driver of PHP mysql client, because of a bug in my work. It took me more than a month to write this article. First, I was too lazy, and second, I was busy with my work. I’ve only been able to get around to these things lately. Each time you do a summary, read the source code carefully, understand the meaning, test validation, to confirm these details. Each of these steps takes a long time and can’t be interrupted. Once interrupted, it takes a long time to review the context. Also deliberately forced myself to write this summary, to change their inertia.

The error “mysql Server too many Connections” occurred during the development and testing of my friends and I in full swing. After a slight investigation, WE found that the PHP background process had established a large number of links instead of closing them. The server environment is about the following PHP5.3. x, mysqli API, mysqlnd driver. The code looks like this:

  1. // Background process A
  2. / *
  3. Configuration information
  4. ‘mysql’=>array(
  5. ‘driver’=>’mysqli’,
  6. // ‘driver’=>’pdo’,
  7. // ‘driver’=>’mysql’,
  8. ‘host’ = > ‘192.168.111.111’,
  9. ‘user’=>’root’,
  10. ‘port’=>3306,
  11. ‘dbname’=>’dbname’,
  12. ‘socket’=>”,
  13. ‘pass’=>’pass’,
  14. ‘persist’=>true, // as mentioned below, this is the configuration for persistent links
  15. ),
  16. * /
  17. $config=Yaf_Registry::get(‘config’);
  18. $driver = Afx_Db_Factory::DbDriver($config[‘mysql’][‘driver’]); //mysql mysqli
  19. $driver::debug($config[‘debug’]); // Notice here
  20. $driver->setConfig($config[‘mysql’]); // Notice here
  21. Afx_Module::Instance()->setAdapter($driver); // Pay attention here. Pay attention to where you feel uncomfortable.
  22. $queue=Afx_Queue::Instance();
  23. $combat = new CombatEngine();
  24. $Role = new Role(1,true);
  25. $idle_max=isset($config[‘idle_max’])? $config[‘idle_max’]:1000;
  26. while(true)
  27. {
  28. $data = $queue->pop(MTypes::ECTYPE_COMBAT_QUEUE, 1);
  29. if(! $data){
  30. usleep(50000); // Sleep for 0.05 seconds
  31. ++$idle_count;
  32. if($idle_count>=$idle_max)
  33. {
  34. $idle_count=0;
  35. Afx_Db_Factory::ping();
  36. }
  37. continue;
  38. }
  39. $idle_count=0;
  40. $Role->setId($data[‘attacker’][‘role_id’]);
  41. $Property = $Role->getModule(‘Property’);
  42. $Mounts = $Role->getModule(‘Mounts’);
  43. / /…
  44. unset($Property, $Mounts/*….. * /);
  45. }

 

You can see the “$Property” variable and the “$Mounts” variable are frequently created and destroyed in this daemon code. The ROLE object’s getModule method is written like this

Class ROLE extends Afx_Module_Abstract {public function getModule ($member_class) {$property_name  = '__m' . ucfirst($member_class); if (! isset($this->$property_name)) { $this->$property_name = new $member_class($this); } return $this->$property_name; Class Property extends Afx_Module_Abstract {public function __construct ($mRole) {$this->__mRole = $mRole; }}Copy the code

As you can see, the getModule method just emulates the singleton by returning a new object, and they all inherit from the Afx_Module_Abstract class. The approximate code for the Afx_Module_Abstract class is as follows:

  1. abstract class Afx_Module_Abstract
  2. {
  3. public function setAdapter ($_adapter)
  4. {
  5. $this->_adapter = $_adapter;
  6. }
  7. }

 

The key code in the Afx_Module_Abstract class is as above, related to DB, setAdapter method, back to “background process A”, The setAdapter method passes the return of Afx_Db_Factory::DbDriver($config[‘ mysql ‘][‘ driver ‘]) as an argument. Continue with the code for the Afx_Db_Factory class

  1. class Afx_Db_Factory
  2. {
  3. const DB_MYSQL = ‘mysql’;
  4. const DB_MYSQLI = ‘mysqli’;
  5. const DB_PDO = ‘pdo’;
  6. public static function DbDriver ($type = self::DB_MYSQLI)
  7. {
  8. switch ($type)
  9. {
  10. case self::DB_MYSQL:
  11. $driver = Afx_Db_Mysql_Adapter::Instance();
  12. break;
  13. case self::DB_MYSQLI:
  14. $driver = Afx_Db_Mysqli_Adapter::Instance(); // This is it
  15. break;
  16. case self::DB_PDO:
  17. $driver = Afx_Db_Pdo_Adapter::Instance();
  18. break;
  19. default:
  20. break;
  21. }
  22. return $driver;
  23. }
  24. }

 

This is a factory class. Continue to look at the real DB Adapter part of the code

  1. class Afx_Db_Mysqli_Adapter implements Afx_Db_Adapter
  2. {
  3. public static function Instance ()
  4. {
  5. if (! self::$__instance instanceof Afx_Db_Mysqli_Adapter)
  6. {
  7. self::$__instance = new self(); // This is a singleton. Why is it a link to mysql?
  8. }
  9. return self::$__instance;
  10. }
  11. public function setConfig ($config)
  12. {
  13. $this->__host = $config[‘host’];
  14. / /…
  15. $this->__user = $config[‘user’];
  16. $this->__persist = $config[‘persist’];
  17. if ($this->__persist == TRUE)
  18. {
  19. $this->__host = ‘p:’ . $this->__host; // Persistent links are supported
  20. }
  21. $this->__config = $config;
  22. }
  23. private function __init ()
  24. {
  25. $this->__link = mysqli_init();
  26. $this->__link->set_opt(MYSQLI_OPT_CONNECT_TIMEOUT, $this->__timeout);
  27. $this->__link->real_connect($this->__host, $this->__user, $this->__pass, $this->__dbname, $this->__port, $this->__socket);
  28. if ($this->__link->errno == 0)
  29. {
  30. $this->__link->set_charset($this->__charset);
  31. } else
  32. {
  33. throw new Afx_Db_Exception($this->__link->error, $this->__link->errno);
  34. }
  35. }
  36. }

 

As you can see from the above code, we have enabled long links. Why do we create so many links so frequently? In order to simulate and reproduce this problem, I tried to test the local development environment, but I could not reproduce it anyway. Compared the environment, my development environment is Windows7, PHP5.3. x, mysql, libmysql, and the server is not consistent, the problem is likely to occur in mysql and mysqli API. Or libmysql and mysqlnd. So I carefully opened the PHP source code (the latest 5.3.x) and finally found the cause of these problems.

  1. // in line 907-916 of ext\mysql\php_mysql.c
  2. //mysql_connect and mysql_pconnect call persistent false or true
  3. static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent)
  4. {
  5. /* hash it up */
  6. Z_TYPE(new_le) = le_plink;
  7. new_le.ptr = mysql;
  8. // Notice the code in if below
  9. if (zend_hash_update(&EG(persistent_list), hashed_details, hashed_details_length+1, (void *) &new_le, sizeof(zend_rsrc_list_entry), NULL)==FAILURE) {
  10. free(mysql);
  11. efree(hashed_details);
  12. MYSQL_DO_CONNECT_RETURN_FALSE();
  13. }
  14. MySG(num_persistent)++;
  15. MySG(num_links)++;
  16. }

 

[mysql_pconnect] persistent_list [mysql_pconnect] [mysql_pconnect] Persistent_list looks for links to IP, PORT, USER, PASS, and CLIENT_FLAGS.

The MYSqli extension of PHP uses not only a persistent_list to store links, but also a free_link to store currently idle TCP links. When the search is performed, it also determines whether the link exists in the free free_link list. If it does, the TCP link will be used. This link is pushed into free_links only after mysqli_closez or RSHUTDOWN. (Mysqli will look for the same IP address, PORT, USER, PASS, DBNAME, SOCKET as the same id. Unlike mysql, there is no CLIENT, DBNAME and SOCKET, and IP also includes the long connection identifier “P”)

  1. Mysqli_common_connect creates a TCP connection (when mysqli_connect is called)
  2. do {
  3. if (zend_ptr_stack_num_elements(&plist->free_links)) {
  4. mysql->mysql = zend_ptr_stack_pop(&plist->free_links); // Pop it out, and the next mysqli_connect call from the same script will not find it
  5. MyG(num_inactive_persistent)–;
  6. /* reset variables */
  7. #ifndef MYSQLI_NO_CHANGE_USER_ON_PCONNECT
  8. if (! Mysqli_change_user_silent (mysqli_change_user_silent(mysql->mysql, username, passwd, dbname, passwd_len)) { Notice here mysqli_change_user_silent
  9. #else
  10. if (! mysql_ping(mysql->mysql)) {
  11. #endif
  12. #ifdef MYSQLI_USE_MYSQLND
  13. mysqlnd_restart_psession(mysql->mysql);
  14. #endif
  15. }
  16. // ext\mysqli\mysqli_api.c lines 585-615
  17. /* {{{ php_mysqli_close */
  18. void php_mysqli_close(MY_MYSQL * mysql, int close_type, int resource_status TSRMLS_DC)
  19. {
  20. if (resource_status > MYSQLI_STATUS_INITIALIZED) {
  21. MyG(num_links)–;
  22. }
  23. if (! mysql->persistent) {
  24. mysqli_close(mysql->mysql, close_type);
  25. } else {
  26. zend_rsrc_list_entry *le;
  27. if (zend_hash_find(&EG(persistent_list), mysql->hash_key, strlen(mysql->hash_key) + 1, (void **)&le) == SUCCESS) {
  28. if (Z_TYPE_P(le) == php_le_pmysqli()) {
  29. mysqli_plist_entry *plist = (mysqli_plist_entry *) le->ptr;
  30. #if defined(MYSQLI_USE_MYSQLND)
  31. mysqlnd_end_psession(mysql->mysql);
  32. #endif
  33. zend_ptr_stack_push(&plist->free_links, mysql->mysql); // This is push back, we can use it again next time
  34. MyG(num_active_persistent)–;
  35. MyG(num_inactive_persistent)++;
  36. }
  37. }
  38. mysql->persistent = FALSE;
  39. }
  40. mysql->mysql = NULL;
  41. php_clear_mysql(mysql);
  42. }
  43. / * * /}}}

 

Why does MYSQLI do this? Why can’t the same long connection be reused in the same script?

Mysqli_change_user_silent = mysqli_common_connect = mysqli_common_connect Mysqli_change_user_silent corresponds to mysql_change_user of libmysql or mysqlnd_change_user_ex of mysqlnd, Mysql_change_user (mysql_change_user, mysql_change_user, mysql_change_user, mysql_change_user, mysql_change_user, mysql_change_user, mysql_change_user, mysql_change_user, mysql_change_user, mysql_change_user, mysql_change_user, mysql_change_user, mysql_change_user) It is not PHP’s mysqli that determines The sent SQL instructions and makes a response decision), see The manual for The description of mysqli Extension and Persistent Connections. This is designed for this new feature, which mysql extensions do not support.

From the shallow understanding of these code, you can understand the difference between mysqli and mysql persistent link, this problem, may be difficult to understand, I later searched, also found a confusion because of this reason, we look at this case, may be very easy to understand. The Mysqli persistent connect doesn’t work is not specific to the underlying implementation of Mysqli. The code is as follows:

  1. $links = array();
  2. for ($i = 0; $i < 15; $i++) {
  3. $links [] = mysqli_connect (‘ p: 192.168.1.40 ‘, ‘USER’, ‘the PWD’, ‘DB’, 3306);
  4. }
  5. sleep(15);

 

The process list looks like this:

Netstat - an | grep 192.168.1.40: TCP 3306 0 0 192.168.1.6:52441 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52454 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52445 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52443 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52446 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52449 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52452 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52442 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52450 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52448 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52440 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52447 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52444 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52451 192.168.1.40:3306 ESTABLISHED TCP 0 0 192.168.1.6:52453 192.168.1.40:3306 ESTABLISHEDCopy the code

So if YOU look at the code this way, it’s a lot clearer, and it’s a lot easier to verify that I got it right, so you can see it

  1. for ($i = 0; $i < 15; $i++) {
  2. $links ($I) = mysqli_connect (‘ p: 192.168.1.40 ‘, ‘USER’, ‘the PWD’, ‘DB’, 3306);
  3. var_dump(mysqli_thread_id($links[$i])); // If you are worried about closing a new TCP connection, you can print the thread ID to see if it is the same ID
  4. mysqli_close($links[$i])
  5. }
  6. / *
  7. The results are as follows:
  8. root@cnxct:/home/cfc4n# netstat -antp |grep 3306|grep -v “php-fpm”
  9. TCP 00 192.168.61.150:55148 192.168.71.88:3306 ESTABLISHED 5100/php5
  10. root@cnxct:/var/www# /usr/bin/php5 4.php
  11. int(224218)
  12. int(224218)
  13. int(224218)
  14. int(224218)
  15. int(224218)
  16. int(224218)
  17. int(224218)
  18. int(224218)
  19. int(224218)
  20. int(224218)
  21. int(224218)
  22. int(224218)
  23. int(224218)
  24. int(224218)
  25. int(224218)
  26. * /

 

If you’re worried about closing a new TCP connection, you can print the thread ID to see if it’s the same ID. (I didn’t reply to this post, but that doesn’t make me bad.) This is the CLI mode. In FPM mode, each page request is handled by a single FPM child process. This subprocess is responsible for maintaining long links between PHP and mysql Server. If you visit this page multiple times to check whether the thread ID is the same, it may be distributed to other FPM subprocesses, resulting in different results. But eventually, each FPM subprocess maintains these TCP links separately.

In general, the difference between mysqli extension and mysql extension is the following several

  • Mysqli = ‘p:’; Mysql uses mysql_pconnect; .
  • The persistent link established by mysqli must be released after mysqli_close before it can be reused by the next request, or after RSHOTDOWN. Mysql long connection, can be reused immediately
  • Persistent links created by PDO can be reused without being closed.
  • When mysqli establishes a persistent link, it will automatically clean up the last session variable, roll back the transaction, unlock the table, release the lock, etc. Mysql will not.
  • Mysqli checks whether the persistent link identifier is IP, PORT, USER, PASS, DBNAME, SOCKET. Mysql is IP, PORT, USER, PASS, CLIENT_FLAGS

Add a mysqli_close method to the __destruct destructor of a Property class and call the mysqli_close function when it is destroyed. Push persistent links into free_links. If you think so, I can only congratulate you, wrong, the best solution is not to let it be created so many times at all. Colleague Dietoad gave a solution, the most true singleton for DB ADAPTER, and, optionally, whether to create new links. The following code:

  1. // DB FACTORY
  2. class Afx_Db_Factory
  3. {
  4. const DB_MYSQL = ‘mysql’;
  5. const DB_MYSQLI = ‘mysqli’;
  6. const DB_PDO = ‘pdo’;
  7. static $drivers = array(
  8. ‘mysql’=>array(),’mysqli’=>array(),’pdo’=>array()
  9. );
  10. Public static function DbDriver ($type = self::DB_MYSQLI, $create = FALSE
  11. {
  12. $driver = NULL;
  13. switch ($type)
  14. {
  15. case self::DB_MYSQL:
  16. $driver = Afx_Db_Mysql_Adapter::Instance($create);
  17. break;
  18. case self::DB_MYSQLI:
  19. $driver = Afx_Db_Mysqli_Adapter::Instance($create);
  20. break;
  21. case self::DB_PDO:
  22. $driver = Afx_Db_Pdo_Adapter::Instance($create);
  23. break;
  24. default:
  25. break;
  26. }
  27. self::$drivers[$type][] = $driver;
  28. return $driver;
  29. }
  30. }
  31. //mysqli adapter
  32. class Afx_Db_Mysqli_Adapter implements Afx_Db_Adapter
  33. {
  34. public static function Instance ($create = FALSE)
  35. {
  36. if ($create)
  37. {
  38. return new self(); // Add the $create argument
  39. }
  40. if (! self::$__instance instanceof Afx_Db_Mysqli_Adapter)
  41. {
  42. self::$__instance = new self();
  43. }
  44. return self::$__instance;
  45. }
  46. }

 

 

It seems so important that the development environment is consistent with the runtime environment, otherwise these problems would not have occurred. But wouldn’t it be a shame not to have such an interesting problem? (Photo: Winter)

via http://www.cnxct.com/some-differences-between-mysql-and-mysqli-of-persistent-connection/

To admire the