Home >Backend Development >PHP Tutorial >Detailed explanation of the implementation method of reading and writing separation in CodeIgniter, codeigniter reading and writing_PHP tutorial
This article describes the implementation method of CodeIgniter reading and writing separation. Share it with everyone for your reference, the details are as follows:
The current server is only a master-slave one, and no read-write separation is configured. The function of read-write separation can only be implemented by the program. Here we mainly talk about how Codeigniter implements read-write separation, and the following two points need to be met:
1. The separation of reading and writing should be transparent to development.
There are solutions on the Internet to realize the separation of reading and writing by manually loading multiple DBs. Such separation is too closely related to the business, increases the difficulty of development and is not conducive to maintenance. What we have to do is to read the heavy database by default, and write to the main database. Library, separation of reading and writing is transparent to developers
2. Simple configuration.
Retain the existing configuration method and configure read-write separation by adding an array, without affecting the original usage method.
Thoughts
1. The simplest idea to achieve read-write separation is to determine whether to insert into the main library or read from the slave library according to the query statement where the query is finally executed, so this function needs to be found.
2. The database should be connected only once, and the link should be reusable next time. That is to say, all read operations are available even after re-database, and there is no need to connect again. The same is true for the main database. So we can put the link in the CI super object.
3. The master-slave judgment is based on the final executed SQL statement, so the automatic link autoinit parameter in the database configuration does not need to be set to true. If it is connected by default and does not need to operate the library, it will waste resources. .
4. You can use $this->db in the model to directly operate the query without any other adjustments.
5. Do not modify files under system directly
Implement reading and writing separation
CI’s DB class is fixed to read files under system, which we can achieve through appropriate rewriting. The first is Loader.php, in which the database method is used to load database objects. It refers to the system/database/DB.php file. We determine whether there is a custom DB.php file and import it if it exists.
Rewrite Loader.php
public function database($params = '', $return = FALSE, $active_record = NULL) { $CI =& get_instance(); if (class_exists('CI_DB') AND $return == FALSE AND $active_record == NULL AND isset($CI->db) AND is_object($CI->db)) { return FALSE; } if(file_exists(APPPATH.'core/database/DB.php')) { require_once(APPPATH.'core/database/DB.php'); } else { require_once(BASEPATH.'database/DB.php'); } if ($return === TRUE) { return DB($params, $active_record); } $CI->db = ''; $CI->db =& DB($params, $active_record); } /* End of file MY_Loader.php */ /* Location: ./application/core/MY_Loader.php */
Then we create database/DB.php under application/core. This file has only one DB method, which is used to read the configuration file and perform initialization work. There are also two places that need to be rewritten:
Rewrite DB.php
//DB_driver.php为所有驱动方式的父类,最终执行查询的方法在该文件中 //第一处修改为判断自定义的DB_driver.php是否存在,存在则引入 if(file_exists(APPPATH.'core/database/DB_driver.php')) { require_once(APPPATH.'core/database/DB_driver.php'); } else { require_once(BASEPATH.'database/DB_driver.php'); } //第二处 $params['dbdriver'].'_driver.php' 该文件可不调整,实际未修改该文件,为了方便调试也加了 //mysql驱动对应system/database/drivers/mysql/mysql_driver.php,mysql的最后执行方法在这里, //包括数据库打开和关闭、查询等,可以该文件增加相应日志查看读写分离是否有效 if(file_exists(APPPATH.'core/database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php')) { require_once(APPPATH.'core/database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php'); } else { require_once(BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php'); } //将当前group name赋值给param,方便判断 $params['group_name'] = $active_group; /* End of file DB.php */ /* Location: ./application/core/database/DB.php */
The adjustment of the entire DB.php is basically the introduction of files. The introduction of group name is to facilitate subsequent judgment. If it is not introduced, it can be configured through the host and database names. If you want to force autoint to be turned off, you can delete the following paragraph in DB.php:
if ($DB->autoinit == TRUE) { $DB->initialize(); }
The next step is the core part. Separation of reading and writing is achieved based on query statements.
The simple_query method in DB_driver.php can be understood as the final method of executing the SQL statement. We can judge the database link here.
Rewrite DB_driver.php
//增加属性,表示当前组 var $active_group; //增加属性,使用强制使用主库 var $db_force_master; //该方法为执行查询的必经之地,我们可以在这里根据类型判断使用哪个链接。 function simple_query($sql) { //load_db_proxy_setting方法这里写在helper中,也可以直接写在该类中,写在helper中则需要在自动加载中加载该helper //该方法的作用是根据当前链接group name 和sql读写类型,以及是否强制使用主库判断使用哪个链接。使用主库 OR 重库? //主重库的负载均衡,单点故障都可以在这里考虑。也就是根据3个参数返回一个可用的配置数组。 $proxy_setting = load_db_proxy_setting($this->group_name, $this->is_write_type($sql), $this->db_force_master); if(is_array($proxy_setting) && ! empty($proxy_setting)) { $proxy_setting_key = key($proxy_setting); $this->group_name = $proxy_setting_key; //将当前配置重新赋值给类的属性,如果database.php配置的是DSN字符串,则需要在load_db_proxy_setting中做处理 foreach($proxy_setting[$proxy_setting_key] as $key => $val) { $this->$key = $val; } //定义链接ID为conn_前缀 $proxy_conn_id = 'conn_'.$proxy_setting_key; $CI = & get_instance(); //赋值给CI超级对象或者直接从CI超级对象中读取 if(isset($CI->$proxy_conn_id) && is_resource($CI->$proxy_conn_id)) { $this->conn_id = $CI->$proxy_conn_id; } else { $this->conn_id = false; $this->initialize(); $CI->$proxy_conn_id = $this->conn_id; } //强制只一次有效,下次查询失效,防止一直强制主库 $this->reset_force_master(); } if ( ! $this->conn_id) { $this->initialize(); } return $this->_execute($sql); } //某些情况会强制使用主库,先执行该方法即可 public function force_master() { $this->db_force_master = TRUE; } public function reset_force_master() { $this->db_force_master = FALSE; } /* End of file DB_driver.php */ /* Location: ./application/core/database/DB_driver.php */
At this point, the separation of reading and writing has been basically realized, but to do things well, the linked database object needs to be closed. You can close the connection after the execution is completed in the public controller.
There is also a close method in DB_driver.php. Can you consider whether it can be closed in this method? I think this is not possible here.
Close database link
class MY_Controller extends CI_Controller { public function __construct() { parent::__construct(); $this->load->service('common/helper_service', NULL, 'helper'); //下面这段为关闭CI超级对象中的数据库对象和数据库链接,db的对象Codeigniter.php中会关闭 register_shutdown_function(function(){ foreach(get_object_vars($this) as $key => $val) { if(substr($key, 0, 3) == 'db_' && is_object($this->{$key}) && method_exists($this->{$key}, 'close')) { $this->{$key}->close(); } if(substr($key, 0, 5) == 'conn_' && is_resource($this->{$key})) { $this->db->_close($val); unset($this->{$key}); } } }); } } /* End of file MY_Controller.php */ /* Location: ./application/core/MY_Controller.php */
Used in the model, in order to make $this->db available in each model and not connect to the database multiple times, the link is also placed in the CI super object. This can be done even if there is no separation of reading and writing. It can easily connect multiple DBs. If you want to use other libraries for a specific model, you only need to pass in the group name in the constructor.
Model adjustment
public function __construct($group_name = '') { parent::__construct(); $this->initDb($group_name); } private function initDb($group_name = '') { $db_conn_name = $this->getDbName($group_name); $CI = & get_instance(); if(isset($CI->{$db_conn_name}) && is_object($CI->{$db_conn_name})) { $this->db = $CI->{$db_conn_name}; } else { $CI->{$db_conn_name} = $this->db = $this->load->database($group_name, TRUE); } } private function getDbName($group_name = '') { if($group_name == '') { $db_conn_name = 'db'; } else { $db_conn_name = 'db_'.$group_name; } return $db_conn_name; } /* End of file MY_Model.php */ /* Location: ./application/core/MY_Model.php */
The final database configuration method only needs to configure an array based on the original one. Whether to use dual masters or one master and multiple slaves depends on the configuration here. At first I thought of adding key names directly to the original configuration, but the corresponding relationship between master and slave is still not so clear. The definition here determines the implementation of load_db_proxy_setting.
database.php configuration
$_master_slave_relation = array( 'default_master' => array('default_slave1', 'default_slave2', 'default_slave3'), ); /* End of file database.php */ /* Location: ./application/config/database.php */
The initial database link is not placed in the CI super object. It is found that the link will be opened every time when loading multiple models, so be sure to test after completing the read-write separation. You can check whether the database link is opened and closed. Executed as expected (methods correspond to db_connect and _close in application/core/database/drivers/mysql/mysql_driver.php). The two most important points in the entire adjustment process are the simple_query method and closing the database connection in the constructor. The adjustment in the model is to make it easier to link multiple libraries. It is also adjusted in this way when the separation of reading and writing is not implemented. Commonly used methods are separated into a file and MY_Model inherits them.
There are many middlewares that implement MYSQL reading and writing separation. When these are not used, reading and writing separation can be achieved through program control. Of course, this only implements the separation of reading and writing, and you can force the use of the main library. If you want a better allocation method, you can think about the allocation method in load_db_proxy_setting.
Readers who are interested in more content related to the CodeIgniter framework can check out the special topics on this site: "Introduction to codeigniter tutorial" and "Advanced tutorial on CI (CodeIgniter) framework".
I hope this article will be helpful to everyone’s PHP program design based on the CodeIgniter framework.