标签归档:数据库

PostgreSQL 入门

2010年9月20日发布了PostgreSQL 9.0
2011年9月12日发布了PostgreSQL 9.1(同步复制)
2012年9月10日发布了PostgreSQL 9.2(级联复制),CentOS 7.x默认YUM源会安装此版本。

大体应该是每年一个大版本:
2013 PostgreSQL 9.3
2014 PostgreSQL 9.4
2015 PostgreSQL 9.5
2016 PostgreSQL 9.6
2017 PostgreSQL 10.0

#CentOS 7中默认安装PostgreSQL 9.2,可以通过安装一个Yum包来获取最新的版本
yum install https://yum.postgresql.org/9.6/redhat/rhel-7-x86_64/pgdg-redhat96-9.6-3.noarch.rpm

#安装服务端和第三方贡献包和客户端
yum install postgresql96.x86_64 postgresql96-contrib.x86_64 postgresql96-server.x86_64 

#初始化数据库(默认数据目录/var/lib/pgsql/9.6/data/)
/usr/pgsql-9.6/bin/postgresql96-setup initdb

#设置开机启动和启动PostgresSQL
systemctl enable postgresql-9.6.service
systemctl start postgresql-9.6.service

#PostgresSQL安装后就可以启动,安装时自动创建了一个叫postgres的系统用户 和 一个同名的用户名和数据库(postgres)
#设置数据库需要切换到postgres用户,以postgres用户进入PostgreSQL不需要密码
[root@localhost ~]# su postgres
bash-4.2$ psql
could not change directory to "/root": Permission denied
psql (9.6.3)
Type "help" for help.

#PostgreSQL默认会创建一个叫postgres的数据库,还有两个模板数据库template0和template1。
#用户新建数据库时默认从模板数据库template1克隆出来(可以定制这个模板库)。
#而template0是一个最简化的模板库,创建数据库时,如果明确指定从此数据库中继承,则创建一个最简化的数据库。
postgres=# \l
                                  List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges   
-----------+----------+----------+-------------+-------------+-----------------------
 postgres  | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | 
 template0 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
 test      | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | 
(4 rows)

############################################################
#使用postgres用户,来生成其他用户和新数据库

\password postgres  #为postgres用户设置密码
CREATE USER dbuser WITH PASSWORD 'password'; #新建立用户和对应的密码
CREATE DATABASE exampledb OWNER dbuser;  # 创建用户的数据库
GRANT ALL PRIVILEGES ON DATABASE exampledb to dbuser;  #为用户授权

############################################################
#一般常用操作
#切换用户
su postgres
#运行psql终端
psql
#查看数据库
\l
#创建数据库
CREATE DATABASE test;
#切换数据库
\c test;
#创建数据库
CREATE TABLE t(id int primry key, name varchar(40));
#查看库中的数据表
\d
#查看具体的数据表结构
\d t
#查看更加详细的表结构
\d+ t

#显示SQL执行的时间
\timing on

postgres=# \q

登录,导入,命令:

#如果当前系统用户是dbuser,则-U dbuser可以省略
psql -U dbuser -d exampledb -h 127.0.0.1 -p 5432

#如果要导入数据库
psql exampledb < exampledb.sql

一般命令:

\h:查看SQL命令的解释,比如\h select。
\?:查看psql命令列表。
\l:列出所有数据库。
\c [database_name]:连接其他数据库。
\d:列出当前数据库的所有表格。
\d [table_name]:列出某一张表格的结构。
\du:列出所有用户。
\e:打开文本编辑器。
\conninfo:列出当前数据库和连接的信息。

基本配置:
1 监听地址和端口:

cd /var/lib/pgsql/9.6/data/
vi postgresql.conf

#listen_address = 'localhost'
#port = 5432

#############################
#如果修改了远程监听,还需要修改pg_hba.conf,添加一条记录
vi pg_hba.conf
host    all		all		all			trust

注:pg_hba.conf配置文件中的认证METHOD的ident修改为password,可以实现用账户和密码来访问数据库,其中这个认证标示有”trust”, “reject”, “md5”, “password”, “gss”, “sspi”

2 数据库日志

#日志收集
logging_collector = on

#日志目录(默认)
log_directory = 'pg_log'

#日志切换
# 每天生成一个新日志文件
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_truncate_on_ratation = off
log_rotation_age = 1d
log_roration_size = 0

#每当满一定大小则重建
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_truncate_on_ratation = off
log_rotation_age = 0
log_roration_size = 10M

#保留x天日志,循环覆盖
log_filename = 'postgresql-%a.log'
log_truncate_on_ratation = on
log_rotation_age = 1d
log_roration_size = 0

3 内存参数设置
shared_buffers: 共享内存的大小,主要用于共享数据块(缓存)
work_mem: 单个SQL执行时,排序、hash join所使用的内存
根据实际情况,可以适当调大。

————————————————————————————–
数据库逻辑结构
数据库:一个PostgreSQL实例下可以管理多个数据库。
表、索引:一个数据库中有很多表、索引。在PostgreSQL中表的术语为“Relation”(Table)。
数据行:每张表中有很多行数据。在PostgreSQL中行的术语为“Tuple”(Row)。

在PostgreSQL中,一个数据库包含一个或多个模式,模式中有包含了表、函数及操作符等数据库对象(模式可以理解为在数据库和表之间引入了一个命名空间,也即数据库-模式-表)。在PostgreSQL中不能同时访问不同数据库中的对象(需要重新连接到新库,MySQL可以切换库),而模式没有此限制(从这个概念上看,模式类似MySQL中的数据库级别的概念)。

要创建或访问模式中的对象,需要写上模式名(schema_name.table_name),通常创建和访问表时都不用指定模式,实际上这时访问的都是“public”的模式。当登录到该数据库是,如果没有特殊指定,都是以该模式(public)操作各种数据对象的。

默认,用户无法访问模式中不属于他们的对象。如要访问,模式的所有者必须在模式上赋予它们“USAGE”权限。用户也可以在别人的模式里创建对象,则需要被赋予在该模式上的”CREATE”权限。默认情况下每个人在public模式上都有CREATE和USAGE权限。

PostgreSQL支持两类临时表,一种是会话级的临时表,一种是事务级的临时表。

在PostgreSQL中,表空间实际上是为表指定一个存储的目录。在创建数据库时可以为数据库指定默认的表空间。创建表和索引时可以指定表空间,这样表、索引就可以存储到表空间对应的目录。

PostgreSQL使用多进程架构,每个连接会启动一个新的服务进程。不同于多线程方案,多进程架构通常使用共享内存来实现进程间通信,数据共享。PostgreSQL启动后,会生成一块共享内存用做数据库块的缓冲区。

数据目录结构:
使用环境变量PGDATA指向数据目录的根目录。目录的初始化是使用initdb来完成的。完成后这个数据目录下会生成三个配置文件(postgresql.conf实例配置文件,pg_hba.conf认证配置文件,pg_ident.conf认证方式ident的用户映射文件)

服务配置
1 连接配置项
listen_address
port
max_connections
superuser_reserved_connections 为超级用户连接保留的链接数
unix_socket_directory
unix_socket_group
unix_socket_permissions
bonjour
bonjour_name
tcp_keepalives_idle
tcp_keepalives_interval
tcp_keepalives_count

2 内存配置
shared_buffers 可以是专用内存的25%,比如1G,可以分250M(注意一个单位是8K,32M就是4000个8K,这里设置为4000)。
temp_buffers
work_mem
maintenance_work_mem
max_stack_depth

3 预写式日志配置
4 错误报告和日志项

访问控制配置文件
在PostgreSQL中,运行哪些IP的机器访问数据库服务器是由pg_hba.conf文件控制的。HBA的意思是host-based authentication,也就是基于主机的认证。

格式:

type	database	user	address		method

local	all		all			peer
host	all		all	127.0.0.1/32	ident
host	all		all	all		md5

第一字段是local,这个记录匹配通过UNIX域套接字的链接认证;是host时,这条记录匹配通过TCP/IP进行的连接(包括SSL和非SSL)。

认证方法有:trust、reject、md5和ident。
1 trust
无条件地允许连接。这个方法允许任何可以与PostgreSQL数据库服务器连接的用户以任意PostgresSQL数据库用户身份进行链接,不需要口令或其它任何认证
2 reject
无条件拒绝链接
3 md5
要求客户端提供一个MD5加密的口令进行认证
4 password
要求客户端提供一个未加密的口令进行认证
5 ident
运行客户端上的特定操作系统用户连接到数据库

查看系统信息的常用命令

#查看版本
select version();

#查看数据库启动时间
select pg_postmaster_start_time();

#查看加载配置文时间
select pg_conf_load_time();

#显示当前数据库时区
show timezone;

#查看当前实例有哪些数据库
psql -l #\l

#查看当前用户名
select user;

#查看Session用户
select session_user;

Laravel 命令行工具实例

大数据导入

<?php
 
namespace eBay\Console\Commands;
 
use Illuminate\Console\Command;
use DB;
use Schema;
 
class AmazonImport extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'amazon:import';
 
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Import Amazon Data';
 
    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }
 
    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        //
        $import = DB::table('amazon_imports')->where("has_import",'<',1)->orderBy('date')->first();
        if(!isset($import->id)) { return; }
         
        // 表名
        $tableName = 'amazon_import_'.$import->date;
        // 删表,如果存在
        Schema::dropIfExists($tableName);
        // 建表
        $this->builderTable($tableName);
         
        if (Schema::hasTable($tableName)){
            $s = microtime(true);
             
            // 插入数据
            $file = 'D:/11-201.txt';
            $row = 0; $dd = [];
            if (($handle = fopen($file, "r")) !== FALSE) {
                while (($data = fgetcsv($handle, 0, "\t")) !== FALSE) {
                    $row++;
                    if($row < 2) { continue; }
                    $d = [ 
                        'marketplace_id' => $data [0],
                        'gl_product_group' => $data [1],
                        'gl_product_group_desc' => $data [2],
                        'category_desc' => $data [3],
                        'subcategory_desc' => $data [4],
                        'asin' => $data [5],
                        'item_name' => $data [6],
                        'brand_name' => $data [7],
                        'ordered_gms_usd_tim' => $data [8],
                        'ordered_units_tim' => $data [9],
                        'fba_ordered_gms_usd_tim' => $data [10],
                        
                        'fba_ordered_units_tim' => $data [11],
                        'asp' => round($data [12],15),
                        
                        'gms_rank' => $data [13],
                        'gms_view_count_tim' => $data [14],
                        'units_conversion_rate' => round($data [15],15)
                    ];
                    
                    //if($row > 5000) { break; }
                    //continue;
                    
                    $dd [] = $d;
                     
                    // 合理调整,MySQL对于批量数据大小有限制
                    if (count ( $dd ) > 1999) {
                        DB::table ( $tableName )->insert ( $dd );
                        $dd = [ ];
                    }
                }
                if(!empty($dd)) {
                    DB::table ( $tableName )->insert ( $dd );
                    $dd = [ ];
                }
                $e = microtime(true);
                echo 'Total:'.(float)($e - $s)."\n";
                 
                fclose($handle);
                 
                // 
                DB::table('amazon_imports')->where('id',$import->id)->update(['has_import'=>1]);
            }
            
            // 插表完成后,更新类目
            DB::table('amazon_categories')->truncate();            
            $prefix = DB::connection()->getTablePrefix();  
            $query = "INSERT INTO ".$prefix."amazon_categories(product_group, product_group_desc, category_desc,subcategory_desc)
                SELECT gl_product_group, gl_product_group_desc, category_desc, subcategory_desc FROM `".$prefix.$tableName."` GROUP BY gl_product_group_desc,category_desc,subcategory_desc";  
            DB::statement($query);
             
            // 插表完成后,产生比对数据
            $compares = DB::table('amazon_imports')
                ->where("has_import",'>',0)
                ->where('id','!=',$import->id)
                ->orderBy('date','desc')->take(4)->get();
             
            $this->call('amazon:caculate', [
                'from' => $import->date
            ]);
        } else {
            return;
        }
    }
     
    public function builderTable($tableName)
    {
        Schema::create($tableName, function($table)
        {
            $table->increments('id');
             
            //
            $table->tinyInteger('marketplace_id')->unsigned();
            $table->smallInteger('gl_product_group')->unsigned();
            $table->string('gl_product_group_desc',32);
            $table->string('category_desc',64);
            $table->string('subcategory_desc',64);
            $table->string('asin',16);
            $table->string('item_name',128);
            $table->string('brand_name',64);
            $table->decimal('ordered_gms_usd_tim',10,2);
            $table->decimal('fba_ordered_gms_usd_tim',10,2);
            $table->smallInteger('ordered_units_tim');
            $table->smallInteger('fba_ordered_units_tim');
            $table->double('asp',20,15);
            $table->smallInteger('gms_rank');
            $table->mediumInteger('gms_view_count_tim');
            $table->double('units_conversion_rate',20,15);
            
            $table->index('asin');
        });
    }
}

执行批量计算

<?php
 
namespace eBay\Console\Commands;
 
use Illuminate\Console\Command;
use DB;
use Schema;
 
class AmazonCaculate extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'amazon:caculate {from}';
 
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Caculate Amazon Data';
 
    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }
 
    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        // 取回参数
        $date = $this->argument('from');
        if(!empty($date)) {
            $has = DB::table("amazon_imports")->where('date',$date)->where('has_import','>',0)->first();
            if(!isset($has->id)) {
                echo 'Import Date:'.$date." Do Not Import.\n";
                return;
            }
        }
        
        // 取回需要比较的日期,当前记录的前4个
        $compares = DB::table('amazon_imports')
            ->where("has_import",'>',0)
            ->where('id','<', $has->id)
            ->orderBy('date','desc')->take(4)->get();
        $nowTable = "amazon_import_".$has->date;
        
        // 原表不存在,就不要比较了
        if (!Schema::hasTable($nowTable)){
            echo "Table ".$nowTable." DO NOT Existes\n";
            return;
        }

        foreach($compares as $compare) {
            $beforeTable = "amazon_import_".$compare->date;
            // 被比较的表不存在,就到此结束,后面的咔嚓
            if (!Schema::hasTable($beforeTable)){
                break;
            }
            
            $tableName = 'amazon_'.$has->date."_".$compare->date;
            // 如果结果表已经存在,不需要重复
            if (!Schema::hasTable($tableName)){
                // 删表,如果存在
                // Schema::dropIfExists($tableName);
                // 建表
                $this->builderTable($tableName);
            
                // 批量插入
                $prefix = DB::connection()->getTablePrefix();
                $query = "INSERT INTO `".$prefix.$tableName."`
                    (marketplace_id,
                    gl_product_group,
                    gl_product_group_desc,
                    category_desc,
                    subcategory_desc,
                    asin,
                    item_name,
                    brand_name,
                    
                    ordered_gms_usd_tim,
                    ordered_gms_usd_tim_diff,
                    
                    fba_ordered_gms_usd_tim,
                    fba_ordered_gms_usd_tim_diff,
                    
                    ordered_units_tim,
                    ordered_units_tim_diff,
                    
                    fba_ordered_units_tim,
                    fba_ordered_units_tim_diff,
                    
                    asp,
                    asp_diff,
                    
                    gms_rank,
                    gms_rank_diff,
                    
                    gms_view_count_tim,
                    gms_view_count_tim_diff,
                    
                    units_conversion_rate,
                    
                    is_new)
                SELECT 
                    n.marketplace_id, 
                    n.gl_product_group, 
                    n.gl_product_group_desc, 
                    n.category_desc,
                    n.subcategory_desc,
                    n.asin,
                    n.item_name,
                    n.brand_name,
                    n.ordered_gms_usd_tim,
                    (n.ordered_gms_usd_tim - b.ordered_gms_usd_tim) AS ordered_gms_usd_tim_diff,
                    
                    n.fba_ordered_gms_usd_tim,
                    (n.fba_ordered_gms_usd_tim - b.fba_ordered_gms_usd_tim) AS fba_ordered_gms_usd_tim_diff,
                    
                    n.ordered_units_tim,
                    (n.ordered_units_tim - b.ordered_units_tim) AS ordered_units_tim_diff,
                    
                    n.fba_ordered_units_tim,
                    (n.fba_ordered_units_tim - b.fba_ordered_units_tim) AS fba_ordered_units_tim_diff,
                    
                    n.asp,
                    (n.asp - b.asp) AS asp_diff,
                    
                    n.gms_rank,
                    (n.gms_rank - b.gms_rank) AS gms_rank_diff,
                    
                    n.gms_view_count_tim,
                    (n.gms_view_count_tim - b.gms_view_count_tim) AS gms_view_count_tim_diff,
                    
                    n.units_conversion_rate,
                    
                    IF(b.id IS NULL,1,0)
                FROM `".$prefix.$nowTable."` n LEFT JOIN `".$prefix.$beforeTable."` b ON n.asin = b.asin";
                
                DB::statement($query);
            } 
        }
    }
     
    public function builderTable($tableName)
    {
        Schema::create($tableName, function($table)
        {
            $table->increments('id');
             
            //
            $table->tinyInteger('marketplace_id')->unsigned();
            $table->smallInteger('gl_product_group')->unsigned();
            $table->string('gl_product_group_desc',32);
            $table->string('category_desc',64);
            $table->string('subcategory_desc',64);
            $table->string('asin',16);
            $table->string('item_name',128);
            $table->string('brand_name',64);
            $table->decimal('ordered_gms_usd_tim',10,2);
            $table->decimal('ordered_gms_usd_tim_diff',10,2);
            
            $table->decimal('fba_ordered_gms_usd_tim',10,2);
            $table->decimal('fba_ordered_gms_usd_tim_diff',10,2);
            
            $table->integer('ordered_units_tim');
            $table->integer('ordered_units_tim_diff');
            
            $table->integer('fba_ordered_units_tim');
            $table->integer('fba_ordered_units_tim_diff');
            
            $table->double('asp',20,15);
            $table->double('asp_diff',20,15);
            
            $table->integer('gms_rank')->unsigned();
            //$table->smallInteger('gms_rank_before')->unsigned();
            $table->integer('gms_rank_diff');
            
            $table->integer('gms_view_count_tim');
            $table->integer('gms_view_count_tim_diff');
            
            $table->double('units_conversion_rate',20,15);
            $table->tinyInteger('is_new')->default(0);
            
            $table->index('asin','asin_idx');
            
            //$table->index('gl_product_group_desc');
            //$table->index('category_desc');
            //$table->index('subcategory_desc');
            $table->index(['gl_product_group_desc','category_desc','subcategory_desc'],'category_idx');
        });
    }
}

命令行工具是非常便利的,数据库的操作相比之下也省了不少功夫。

Magento数据库适配器的获取分析

数据库适配器的管理是在Mage_Core_Model_Resource类中实现的。这个类没有继承任何类。类注释有一句:Resources and connections registry and factory,说是资源和连接注册表和工厂。

一般这个类只有一份实例,它注册到全局的注册表中:

Mage::getSingleton('core/resource'); //core/resource是硬编码

getSingleton方法不是在类级别上实现单例模式,是在全局中注册一个这样的类的时候,每次首先检查全局注册表,如果存储就不会建立。这个方法内部使用Mage::getModel()来找到模型(global/models/core -> Mage_Core_Model,然后组成Mage_Core_Model_Resource) Mage_Core_Model_Resource,返回一个实例。

我们一路跟进去,看它如何获取数据库适配器的(从getConnection()方法开始):

//一般$name = core_read 或 core_read等,每个资源都可以使用自定义的读写适配器,没有指定就使用默认的
//Mage_Core_Model_Resource-----
    public function getConnection($name)   
    {
        if (isset($this->_connections[$name])) {
            $connection = $this->_connections[$name];
            if (isset($this->_skippedConnections[$name]) && !Mage::app()->getIsCacheLocked()) {
                $connection->setCacheAdapter(Mage::app()->getCache());
                unset($this->_skippedConnections[$name]);
            }
            return $connection;
        }
        $connConfig = Mage::getConfig()->getResourceConnectionConfig($name); //global->resources->default_setup->connection
		//所有没有配置的都是用默认适配器 $connConfig在很情况下是false,所以_getDefaultConnection就让它使用默认的
        if (!$connConfig) {
            $this->_connections[$name] = $this->_getDefaultConnection($name);
            return $this->_connections[$name];
        }
        if (!$connConfig->is('active', 1)) {
            return false;
        }

        $origName = $connConfig->getParent()->getName();  //global->resources->default_setup
        if (isset($this->_connections[$origName])) {      //default_setup
            $this->_connections[$name] = $this->_connections[$origName];
            return $this->_connections[$origName];
        }

        $connection = $this->_newConnection((string)$connConfig->type, $connConfig);/**** 被执行 type->pdo_mysql 获取一个Varien_Db_Adapter_Pdo_Mysql适配器

        if ($connection) {
            if (Mage::app()->getIsCacheLocked()) {
                $this->_skippedConnections[$name] = true;
            } else {
                $connection->setCacheAdapter(Mage::app()->getCache());
            }
        }

        $this->_connections[$name] = $connection;
        if ($origName !== $name) {   //default_setup != core_read
            $this->_connections[$origName] = $connection;
        }
	// $this->_connections[default_setup] = $this->_connections[core_read] = $connection

        return $connection;
    }

//Mage_Core_Model_Config  ----->
    public function getResourceConnectionConfig($name)   //$name = core_read
    {
        $config = $this->getResourceConfig($name);
        if ($config) {
            $conn = $config->connection;
            if ($conn) {
                if (!empty($conn->use)) {  //global->resources->core_read/connection/use值为default_read
                    return $this->getResourceConnectionConfig((string)$conn->use);
                } else {
                    return $conn;
                }
            }
        }
        return false;
    }// 最终返回global->resources->default_setup->connection

    public function getResourceConfig($name)   //返回global->resources->core_read
    {
        return $this->_xml->global->resources->{$name};
    }

//Mage_Core_Model_Resource-----
    protected function _newConnection($type, $config)	//$type=pdo_mysql	$config = global->resources->default_setup->connection

    {
        if ($config instanceof Mage_Core_Model_Config_Element) {
            $config = $config->asArray();
        }
        if (!is_array($config)) {
            return false;
        }

        $connection = false;
        // try to get adapter and create connection
        $className  = $this->_getConnectionAdapterClassName($type); //$type=pdo_mysql  获得Varien_Db_Adapter_Pdo_Mysql
        if ($className) {
            // define profiler settings
            $config['profiler'] = isset($config['profiler']) && $config['profiler'] != 'false';

            $connection = new $className($config);   //使用global->resources->default_setup->connection 实例化Varien_Db_Adapter_Pdo_Mysql继承自Zend_Db_Adapter_Pdo_Mysql
            if ($connection instanceof Varien_Db_Adapter_Interface) {
                // run after initialization statements
                if (!empty($config['initStatements'])) {
                    $connection->query($config['initStatements']); //开始第一个查询
                }
            } else {
                $connection = false;
            }
        }

        // try to get connection from type
        if (!$connection) {
            $typeInstance = $this->getConnectionTypeInstance($type);
            $connection = $typeInstance->getConnection($config);
            if (!$connection instanceof Varien_Db_Adapter_Interface) {
                $connection = false;
            }
        }

        return $connection;
}

通过查找XML,最终适配器保存到了$this->_connections[default_setup] = $this->_connections[core_read] = $connection 中。

在每个资源类中,都可以通过getReadConnection()方法(内部调用_getReadAdapter)来获取这个适配器对象。 当要和数据库交换时,可以使用资源类提供的包装方法(内部会调用适配器来完成),比如load() save() delete()等,也可以直接使用适配器提供的方法。

注意看getConnection()方法开始的代码,说明连接对象是会被缓存的,这样做是有好处的,具体是如何实现的就属于缓存方案的问题了。

还必须一提的是:Mage::getConfig()->getResourceConnectionConfig($name)

    public function getResourceConnectionConfig($name)
    {
        $config = $this->getResourceConfig($name);
        if ($config) {
            $conn = $config->connection;
            if ($conn) {
                if (!empty($conn->use)) {
                    return $this->getResourceConnectionConfig((string)$conn->use);
                } else {
                    return $conn;
                }
            }
        }
        return false;
}

    public function getResourceConfig($name)
    {
        return $this->_xml->global->resources->{$name};
    }

比如我传递了core_read进去,getResourceConfig返回 global/resources/core_read(如果找不到就返回false),那么getResourceConnectionConfig中就进入connection节点,然后判读这个节点是否有use,如果有再重复这个过程。然后返回global/resources/core_read/connection,这个就是作为创建适配器的参数。如果返回false就是用默认适配器,在Magento早期版本,资源模型还必须配置读写适配器,后来版本修改了这个过程,如果没有定义就使用默认的。 这个过程可以推导出如果要自定义适配器,需要在模块的config.xml文件中

<global>
	<resources>
	<模块名_read>
		<connection>
<host>localhost</host>
<username>root</username>
<password>root</password>
<dbname>magento</dbname>
<initStatements>SET NAMES utf8</initStatements>
<model>mysql4</model>
<type>pdo_mysql</type>
<pdoType/>
<active>1</active>
</connection>
	</模块名_read>
</resources>
</global>

在创建连接时最后还有这段代码

        if (!$connection) {
            $typeInstance = $this->getConnectionTypeInstance($type);
            $connection = $typeInstance->getConnection($config);
            if (!$connection instanceof Varien_Db_Adapter_Interface) {
                $connection = false;
            }
        }

如果适配器创建失败了,那么就通过类型视图获取实例,比如这里传递pdo_mysql,那么先去配置文件找global/ resource/ connection/types/类型名(这里是pdo_mysql),取出类名Mage_Core_Model_Resource_Type_Db_Pdo_Mysql,然后就直接new这个类,通过查看源代码,最终还是会实例化和type相关的适配器类,比如这里是Varien_Db_Adapter_Pdo_Mysql类。总体上看,比较粗糙。

另外,在资源模型中,最好总是使用$this->_getReadAdapter() 和 $this->_getWriteAdapter(),这两个方法命名不但非常直观,还主要在于_getReadAdapter()方法内部会判断是否启动了事务,如果启动了就返回写适配器作为读适配器。另外,$this->getReadConnection()方法是$this->_getReadAdapter()包装器。

Magento数据库抽象

可以看到Magento的数据库抽象使用了Zend Framework的Pdo_MySql抽象,不过它实现了自己的Varien_Db_Adapter_Interface接口。 Statement和Select都构架在Zend Framework的原有实现上。

从实现原理上看,过程并不复杂,但是涉及的方法非常多。肯定是无法全部记得住的。不过可以研究系统模块获取最常使用的范例代码,这样可以加速学习进度。

首先,每一个模块都可以有几部分组成,很关键的一个就是模型,每个模型可以对应有资源模型,资源模型可以操作数据库,但是能操作那些数据表(实体)是必须写入配置文件的,因为获取实体对应的表名需要搜索全局XML(也是可以不写,只要跳过到配置文件检查即可,比如直接写数据表名,而不是通过资源模型本身提供的getTable getMainTable来先从全局XML获取实体,然后才获得表名,但是这个方法会自动验证表是否存在)。比如Core模块的实体配置有(注意,分配实体一定是在资源模型中,因为只有它才需要):

           <core_resource>
                <class>Mage_Core_Model_Resource</class>
                <deprecatedNode>core_mysql4</deprecatedNode>
                <entities>
                    <config_data>
                        
                    <table>core_config_data</table></config_data>
                    <website>
                        
                    <table>core_website</table></website>
                    <store>
                        
                    <table>core_store</table></store>
                    <resource>
                        <table>core_resource</table>
                    </resource>
                    <cache>
                        <table>core_cache</table>
                    </cache>
                    <cache_tag>
                        <table>core_cache_tag</table>
                    </cache_tag>
                    <cache_option>
                        <table>core_cache_option</table>
                    </cache_option>
		</entities>
            </core_resource>

以上并没有把全部的实体都列出来。下面看一些例子(来自Mage_Core_Model_Resource_Config类的loadToXml方法)

    public function loadToXml(Mage_Core_Model_Config $xmlConfig, $condition = null)
    {
        $read = $this->_getReadAdapter();
        if (!$read) {
            return $this;
        }
        $websites = array();
        $select = $read->select()
            ->from($this->getTable('core/website'), array('website_id', 'code', 'name'));
        $rowset = $read->fetchAssoc($select);
        foreach ($rowset as $w) {
            $xmlConfig->setNode('websites/'.$w['code'].'/system/website/id', $w['website_id']);
            $xmlConfig->setNode('websites/'.$w['code'].'/system/website/name', $w['name']);
            $websites[$w['website_id']] = array('code' => $w['code']);
        }
	//………
}

这是一个很简洁的例子,使用_getReadAdapter获取适配器,符合刚才的分析。然后直接调用适配器的select()方法获取一个Varien_Db_Select对象,然后用它来构建查询,这里调用了它的from方法,第一参数指定表明,第二参数用数组指定了要查询的列。然后把这个对象直接传递给适配器的fetchAssoc方法,它就返回了一个关联数组,它包保存了从数据库返回的数据。

至于Varien_Db_Select的使用,由于它继承自Zend_Db_Select,使用上自然会保存一致(http://blog.ifeeline.com/429.html)。Varien_Db_Select实现了构建Left Join联结这样的功能,填补了Zend_Db_Select的不足。

你可以看到,大部分的SQL的操作,都是首先使用Select构建查询,然后再传递给适配器对象的。对应EAV模型,如果Select不支持构造左联结,那你就只能写SQL了。

Mage_Core_Model_Resource类实例作为一个单例存在,意味着我随时可以获取它的引用(Mage::getSingleton(‘core/resource’)),比如在我自己的控制器方法中,然后调用它的getConnection()来获取适配的引用(如果是想获取读适配就传递read,如果想获取写适配器就传递write,留空就默认返回写适配器)。适配器获取后,你可以任意操作数据库(这个是绕开Magento MVC的规定:使用模型和资源模型的方法)。

永久连接: http://blog.ifeeline.com/455.html
原创文章,转载务必保留出处。

Zend Framework的数据库适配器逻辑分析

Zend/Db.php

Zend_Db类它只包含了一个静态的所谓的工厂方法factory,关键代码:

class Zend_Db
{
    public static function factory($adapter, $config = array()){

        $adapterNamespace = 'Zend_Db_Adapter';

        $adapterName = $adapterNamespace . '_';
        $adapterName .= str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($adapter))));

        $dbAdapter = new $adapterName($config);

        if (! $dbAdapter instanceof Zend_Db_Adapter_Abstract) {
        }

        return $dbAdapter;
	}
}

比如传入’PDO_MYSQL’,最终实例化的类是Zend_Db_Adapter_Pdo_Mysql,所有的Adapter应该都应该继承自Zend_Db_Adapter_Abstract,因为这个方法会检查这个。

在Zend\Db\Adapter目录中,有Zend_Db_Adapter_Db2 Zend_Db_Adapter_Mysqli Zend_Db_Adapter_Oracle Zend_Db_Adapter_Sqlsrv 这几个类都继承自Zend_Db_Adapter_Abstract,应该都是可以使用Zend_Db类的factory方法来获取其适配器的。每个适配器还对应有一个异常类。我们这里不关注它(关注Pdo)。

首先Pdo这个包中的一个抽象类(它处于Pdo类的顶层),它也是继承Zend_Db_Adapter_Abstract的(要不然就不能用Zend_Db类的factory来获取实例),然后有针对特定数据库的适配器,比如Zend_Db_Adapter_Pdo_Pgsql, Zend_Db_Adapter_Pdo_Mysql,看下继承关系:
Zend Framework Pdo Mysql

接下看看获取一个适配器时都做了什么工作,如下:

$params = array ('host'     => '127.0.0.1',
                 'username' => 'vfeelit',
                 'password' => '******',
                 'dbname'   => 'test');

$db = Zend_Db::factory('PDO_MYSQL', $params);

事实上会new Zend_Db_Adapter_Pdo_Mysql($params)。当然,直接new这个就更加直接了。 查看Zend_Db_Adapter_Pdo_Mysql 和 Zend_Db_Adapter_Pdo_Abstract都没有构造函数实现,那必定是使用Zend_Db_Adapter_Abstract中的了,但是仔细看了下这个构造函数,其实没做什么啊,不过注意到每个需要连接到数据库的方法中,都先调用$this->_connect(),而这个方法在其中定义为抽象方法,好了,明白,它交给了具体的类去实现这个方法。 看Zend_Db_Adapter_Pdo_Abstract的_connect()方法,如下关键代码:

        try {
            $this->_connection = new PDO(
                $dsn,
                $this->_config['username'],
                $this->_config['password'],
                $this->_config['driver_options']
            );
            // set the PDO connection to perform case-folding on array keys, or not
            $this->_connection->setAttribute(PDO::ATTR_CASE, $this->_caseFolding);

            // always use exceptions.
            $this->_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

        } 

根据参数生成了一个PDO对象,保存在了$this->_connection中。可以说,这是一个优化了的调用。尽管如此,我们还是可以在具体类中覆盖这个方法,比如Zend_Db_Adapter_Pdo_Mysql对象中:

    protected function _connect()
    {
        if ($this->_connection) {
            return;
        }

        if (!empty($this->_config['charset'])) {
            $initCommand = "SET NAMES '" . $this->_config['charset'] . "'";
            $this->_config['driver_options'][1002] = $initCommand; // 1002 = PDO::MYSQL_ATTR_INIT_COMMAND
        }

        parent::_connect();
    }

它还是添加了一些自己的东西,注意这里的’charset’,对应MySQL,它决定了发起连接的字符编码,默认没有给出那就空的。

就这样就PHP的PDO进行了简单封装,并且扩展了它,提供了很多有用方法。

永久连接:http://blog.ifeeline.com/423.html
原创文章,转载务必保留出处。