2010年8月17日星期二

SQLite PHP开发 (转)

目标读者


本文是为那些对学习SQLite 扩展类库感兴趣的PHP程序员而写的.本文将向读者介绍PHP SQLite扩展类库的功能,并且对SQLite与其它数据库系统的优势关系做了概要介绍.

假设读者对PHP和SQL有个基本的了解.如果有过MySQL或者PostgreSQL的经验那就是最好不过了.



介绍


在近几个月以来,你也许听过一个新的PHP数据库扩展类库SQLite.好多人认为SQLite是自有面包片以来最好的 东东,其提供了一个快速的访问平面文件数据库的接口.并且提供了访问大容量数据库的简洁的手段,但是并没有所意想的功能或者速度上的损失.在本文中,我们 将探讨这个新的激动人心的扩展库,并且希望以此来验证其传说中的优势和好处.


摘自:http://apps.hi.baidu.com/share/detail/1650499

啥是SQLite?


SQLite 是实现了SQL 92标准的一个大子集的嵌入式数据库.其以在一个库中组合了数据库引擎和接口,能将所有数据存储于单个文件中而著名.我觉得SQLite的功能一定程度上 居于MySQL 和PostgreSQL之间.尽管如此,在性能上面,SQLite常常快2-3倍 (甚至更多).这利益于其高度调整了的内部架构,因为它除去了服务器端到客户端和客户端到服务器端的通信.


所有这些都集在一个包中,也仅仅比MySQL的客户端的库稍微大一点.而令人印象深刻的特点是你可将你的整个数据库系统 放在其中.利用非常高效的内存组织,SQLite只需在很小的内存中维护其很小的尺寸,远远比其它任何数据库系统都小.这些特点使得其成为在需要高效地应 用数据库的任务中一个非常方便的工具.



它对我有啥用?


除了速度和效率,SQLite还有其它好多的优势使得其能成为许多任务中一个理想的解决方案.因为SQLite的数据库 都是简单文件,因此无须一个管理队伍花时间来构造复杂的权限结构来保护用户的数据库.因为权限通过文件系统自动进行.这也同时意味着(数据库空间的大小只 与环境有关,与本身无关)无段特殊的规则来了解用户磁盘空间.用户可以从创建他们想要的任意多的数据库和对其对这些数据库的绝对控制权而得到好处.

数据库就是一个文件的事实使用SQLite可以轻易地在服务器间移动.SQLite也除去了需要大量内存和其它系统资源的伺候进程.即使当数据库在大量地使用时也是如此.



SQLite 扩展库


作为最新的数据库扩展库,SQLite很幸运地没有遗留代码.不象其它的数据库比如MySQL,它必须为了保持向下兼容 而维护一大堆陈旧过时的行为特性.它也使用扩展库可以利用最新的PHP特性来获得最高级的性能和功能.扩展库的开发人员努力地使用户可以很方便地将其它数 据库迁移到SQLite.并且同时保持已经用PHP实现的界面.

SQLite也支持面向过程接口中数据库资源传递的灵活机制.使得其可以一样容易地从MySQL和PostgreSQL中迁移而来,MySQL中数据库资源是向后传递的( passed last),而PostgreSQL中是向前传递的(passed first).

SQLite 也具有强大的面向对象接口来从数据库中高效地存取数据.减少了你实现你对于过程接口的面向对象外包的时间.正如如下示例所示,面向对象接口可以避免你一次传递所有资源.( passing resources altogether.)




// 构造新数据库(面向对象接口)
$db = new SQLiteDatabase("db.sqlite");

// 创建foo表并插入示例数据
$db->query("BEGIN;
         CREATE TABLE foo(id INTEGER PRIMARY KEY, name CHAR(255));
         INSERT INTO foo (name) VALUES('Ilia');
         INSERT INTO foo (name) VALUES('Ilia2');
         INSERT INTO foo (name) VALUES('Ilia3');
         COMMIT;"
);

// 执行一个查询    
$result = $db->query("SELECT * FROM foo");
// 迭代地读取行
while ($result->valid()) {
    
// 获得当前行数据
    
$row = $result->current();     
    
print_r($row);
// 前进到下一行
    
$result->next();
}

// 不一定需要此句PHP会自动关闭链接
unset($db);

?>



安装 SQLite


在 PHP 5.0安装SQLite 是很轻易的事,因为其已经捆绑了SQLite扩展和库.因此所有你需要做的是在人的配置命令行中加入–with-sqlite 就可以了. 但我仍然推荐你安装一个SQLite库.这仅仅是因为这个扩展库是二进制的,你可以在没有PHP的情况下也可以打开和操纵SQLite数据库.这对于你在 各种时间趋势下来调试和执行以及测试你的查询是很方便的.经常地,你会发展捆绑的SQLite库有点过时了,因此,用外部的库来编译你的PHP程序,可以 使得你能获益于扩展库的最新修正和SQLite的最新特性.这也允许你在无需重新编译PHP的情况下来更新你的SQLite库.

用一个外部的扩展库来编译SQLite扩展用如下命令就可以了            –with-sqlite=/path/to/lib/.

我同样应当提到的是SQLite扩展同时提供了一系列可以理解的测试SQLite所支持的单个函数和方法面向对象和面向过程的测试.这不仅仅是理解如何运用每个函数和方法工作的绝佳的示例资源.也提供了预期的的输出,使你可以看见每个操作的最终结果.

使用 SQLite
SQL面向过程的接口跟MySQL和其它数据库的接口几乎是同样的.因而,对于大部分的将其它数据库迁移到SQLite的工作仅是将函数前缀mysql/pq/等等...改为sqlite.
// 创建一个新的数据库 (面向过程接口)
$db = sqlite_open("db.sqlite");

// 如果你还需创建表foo,请将下面一句的注释标志清除.
// sqlite_query($db , "CREATE TABLE foo (id INTEGER PRIMARY KEY, name CHAR(255))");

//
插入示例数据
sqlite_query($db, "INSERT INTO foo (name) VALUES ('Ilia')");
sqlite_query($db, "INSERT INTO foo (name) VALUES ('Ilia2')");
sqlite_query($db, "INSERT INTO foo (name) VALUES ('Ilia3')");

// 执行查询
$result = sqlite_query($db, "SELECT * FROM foo");
// 迭代访问数据行
while ($row = sqlite_fetch_array($result)) {
    
print_r($row);
    
/*查询结果的每一行如下所示
     Array
     (
         [0] => 1
         [id] => 1
         [1] => Ilia
         [name] => Ilia
     )
*/
}

// 手动关闭数据连接
sqlite_close($db);

?>
SQLite和其它数据库一个大的不同在于其数据库引擎本身.不象其它数据库,SQLite是松散型的. SQLite中所有数据库都以空字符串结尾而不是对特定的列类型用特定的二进制表现.为了兼容性的原因,SQLite仍然在表创建时支持一些类型规范, 比如INT, CHAR, FLOAT, TEXT如此等等,但是实际上并没有用到这些数据类型.在内部,SQLite仅仅对字符串和整形在排序期间作了区分.因此,如果你不是有意地排序数据,你 可以无需在CREATE TABLE语句 中指定数据类型.

SQLite的类型无关特性也在一定程度上降低了其数据排序和数据比较的速度.因为,每一次比较SQLite都需要判断数据,然后来决定是用字符串还是用 数字型比较的机制.SQL表格也提供了自动增长的键值以对数据行快速访问. 同时也意味着对最后插入的一行的引用的访问.在SQLite里,这有一个不太常见的语法规则. 要创建这样一个自动增长的键值的字段,你必须将字段声明为INTEGER PRIMARY KEY, 而不是将其指定为一个特殊类型,并赋值以附加属性来指出这是一个自增长的字段.
链式查询
正如你所想象的.SQLite拥有许多提高其效能和扩展其功能的新的特性.这些特性的其中这五就是执行链式查询的能 力.这就意味着你可以通过一个查询函数来执行多条查询.这大大地减少了你需要运行的PHP函数的量.也提高了脚本运行的速度.这也允许你在事特处理中包含 查询语句块.大大提高性能.这是当执行多条写查询时提高性能的重要因素.当然,运用这个功能时也需要考虑到一些带来混乱的问题.

如果SQLite中的任何查询用到了用户指定的输入,你必须仔细地验证用户的输入以防止SQL注射攻击.不像MySQL,SQL注射仅仅导致一个令人难堪 的查询错误.在SQLite中,允许攻击者在你的服务器上执行查询,可能导致潜在的灾难性的后果.如果查询展示会中包含了插入语句,并且你希望获得id, sqlite_last_insert_rowid() 函数可以达到这个目的,仅仅取得最后一次插入的id.另外一方面,当试图知道多少行受到查询影响时,可以用sqlite_changes(),这个函数的 结果 就是所有执行的查询所影响的总行数.如果你的查询块中包含了SELECT,请确保它是第一个查询.否则你的结果集将不包含查询中所存取的行.

// 创建一个仅在内存中的数据库(不存为文件的)
$db = new SQLiteDatabase(":memory:");
// 创建一个两列的表 bar ,并且在其中插入两行.
/*
为了提高性能,整个查询块封装在 一个事物中. */
$db->query("BEGIN;
         CREATE TABLE bar ( id INTEGER PRIMARY KEY, id2 );
         INSERT INTO bar (id2) VALUES(1);
         INSERT INTO bar (id2) VALUES(2);
         COMMIT;"
);
// 将输出"2 insert queries"
echo $db->changes()." insert queries\n";
// 将输出 "last inserted row id: 2"
echo "last inserted row id: ".$db->lastInsertRowid();

?>
新函数
除了向后前进的特性,(back end features), SQLite 也提供了一系统简化和提供数据存取速度的函数.

$db
= new SQLiteDatabase("db.sqlite");
/* 执行查询,并将查询结果存入数组中 */
$result_array = $db->arrayQuery("SELECT * FROM foo", SQLITE_ASSOC);
print_r($result_array);

?>
这个函数使得查询执行和数据存取都由一个函数调用来实现.大大地减少了整个PHP执行的过程.PHP脚本也大大地简 化,现在你只要一单个函数就可以了,而否则的话你将要在一个循环中调用一系列数据存取函数.例如,如果仅仅是需获得一列,可以用函数 sqlite_single_query(),这将立刻返回 一个字符串或者一个字符串数组,这将取决于有多少行数据被查询出来.

$db
正如运用其它许多特性一样,你应该合理地使用它而不是滥用它.当在一个查询中一次存取所有数据时,你要记得查询结果 的所有数据是保存在内存中的.如果结果集中包含大量的数据,内存的分配就会将减少函数调用次数所带来的性能优势化为子虚乌有.因此,请在存取的数据量比较 小的情况下运用这些函数.

SQLite 迭代器

在 PHP 5.0 中,有另外一种方式来从查询中获得行数据,那就是运用迭代器.

$db = new SQLiteDatabase("db.sqlite");
// 执行非缓冲的查询可以减少内在的使用.
$res = $db->unbufferedQuery("SELECT * FROM foo");
foreach ($res as $row) { //迭代结果集对象
        // 输出代码
        print_r($row);
}

?>

迭代对象的工作过程除了不需要要键值(’keys’)和用一个值来表示数组中所包含的特定的数据行之 外,其它跟通过foreach()访问数组对象很相似.因为迭代器是内部引擎句柄而不是函数. 迭代器跟sqlite_fetch_*()函数比较用到的PHP代码就少得很多了.并且不需要把结果集缓冲到内存中.总之,迭代器是一种非常快速,但是简 单易用的获取数据的方法.运用SQLite的对象迭代器,不会存在什么不足之处,当你需要遍历一个多行数据集时,你一定应该考虑使用它们.

工具函数

SQLite扩展也提供了一些在操作数据库时使用起来很方便的工具函数.其中一个就是sqlite_num_fields(), 可以用于获得一个特定的结果集中的字段数(列数).
当然,也有其它选择,如果你想取得数据的同时得到列数,你可以对数据集使用count()方法,就可以获得上面函数相同的数目.如果同时取回了字符串型和 数字型的主键,你必须让结果一分为二,因为结果数组中的字段入口数会是字段数的两倍.如果你的脚本需要获得特定表的列名时,这个数目是很有用的.如果是这 样的话,你就可以在一个循环中用sqlite_field_name()来获得字段名称信息.正如下例所示.[译者注,本例是PHP5的情况,用的是OO 接口,在PHP4中,需要变换相对应的函数,具体请参见PHP Manual SQLite的参考一节]

$db = new SQLiteDatabase("db.sqlite");
$res = $db->unbufferedQuery("SELECT * FROM foo LIMIT 1");
//取得字段数目
$n_fields = $res->numFields();
$i = 0;
while ($i < $n_fields) {
    //获得单个字段名
    $field_name = $res->fieldName($i++);
    echo $field_name."\n";
}

?>

很显然,这是获得表中列名的一种很理想的方式.很明显示地,当源表中没有任何行时,这个方式就会失败. 并且这个方法取了一些你可能并不想用的数据,因此,一个更好的解决方法是用sqlite_fetch_column_types()函数.这具函数 会返回特定表的列和类型,而不管表中的数据量如何.

缓存优势

在很多情况下,由于性能和内存的原因,你可能想执行非缓冲查询.尽管如此,这存在在某种情况下所必须的轻微的功能损失.这也就说明为什么,非缓冲查询不总是最优的选择.

例如,假设你想知道你的查询到底取回了多少行数据.如果用非缓存查询的话,你必须一行行地查询,才能知 道这个数目.而用缓存查询,只要不费吹灰之力地执行sqlite_num_rows()函数就可以了. 这就可以取回查询结果的行数.非缓存查询还仅限于线性数据获取,这就意味着你必须取得一系列中所有行的信息,一次一行.而对于缓存查询则没有这个限制.你 可用sqlite_seek() 函数来到达任意一行,并且取回数据.只要需要,逆向地获取行数据也是可以的.

$db = new SQLiteDatabase("db.sqlite");
$res = $db->query("SELECT * FROM foo");
$n_rows = $res->numRows(); // 获得行数
$res->seek($n_rows - 1); // 到最后一行
// 返回获取数据
do {
    $data = $res->current(SQLITE_ASSOC); // 获得行数据
    print_r($data);
}
while ($res->hasPrev()&& $res->prev()); // 逆向前进,直到第一行

?>

定制函数

SQLite扩展所带来的对表格操作的最有意思的特性之一是用SQL创建你自己的函数的能力.这可能得 益于SQLite在同一个库中组合了数据库引擎和接口,而这个库又同PHP很好地耦合。通过使用sqlite_create_function(),你可 以 构造能够运用于结果集或在WEHRE子句中使用的函数。

/* 此函数用于分别用户指定的字符串和数据库中字符串在包含的字符上的差异*/
function char_compare($db_str, $user_str) {
    return similar_text($db_str, $user_str);
}

$db = new SQLiteDatabase("db.sqlite");

/* 利用已有的PHP函数在SQLite内部创建char_compare()函数。 此调用的第3个参数指明创建的函数所需的参数数目*/
$db->createFunction('char_compare', 'char_compare', 2);
       
/* 执行包含char_compare()函数,并用其比较name和指定字符串的查询*/
$res = $db->arrayQuery("SELECT name, char_compare(name, 'Il2') AS sim_index FROM foo", SQLITE_ASSOC);

print_r($res);

?>

用PHP内嵌SQL的能力,允许你简化实际的脚本,从而使更多的开发者来运用它。这个特性也允许PHP 作为一个模块引擎,简化用数据库中数据来生成HTML结构的过程。在很多情况下,这能大大简化代码,以达到无须在PHP顶部放置一个模块系统。除了代码简 化之外,这也提高了程序性能和减少脚本内存占用。因为无须在用户空间中进行数据处理。

注意,如果你运用的数据有可能包含二进制数据。在处理这些数据之前,你必须用 sqlite_udf_decode_binary() 函数来对SQLite内部二进制编码进行解码。当你完成了之后,你必须用函数sqlite_udf_encode_binary()来对二进制数据进行编 码,以在后面某个时候能在没有损坏的情况下正常使用它。

总结

至此,你已经明白SQLite如何工作,并且了解了它能提供什么.你可能在你当前或将来的应用程序中运用它.希望,这个简短的介绍能使你熟悉SQLite提供的功能,并不会对你以往对SQLite的好印象带来任何不良影响.

正好所有的工具一样,SQLite也有其强项和弱项.对于小型的,并且大部分操作为读取操作的应用程 序,SQLite 提供了理想的解决方案.而对大型的频繁写入的应用,SQLite是不太合适的.这种限制是由于SQLite的单文件架构导致的.这种架构不允许你在服务器 间多路访问,也不允许在写时对数据库加锁.

关于作者

Ilia Alshanetsky 已经从事Web应用程序开发超过7年了,其中大部分都是基于PHP的开发.在最近的几年里,他热忱地加入PHP的开发中来,并合作开发大量的扩展,包括 SQLite.现在Ilia正在经营他自己的公司. Advanced Internet Designs Inc.,公司的业务主要是开发和支持一个开源论坛FUDforum的开发.

欲联系Ilia,可以通过电子邮件ilia@prohost.org

= sqlite_open("db.sqlite");
// 获得列的id (作为字符串)
$id = sqlite_single_query($db, "SELECT id FROM foo WHERE name='Ilia'");
var_dump($id); //string(1)

//
如果结果多于一行,返回的将是数组
$ids = sqlite_single_query($db, "SELECT id FROM foo WHERE name LIKE 'I%'");
var_dump($ids); // array(3)

?>

没有评论: