在甲骨文LOB和PHP工作 由哈里Fuecks
达到4,000字节的限制?输入LOB ...
在这个“Oracle + PHP Cookbook”HowTo中,您将学习可用的LOB类型和与之相关的问题,然后探索PHP中常见的LOB操作的示例。使用像VARCHAR2这样的Oracle类型是很好的,但是如果你需要能够一次存储超过4,000字节的限制呢?对于此任务,您需要一个Oracle的长对象(LOB)类型,这反过来要求您了解如何使用PHP API来处理LOB。这本身对于那些不熟悉它的人来说是令人生畏的。
Oracle中的长对象
Oracle提供以下LOB类型:
- BLOB,用于存储二进制数据
- CLOB,用于存储使用数据库字符集编码的字符数据
-
NCLOB,用于存储使用国家字符集的Unicode字符数据。需要注意的是NCLOBs是,目前不被支持PHP OCI8扩展,你会在这里使用。
- BFILE,用于引用操作系统文件系统下的外部文件
LOB的另一个子类是临时LOB,它可以是BLOB,CLOB或NCLOB,但存储在临时表空间中,直到您释放它。
请注意,旧版本的Oracle分别为字符和二进制数据提供了LONG和LONG RAW类型。随着Oracle9需要我这些赞成的LOB被弃用。
LOB存储。对于BLOB,CLOB和NCLOB类型,Oracle数据库10 ğ能够在一个单一的值存储高达128TB,这取决于你的数据库块大小和“块”设置,为LOB定义的。
LOB本身包括两个元素:LOB内容和LOB定位符,其是到LOB内容的“指针”。这种分离需要允许Oracle存储和有效地管理LOB和它反映在使用到PHP的API INSERT, 数据库,和 选择的LOB(见下文)。
对于内部LOB类型(即不是BFILE),如果LOB的大小小于4KB,则Oracle将在表中存储LOB的内容以及行的其余部分。大于4KB的LOB在默认情况下在表的表空间中存储为“out-of-line”。此方法允许快速检索小LOB,而对于大LOB,访问时间将较慢,但是在扫描表时的整体性能被保留。
还有LOB存储和访问的其他选项,例如内存缓存和缓冲 - 这可以提高性能,具体取决于您的应用程序的具体情况。欲了解更多信息,请参阅 LOB性能指南和 Oracle数据库应用程序开发人员指南-大型对象在Oracle文档中获得。
LOB的限制。许多限制适用于使用LOB类型,最重要的是它们在SQL语句中的使用。您不能在以下任何查询中使用LOB类型。
SELECT DISTINCT <lob_type>
ORDER BY <lob_type>
GROUP BY <lob_col>
这也是非法的表连接,使用LOB类型列 UNION, INTERSECTION和 MINUS语句。
其他限制适用于LOB使用的其他方面,例如,您不能将LOB用作主键列。再次,看 Oracle数据库应用程序开发人员指南-大型对象的详细信息。
CLOB和字符集
数据库的默认字符集由参数NLS_CHARACTERSET定义,并且放置在CLOB中的文本应使用此字符集进行编码。使用此SQL来确定您的数据库字符集编码:
SELECT value FROM nls_database_parameters WHERE parameter ='NLS_CHARACTERSET'
由于缺乏对PHP中NCLOB的支持,您可能需要考虑使用Unicode编码作为数据库字符,例如UTF-8,可以使用以下语句完成(给予足够的权限):
ALTER数据库字符集UTF8
注意:不要在不理解影响的情况下尝试此操作,尤其是如果现有数据或应用程序代码使用不同的字符集。看到 甲骨文全球化支持指南和对全球化的Oracle PHP应用程序概述了解更多信息。
使用LOB
这里的讨论将集中在PHP的OCI8扩展。还值得注意的是,Oracle提供了DBMS_LOB包,其中包含使用PL / SQL处理LOB的并行过程和函数。
PHP OCI8扩展在全局PHP命名空间中注册了一个名为“OCI-Lob”的PHP类。当您执行 SELECT语句,例如,其中一列是一个LOB类型,PHP将自动绑定这个到OCI-LOB对象实例。一旦你有一个OCI高球对象的引用,你可以调用方法一样 的load()和 save()方法来访问或修改LOB的内容。
可用的OCI高球的方法将取决于你的PHP版本,PHP5特别是具有像获得方法 的read() , 求()和 追加() 。在 PHP手册是有点不清楚,在这种情况下,版本号,所以如果有疑问,您可以验证使用下面的脚本。
<?php
foreach(get_class_methods('OCI-Lob')as $ method){
print“OCI-Lob :: $ method()\ n”;
}}
?>
在我的系统上,运行PHP 5.0.5,我得到以下列表的方法:
OCI-Lob :: load()
OCI-Lob :: tell()
OCI-Lob :: truncate()
OCI-Lob :: erase()
OCI-Lob :: flush()
OCI-Lob :: setbuffering()
OCI-Lob :: getbuffering()
OCI-Lob :: rewind()
OCI-Lob :: read()
OCI-Lob :: eof()
OCI-Lob :: seek()
OCI-Lob :: write()
OCI-Lob :: append()
OCI-Lob :: size()
OCI-Lob :: writetofile()
OCI-Lob :: writetemporary()
OCI-Lob :: close()
OCI-Lob :: save()
OCI-Lob :: savefile()
OCI-Lob :: free()
在实践中,PHP 4.x OCI8扩展仅支持读取或写入完整的LOB,这是Web应用程序中最常见的用例。PHP5扩展是为了让LOB的“块”的阅读和写作,以及支持LOB缓冲的方法 setBuffering()和 getBuffering() 。PHP5还提供了独立的功能 oci_lob_is_equal()和 oci_lob_copy() 。
此处的示例将使用新的PHP5 OCI函数名(如 oci_parse而不是 OCIParse)。示例使用以下序列和表:
CREATE SEQUENCE mylobs_id_seq
NOMINVALUE
NOMAXVALUE
NOCYCLE
CACHE 20
NOORDER
增量1;
CREATE TABLE mylobs(
id NUMBER PRIMARY KEY,
mylob CLOB
)
注意,这里的大多数示例使用CLOB,但是相同的逻辑也可以几乎完全应用于BLOB。
插入LOB
要 INSERT内部LOB,首先需要使用相应的Oracle初始化LOB EMPTY_BLOB或 EMPTY_CLOB功能,您无法更新一个包含NULL值的LOB。
初始化后,你再绑定列到PHP OCI-LOB对象和更新通过对象的LOB的内容 保存()方法。
下面的脚本提供了一个示例,返回从该LOB类型 INSERT查询:
<?php
//连接到DB等...
$ sql =“INSERT INTO
mylobs
((
ID,
mylob
)
值
((
mylobs_id_seq.NEXTVAL,
- 初始化为空CLOB
EMPTY_CLOB()
)
返回
- 返回LOB定位器
mylob INTO:mylob_loc“;
$ stmt = oci_parse($ conn,$ sql);
//创建一个“空”OCI-Lob对象以绑定到定位器
$ myLOB = oci_new_descriptor($ conn,OCI_D_LOB);
//将返回的Oracle LOB定位符绑定到PHP LOB对象
oci_bind_by_name($ stmt,“:mylob_loc”,$ myLOB,-1,OCI_B_CLOB);
//使用OCI_DEFAULT作为事务执行语句
oci_execute($ stmt,OCI_DEFAULT)
或死亡(“无法执行查询\ n”);
//现在保存一个值到LOB
if(!$ myLOB-> save('INSERT:'.date('H:i:s',time()))){
//出错时,回滚事务
oci_rollback($ conn);
} else {
//成功后,提交事务
oci_commit($ conn);
}}
//免费资源
oci_free_statement($ stmt);
$ myLOB-> free();
//断开与DB的连接等。
?>
请注意这个例子中如何使用事务,指示 oci_execute与 OCI_DEFAULT常量等待 oci_commit或 oci_rollback。这一点很重要,因为我有两个阶段发生在 INSERT -首先创建行和第二次更新的LOB。
需要注意的是,如果我是用BLOB类型,唯一需要的变化(假设BLOB列)的工作是对 oci_bind_by_name调用:
oci_bind_by_name($ stmt,“:mylob_loc”,$ myLOB,-1,OCI_B_BLOB);
或者,您可以直接绑定字符串,而不指定LOB类型;
<?php
//等等。
$ sql =“INSERT INTO
mylobs
((
ID,
mylob
)
值
((
mylobs_id_seq.NEXTVAL,
:串
)
“;
$ stmt = oci_parse($ conn,$ sql);
oci_bind_by_name($ stmt,':string',$ string);
oci_execute($ stmt)
或死亡(“无法执行查询\ n”);
//等等。
?>
这种方法大大简化了代码,适合当你想要写入到LOB的数据相对较小。相反,如果你想大文件的内容流成LOB,你可以通过文件的内容循环调用 write()方法和 冲洗()的PHP LOB对象写较小的块上,而不是整个文件在单个实例中保持在内存中。
选择LOB
当 SELECT查询包含一个LOB列,PHP将自动绑定列到OCI-LOB对象。例如:
<?php
//等等。
$ sql =“SELECT
*:
从
mylobs
ORDER BY
ID
“;
$ stmt = oci_parse($ conn,$ sql);
oci_execute($ stmt)
或死亡(“无法执行查询\ n”);
while($ row = oci_fetch_assoc($ stmt)){
print“ID:{$ row ['ID']},”;
//调用load()方法来获取LOB的内容
print $ row ['MYLOB'] - > load()。“\ n”;
}}
//等等。
?>
这可以通过使用进一步简化 OCI_RETURN_LOBS恒定,与所用 oci_fetch_array() ,指示它替换为它们的值LOB对象:
while($ row = oci_fetch_array($ stmt,OCI_ASSOC + OCI_RETURN_LOBS)){
print“ID:{$ row ['ID']},{$ row ['MYLOB']} \ n”;
}}
更新LOB
要 UPDATE一个LOB,它也可以使用 “回归”中的SQL命令,与上面 的INSERT的例子,但一个更简单的方法是 SELECT ... FOR UPDATE:
<?php
//等等。
$ sql =“SELECT
mylob
从
mylobs
哪里
id = 3
FOR UPDATE / *锁定行* /
“;
$ stmt = oci_parse($ conn,$ sql);
//使用OCI_DEFAULT执行语句(开始事务)
oci_execute($ stmt,OCI_DEFAULT)
或死亡(“无法执行查询\ n”);
//获取SELECTed行
if(FALSE ===($ row = oci_fetch_assoc($ stmt))){
oci_rollback($ conn);
die(“无法获取行\ n”);
}}
//丢弃现有的LOB内容
if(!$ row ['MYLOB'] - > truncate()){
oci_rollback($ conn);
die(“Failed to truncate LOB \ n”);
}}
//现在保存一个值到LOB
if(!$ row ['MYLOB'] - > save('UPDATE:'.date('H:i:s',time()))){
//出错时,回滚事务
oci_rollback($ conn);
} else {
//成功后,提交事务
oci_commit($ conn);
}}
//免费资源
oci_free_statement($ stmt);
$ row ['MYLOB'] - > free();
//等等。
?>
如同 INSERT,我需要执行 UPDATE使用事务。一个重要的附加步骤是将呼叫 截断() 。当更新与一个LOB 保存() ,将取代的LOB的从启动开始新的数据的长度的内容。这意味着较旧的内容(如果它比新的内容更长)可能仍然留在LOB中。
对于PHP 4.x中,在 截断()是不可用的,下面的替代解决方案使用Oracle的 EMPTY_CLOB()函数的新数据保存到前擦除在LOB任何现有内容。
$ sql =“UPDATE
mylobs
组
mylob = EMPTY_CLOB()
哪里
id = 2403
返回
mylob INTO:mylob
“;
$ stmt = OCIParse($ conn,$ sql);
$ mylob = OCINewDescriptor($ conn,OCI_D_LOB);
OCIBindByName($ stmt,':mylob',$ mylob,-1,OCI_B_CLOB);
//使用OCI_DEFAULT执行语句(开始事务)
OCIExecute($ stmt,OCI_DEFAULT)
或死亡(“无法执行查询\ n”);
if(!$ mylob-> save('UPDATE:'.date('H:i:s',time()))){
OCIRollback($ conn);
die(“无法更新lob \ n”);
}}
OCICommit($ conn);
$ mylob-> free();
OCIFreeStatement($ stmt);
使用BFILES
当使用BFILE类型, INSERT S和 UPDATE刻薄告诉甲骨文在该文件所在的数据库服务器(可能不是同一台机器作为Web服务器)的文件系统中,而不是传递文件内容。使用 SELECT语句,您可以通过Oracle读取BFILE的内容,应该你愿意的话,或致电函数和过程从DBMS_LOB包,以获取有关文件的信息。
BFILE的主要优点是能够直接从文件系统访问原始文件,同时仍然能够使用SQL查找文件。这意味着,例如,图像可以直接由Web服务器提供,而我可以跟踪包含BFILES的表格和“用户”表之间的关系,告诉我谁上传了文件。
例如,我首先需要更新上面使用的表模式;
ALTER TABLE mylobs ADD(mybfile BFILE)
接下来,我需要向Oracle注册目录别名(这需要管理权限),并授予读取它的权限:
CREATE DIRECTORY IMAGES_DIR AS'/ home / harryf / public_html / images'
我现在可以 INSERT像一些BFILE的名称:
<?php
//等等。
//为BFILE名称构建一个INSERT
$ sql =“INSERT INTO
mylobs
((
ID,
mybfile
)
值
((
mylobs_id_seq.NEXTVAL,
/ *
使用Oracle目录引用传递文件名
我创建了名为IMAGES_DIR
* /
BFILENAME('IMAGES_DIR',:filename)
)“;
$ stmt = oci_parse($ conn,$ sql);
//打开目录
$ dir ='/ home / harryf / public_html / images';
$ dh = opendir($ dir)
或死亡(“无法打开$ dir”);
//循环遍历目录的内容
while(false!==($ entry = readdir($ dh))){
//仅匹配扩展名为.jpg,.gif或.png的文件
if(is_file($ dir。'/'。$ entry)&& preg_match('/ \。(jpg | gif | png)$ /',$ entry)
//绑定语句的文件名
oci_bind_by_name($ stmt,“:filename”,$ entry);
//执行语句
if(oci_execute($ stmt)){
print“$ entry added \ n”;
}}
}}
}}
如果我需要,我可以通过Oracle读取BFILE内容使用与上面相同的方法,我选择CLOB。或者,如果我需要获得文件名后面,所以我可以从文件系统中直接访问它们,我可以调用 DBMS_LOB.FILEGETNAME程序,如:
<?php
//等等。
$ sql =“SELECT
ID
从
mylobs
哪里
- 只选择不为空的BFILES
mybfile IS NOT NULL;
$ stmt1 = oci_parse($ conn,$ sql);
oci_execute($ stmt1)
或死亡(“无法执行查询\ n”);
$ sql =“DECLARE
定位器BFILE;
diralias VARCHAR2(30);
filename VARCHAR2(30);
开始
选择
mybfile INTO locator
从
mylobs
哪里
id =:id;
- 从BFILE获取文件名
DBMS_LOB.FILEGETNAME(locator,diralias,filename);
- 分配OUT参数以绑定参数
:diralias:= diralias;
:filename:= filename;
结束;”;
$ stmt2 = oci_parse($ conn,$ sql);
oci_bind_by_name($ stmt2,“:id”,$ row ['ID']);
oci_bind_by_name($ stmt2,“:diralias”,$ diralias,30);
oci_bind_by_name($ stmt2,“:filename”,$ filename,30);
oci_execute($ stmt2);
print“{$ row ['ID']}:$ diralias / $ filename \ n”;
}}
//等等。
?>
此外,还可以使用 DBMS_LOB.FILEEXISTS功能来发现哪些文件已通过操作系统被删除,但在数据库中仍引用。
结论
在此方法文档中,你已经被介绍给不同类型的Oracle数据库10个可用的LOB 摹,希望现在了解他们在允许大型数据实体高效地存储在数据库中的作用。您还学习了如何使用PHP的OCI8 API来处理LOB,涵盖了在使用Oracle和PHP开发时遇到的常见用例。
哈利Fuecks [ http://www.phppatterns.com ]是一个知名的PHP开发人员和作家,自1999年发现PHP他发表了许多介绍,并通过中间PHP文章 Sitepoint网站开发者的网络,以及写作 的PHP文集(SitePoint)。
|
请发表评论