2009年10月7日 星期三

Bitmap 的数据库持久化实现

以下是基于数据覆盖的一个初步讨论,实用中,位映射有广泛的应用领域,也有大量的算法实现需求。在经过初步的推演后,我发现这不是可以一蹴而就的。所以先给出一个简单的实现和应用算法讨论。

Bitmap 的数据库持久化实现

Bitmap 是一个常见的数据类型,常用于优化大量数值的存储和查询。这个概念对于数据 库程序员应该不陌生,Oracle 等数据库通过这种技术优化查询。通常程序员会在一些算法 或数据结构书籍上读到以 C 语言 char* 实现的 bitmap 。这里,我们考虑一个在数据库中 存储的 bitmap 实现。

PG 上的数据结构实现

bitmap 的实现不难,PostgreSQL 中提供了 bitstring 类型,可以直接存储位串。但是单行 bitstring 能提供的数据长度终归有限。为了存储和性能的需要,我们将其设计为一个表。 通过索引值将 bitmap 分片为 bitstring 集。

create table bitmap(idx bigint, segment bit(32));

分析

计算时我们将一个bitmap表视为一个完整的bitmap数据集,设 segment 的宽度为 w ,索引 值为 i 。将第行的 idx 置为0,则第 i 个索引行上的第 b 位对应整个 bitmap 的第 b+i*w 位。逆推之,bitmap 第 x 位为 x div w 行,第 x%w 位。

因为数据分行存储,位计算操作也需要重新定义,要考虑跨界定位问题。Postgres 的 bit 本身支持左移右移操作。这点并不复杂。可以利用掩码技巧将计算参数分解为区片。具体算 法几乎与 C 语言版本一致。当然针对一些有规则操作时,可以充分的利用 SQL 和 PLSQL 的语法特色进行简化。

实例

覆盖统计

详细内容介绍见下一段中的链接。这里简单介绍一下:每个数据样本是一个由大量一维区间 值组成的文本。每一行是一个(起点,终点)区间。要计算二到N个样本件的覆盖率。而数 据本身是未经整理的,可能有重复。

事实上,这个例子是我最初着手实现 bitmap 的起因。可以说 bitmap 可以直接优化此类问 题,首先bitmap以至少8倍以上的比例节省存储空间(以 c 语言 char* 每字节存储位置状 态来计)。而空间缩减后对查询性能也有直接提升。

这个应用的操作非常好实现,将收集来的数据区间段(s, e)转换为二进制数 2^s+2^(s+1)+...+2^e ,即从 s 到 e 之间都为1的二进制位串。然后与整个 bitmap 做或 运算。示例代码如下:

create or replace function segment_over(s bigint, e bigint) returns void as $$
declare
sidx bigint;
eidx bigint;
d_data bit(32) := 4294967295::bit(32); -- pow(2, 32) -1
begin
if currval('bitmap_idx_seq') < (e/32)+1 then
for i in currval('bitmap_idx_seq')..e/32+1 loop
insert into bitmap(segment) select 0::bit(32);
end loop;
end if;
sidx := s/32;
eidx := e/32;
if sidx = eidx then
update bitmap set segment = (d_data << cast((32-e%32) as int))&(d_data >> cast((s%32) as int)) where idx = sidx;
else
update bitmap set segment = segment | (d_data >> cast((s%32) as int)) where idx = sidx;
update bitmap set segment = d_data where sidx < idx and idx < eidx;
update bitmap set segment = segment | (d_data << cast((e%32) AS INT)) where idx = eidx;
end if;
end;
$$ language plpgsql;
优化分析

此示例中,segment_over 操作并未做任何深度优化,仅仅是将前述的思路直接实现出来而 已。实际上根据业务,我们还可以进一步的提高操作效率。

例如,由于覆盖度操作是在数据区迭加或操作,单向的填充动作,可以将已覆盖区间直接压 缩掉(这需要将 idx 字段扩展为起点和终点一对数据,或者通过触发器机制同步一个查询 表)。

再例如,前例中我们的 segment 长度为32,idx 是一个 bigint 类型。这对于海量数据比 较有效,不过如果总样本量较小,完全可以用 integer ,这样可以减少很多转型操作。

在 segment_over 中,我写了一个防出错的探测代码,如果索引值超出了初始化范围,会扩 展 bitmap 表。但是实际上很多应用,其总数据量是有明确上界的。此时可以去掉这部分代 码,使用一个一次性的初始化代码,例如下面这样:

create or replace function init(len bigint) returns void as $$
declare
i bigint;
begin
truncate table bitmap restart identity;
insert into bitmap(idx, segment) select 0, 0::bit(32);
if len/32 < 1 then
return;
end if;
for i in 1..len/32 loop
insert into bitmap(segment) select 0::bit(32);
end loop;
if len%32 > 0 then
insert into bitmap(segment) select 0::bit(32);
end if;
end;
$$ language plpgsql;

将 segment 的宽度调整为略大于覆盖区间长度期望值的数值,可以减少对中间区间的覆盖 操作,也有优化读写效率的可能。

数据库实现的动机

使用数据库存储 bitmap ,可以避免海量长度 bitmap 超出内存容量的情况,是一个比较经 济的实现方式,特别是在机器不够强劲,或者没有条件部署云或网格集群的场景。只要熟练 数据库层开发即可。

由于 Postgres 中已经实现了完备的并发访问、数据备份和查询等功能,可以避免自己开发 文件型 bitmap 所需要考虑以上问题,带来的开发成本。轻松可以满足 OLAP 的需求,在线 随时查询覆盖情况。

合理的使用数据库功能,可以有效的降低使用和开发成本,这种组件式的开发和集成方式, 对于当下追求灵活、高性价比和可持续性的 IT 产业理念,是契合的。


……

劉鑫
March.Liu

2009年9月24日 星期四

sqlalchemy、storm和web2py dal的比较报告

工作项目报告,所以抹掉项目名先,以"X"代之。

分割线内内容仅代表个人意见,与所供职企业及参与社区无关。

===================================

X 从很早的时候就出现各种数据库访问错误。包括链接数占用过多,死锁,
僵尸事务等。本周我集中梳理了一遍代码。我认为,虽然数据库设计方面有诸多
不合理之处,但是这些不合理主要影响业务错误,造成 X 性能和使用上的
问题是因为使用的ORM框架 storm 有严重的缺陷。

首先,storm 对数据库架构的同步有非常奇怪的设定。它不自动同步表结构,却插
手外键关联关系。强制要求外键必须都是级联更新、级联删除、set null。且不说
其设定中有自相矛盾之处,本身在MIS系统中做级联删除就是一件很危险的事——除
了系统维护、分表,MIS系统不应该删除任何数据。 storm 从一开始设计恐怕就没
有考虑企业级应用,但是对于web 开放式应用,storm 对外键的依赖又太笨重了。

其次,storm 在联接数据库后应该会有 DDL 操作(即修改数据库结构)或独占锁
定事务,此推断的证据在于用 storm 联接到 S 库后, S 无法进行
vacuumdb -a -f 处理。而根据 Postgres 手册,PG只有在遇到有链接正在进行
DDL 操作时,才会产生库级锁,造成 vacuum 操作无法进行(或手工建立一个隔离
级别相当的事务锁定)。作为世界上并发能力最强的数据库产品,正常的数据访问
操作与 vacuum 根本不会冲突,热处理资源回收正是 PG 独到的特性。这样造成了
X 应用频繁用光所有的链接数,还在数据库服务器遗留僵尸进程。我观察到
有僵死十几二十天没有完成过的 X 链接,这应该是因为同时多个 storm 链
接提交错误的锁定关系,造成死锁。

第三,storm 如果可以象 DAL 那样,明确设定不同步数据库,以上问题至少可以
解决一半,但是它没有链接配置参数。这造成了我们对其出现的问题无法进行友好
的调整。现在同事在 X 的 controll 层添加了强制的 commit 操作,一定
程度上减少了死链,但是我仍然观察到有点击 X 页面(在测试环境下)无
法响应,甚至造成数据库服务器闪断重启的现象,对于同时运营三十几个数据库的
数据库服务器,这是非常大的安全隐患。

昨天我尝试将 X 的数据库访问层迁移至 web2py DAL,经过一天尝试,总结
出以下的问题:

第一,DAL 缺少精确实数计算类型,它不支持 Numeric 或 Decimal,只能用
double,这是一个相当大的安全隐患,对于涉及财会计算的应用,使用浮点数是一
种很不严肃的作法。

第二,DAL 不支持数组和大数据类型,此类字段在 X 中有几处应用。

第三,DAL 对原生SQL的支持比较初级,虽然也可以使用,但是有时需要兼顾开发
速度,希望可以组合使用的时候,就会受限。

以上问题不是不能解决,但是需要修改 DAL 本身。虽然我一直有计划改造 DAL,
fork 一个对 PG 有良好支持的分支出来。但是这显然需要更多的开发时间。

昨天我详细查阅了一下 sqlalchemy 的文档,进行了一些简单的尝试,感觉这个
ORM 框架比较符合我们的需求:

第一,sqlachemy 对数据库链接的隔离级别和事务有良好的控制,默认也不会去尝
试DDL操作。

第二,sqlachemy 有非常丰富的数据类型支持,包括BLOB,Decimal/Numeric,以
及为 PG 特别定制的数组类型。

第三,sqlachemy 的查询类似 DAL (很可能DAL学习自sqlachemy),对各种查询操
作有良好的支持,还可以嵌入 SQL 片段,也可以方便的直接传入 SQL。

第四,sqlachemy 其实并不难学,它的功能虽然丰富,但是只掌握自己要用到的部
分即可,不需要完全学会,上手还是很简单的。

第五,文档比 storm 完整的多,而且现在仍在活跃开发。

第六,数据存储模型与业务模型分离,虽然看起来有重复劳动,但是对于一个需要
长期维护的企业应用项目,这是正确和严肃的作法。

第七,对特定数据库的特性有良好的支持,还可以扩展。

在使用 sqlachemy 时,我们还可以结合 web2py 原有的一些优秀工具,例如广泛
用于 DAL 的storage类型,这是一个类似 JS Object 的智能对象类型,很适合动
态结构的数据对象。

对于 X 项目的数据访问层重构,我评估工作量至少在一周左右。如果全部
换用 sqlachemy ,可以一劳永逸的解决数据库访问的问题,甚至 S 的后续版
本,我也建议尝试使用这个框架,毕竟这比 hack dal 要方便一些。

sqlachemy 当前的稳定版本是 0.5.6 ,我昨晚试验了开发中的 0.6 ,发现功能还
没有完整实现,现在还不能实用。


===================================

--
光见贼吃肉,没见贼挨打。
……

劉鑫
March.Liu

2009年9月15日 星期二

近日 twitter 发布信息整理

  
   数据库的架构与设计,应该从整体的范围去看问题,要从"数据存储和管理服务"的角度,而不是"数据库软件"

   应用层开发阶段,可以不加入外键,在数据库运营阶段,由DBA补完

   ID列的主键身份,其实只是为了与ORM妥协,在很多情况下,一个表只有ID主键列,既危险又愚蠢。如果应用层存在BUG,就会引发严重问题。我的建议是,至少在运营前期,就为关键实体数据加上唯一索引。

   存储过程有没有用?这个要看情况。做数据库架构就像建筑工程,不是"对""不对"这么简单。早年Oracle宣传一切逻辑都封装在存储过程中,数只从存储过程访问,这叫蛋疼。后来MySQL宣传外键存储过程触发器都不需要,只要能增删改查,这叫犯二。

   一个数据库要如何设计,业务对数据可靠性、完整性、性能的需求平衡是一个很重要的因素。对于用户访问日志(包括点击率等等),有一定的误差也是允许的,而商务活动往往是不允许有哪怕毫厘之差。针对不同的业务,应该有不同的设计和架构方案。
         
   上次李元佳先生介绍的EDB集群方案给我一定启发。典型的集群节点可以每节点由三个机器组成:一个查询节点,一个写入节点,一个备份节点。写入节点和备份节点多出来的内存可以共享给查询节点做为无限缓存(EDB特有功能)。
         
   有时查询节点还要负担起OLAP的任务,或者本身统计查询的计算量就远大于写入量,此时可以由一个写入节点带多个查询节点,或者直接向一个EDB网格同步。当然网格化以后,写入性能通常会有接近线性提升,这样可能就不需要写入节点了。

   前段时间我写过一篇文章,合理利用触发器,可以把一个无限增长的日志表访问,变成限定于20条数据的访问,如果按现在流行的MySQL+ORM,这种东西是做不出来的。
         
   事实上很多项目,尤其是高负载的互联网应用,最终都没有使用ORM。既然如此,拒绝数据库服务端编程就是一件很奇怪的事。在数据库服务端进行合理的编程,可以大大提高性能。
         
   既然数据库服务是作为一个整体使用的,那么性能分析也要从整体考虑。有些功能虽然单独比较性能不高,但是却能在合适的时候,提高应用的整体性能。
         
   数据库设计要符合业务,不要拿来作为自己炫技的场所。
         
   虽然这年头是个人都知道出来吼一句"数据库太慢!"以显示自己牛B,但实际上有多少是真的牛B到跨过了关系型数据库的极限,有多少只是不懂数据库在那里装B呢?
         
   观点:DBA的职责,除了维护工作,应该有以下几个——帮助设计人员找出性能和安全方面的问题;帮助开发人员解决数据库方面的问题,例如一些用其它手段难以解决的性能瓶颈和复杂逻辑;协助架构师制定系统持久层架构;为乙方(通常是DBA就职的一方)提供架构方面的专业意见。
         
    个人认为大多数应用不应该出现大量存储过程,尤其在ORM比较成熟的现今,基本不需要为CRUD操作封装存储过程,而存储过程不能与用户多步交互执行,这就决定了它不会主导事务处理逻辑。但是存储过程可以简化复杂的事务逻辑。
         
    简单的说,存储过程最好是在开发由粗到精的优化过程中加入,代替需要优化的代码部分。如果是那种希望快速粗放的拼装一个项目然后交钥匙不管的初等级开发(不要拿这个来冒充XP过程)并不适合过多使用存储过程,最好是用熟ORM,积累并复用确实有效的一些存储过程和SQL脚本模板。


--
话题越大,废话越多;名字越火星,问题越脑残。
……

劉鑫
March.Liu

2009年8月31日 星期一

[Postgres Story]最近访问用户问题

前几天有个朋友提了一个问题:

应用平台需要统计最近访问的20个用户的信息。

我第一个想到的是用memcache之类的专用的缓冲,以用户名为键,以最后访问时间为值,如 果用户访问比较均匀,限定一个合适的 超时值,查询的时候遍历过滤就好了,虽然不是很 精确,但是实现起来够简单。

答:不可以,领导要求只在数据库端解决。

环境准备

提问的朋友使用的是 MySQL ,这里我用一个 PostgreSQL 8.4(Enterprise DB Postgres Plus Stand Server 8.4) 建立实验环境。操作系统是 Windows 7 RC。

客户端是 Emacs sql-postgres,\timing on。

使用 explain analyze 分析性能消耗。

尝试

那么,最简单的应该是在日志表上查询:

select username, max(logintime)
from log
group by username
order by 2 desc
limit 20;

这里假定的是会话表(log)中有名为 username 的用户标识列,名为 logintime 的时间戳字 段。

但是显然这个查询的性能并不好,它要遍历整个日志表。

首先,我们给日志表加一个聚集索引,使它按插入时间倒排(显然这对插入效率不利)。

create index idx_logintime on login_session(logintime desc);

alter table login_session cluster on idx_logintime;

然后,再进行查询,可见效率有微弱提升。在我本机的 Postgres 8.4 上,通过 explain analyze 可见有不到百分之二的效率上升。我甚至怀疑这仅仅是源自一些随机事件的影响。

换一个角度分析,只要 select max ... group by ... 的模式不改变,就很难有根本的性 能提升,最好的办法仍然是缓存。

改进

有个简单的办法,可以在PG数据库中制造一个缓存表:

  • 首先,直接建立一个 user->timstamp 键值对表。如果用户量不大,可以直接这样:
  create table sessions(username text, seqs serial, logintime timestamp, primary key(username));
  • 然后创建一个触发器函数
create or replace function on_log() returns trigger as $$
begin
if exists(select * from sessions where username=NEW.username) then
update sessions set seqs = nextval('sessions_seqs_seq') where username=NEW.username;
else
insert into sessions(username, logintime) select NEW.username, NEW.logintime;
end if;
if (select count(*) from sessions)> 20 then
delete from sessions where seqs < (select min(seqs) from (select seqs from
sessions order by seqs desc limit 20) as t);
end if;
return NEW;
end;
$$ language plpgsql;

create trigger onlog after insert on log for each row execute procedure on_log();

OK,这样一来,经过反复测试,在我的笔记本上,插入速度基本没有降低(约 2%~ 5%), 而查询"最新的20个用户"这一操作,速度提升了一百七十余倍。几乎可以视作是一个数据库 端的队列缓存了。这个数值是基于 log 表中有五万条数据,在实际应用中日志表十万以上 (按每日切分导出)比比皆是,性能差距会更为显著。

分析

"最新的 20 个用户",这一问题,与"最近5分钟内的用户"此类问题的区别在于,它不能通 过获取当前时间,简单回溯得到有效数据区间。如何避免遍历整个日志表是一个关键问题———— 现代的在线服务系统每日访问日志量动辄数十万,几百上千万的也不罕见,第一种解法明显 不能满足。相比之下,第二种方案不需要干涉日志表的存储排序,不需要建立多余的索引。 通常我们讲触发器速度比较慢,但具体到这里,只是却是一个比较快速高效的实现方案。

如果用户量不大,在几千以内,为用户表建立一个最后登录时间字段,绑定 null last 索 引(可以加上FASTUPDATE=ON),然后利用基本的order by limit就可以得到比较慢意的效 果了。但是对于大型SNS等应用,数十上百万用户也是有可能的(甚至大型企业内部应用, 数万乃至上十万用户的系统也不在少数)。此时,第二种解决方案就理想的多。虽然看起来 在session表上反复进行计算和写入,但是由于session表的数据量非常小,永远只有几十 条,所以计算速度很快。如果对 log 表进行恰当的分区,同时将sessions表存储到另一个 区域(甚至直接缓存到内存),那么对于海量数据,也会有一个稳定的,良好的性能表现。

更进一步的解决方法,则应该是在数据库或应用层服务器环境,建立一个内存中的队列,来 记录这个数据。只要注意并发写入的问题,就可以得到很好的性能。这方面Haskell的STM机 制、我之前用在MSG.Summoner.Trac的旋转锁机制、都可以比较漂亮的解决这一问题。

优化

sessions 表的读写都非常频繁,通常有比较多的 update,,但是数据量基本恒定。应该积极的执行 vacuum。

可以为日志表添加一个 uuid 字段,以便在数据量上升时按 hash 分表。这样可以获得更好 的写入性能。

可以将 session 的主键索引设为 FASTUPDATE=ON 。

触发器中设定

SET LOCAL synchronous_commit TO OFF;

打开 WAL 异步事务提交,可以进一步提高并发写入速度。

更进一步的,可以写一个常驻的守护进程,用应用语言建立一个队列,将这一需求建立为独 立的服务。这个服务可以通过 pl 嵌入语言与数据库联接,也可以直接联接到应用层,有一 些应用层架构依赖于并发的多 fastcgi 实例,此时要注意并发访问冲突和数据同步的问题。

总结

之所以出现这样一个困扰开发人员的问题,根本在于项目领导不提供,也不认同使用灵活、 开放的思维方式解决此问题。服务项目的架构是一个系统工程,类似这样的问题,完全可以 用更开阔的眼光去寻找出路。建立一个简单的缓存队列实例,用perl或python,只需要数十 行。结合Postgres的plperl或plpython等嵌入脚本,可以非常简单的达到目的。甚至不用担 心使用多实例 fastcgi 时,多个应用服务进程争用缓存队列I/O的问题。

即使从方案二出发,通过利用服务器环境的资源,也可以做出更多优化。如把"删除第20之 后的旧数据"这部分脚本,移到 crontab 中,与vacuum 结合执行。会得到更好的效率。

找我请教的这位朋友,使用的不是Postgres,而是MySQL,相对来说很多PG的服务端编程技 巧难以照搬,再加上团队管理的政治问题,我也只有祝他好运了。



--
话题越大,废话越多;名字越火星,问题越脑残。
……

劉鑫
March.Liu

2009年7月24日 星期五

Emacs 窗体的透明设置

昨天朋友发来一个代码给我,可以让 windows 上的 emacs 窗体像 X 或苹果那样实现半透 明。代码并不复杂:

(set-frame-parameter (selected-frame) 'alpha (list 85 50))
(add-to-list 'default-frame-alist (cons 'alpha (list 85 50)))

然后经过一番努力,我写了一个切换功能,按f7进入透明,按f8退出透明状态。

(defun transform-window (a ab)
(set-frame-parameter (selected-frame) 'alpha (list a ab))
(add-to-list 'default-frame-alist (cons 'alpha (list a ab)))
)

(global-set-key [(f7)] (lambda()
(interactive)
(transform-window 85 55)))

(global-set-key [(f8)] (lambda()
(interactive)
(transform-window 100 100)))

但是这种东西两键切换显然不够友好,于是我把它改成了一键切换。

(setq is-alpha nil)

(defun transform-window (a ab)
(set-frame-parameter (selected-frame) 'alpha (list a ab))
(add-to-list 'default-frame-alist (cons 'alpha (list a ab)))
)


(global-set-key [(f8)] (lambda()
(interactive)
(if is-alpha
(transform-window 100 100)
(transform-window 85 50))
(setq is-alpha (not is-alpha))))

后来复读了一下代码,觉得还有改进的余地。首先,只是一个透明度切换,没必要占用两个 快捷键,对于我这种用 Emacs 写 N 种东西的人,快捷键是种相当宝贵的资源。再一点,目 前的设计只能支持两种透明度,还多用了一个全局变量。

在 Feather 兄弟的指点下,我突击了一下 emacs lisp ,写出了这样的版本:

;; transform window
;; Anchor: March Liu (刘鑫) <march.liu@gmail.com>
;;
;; This is a script to set emacs window's alpha value.
;; It work well on windows xp and vista with EmacsWin32
;; useage: add below line in your .emacs
;;
;; (load-file "path/alpha-window.el")
;;
;; you can define your alpha-list to set the transform combine
;; bind key with below code:
;;
;; (global-set-key [(f11)] 'loop-alpha)

(setq alpha-list '((100 100) (95 65) (85 55) (75 45) (65 35)))

(defun loop-alpha ()
(interactive)
(let ((h (car alpha-list))) ;; head value will set to
((lambda (a ab)
(set-frame-parameter (selected-frame) 'alpha (list a ab))
(add-to-list 'default-frame-alist (cons 'alpha (list a ab)))
) (car h) (car (cdr h)))
(setq alpha-list (cdr (append alpha-list (list h))))
)
)

这个脚本的特点如下:

  • 单命令轮转任意多个状态
  • 可以用 (global-set-key [(f11)] 'loop-alpha) 把 loop-alpha 绑定到快捷键上
  • 我设定了四个透明度组合,你可以在自己的 .emacs 里重定义 alpha-list ,设定自己 的透明度方案

目前我只在windows上试验过了,X窗口下如果没有开透明效果应该是不行的。当然,X本身 的半透明就很好用了,我在X上从来没想过需要这么个功能==;

应该只能用于图形界面:P。



--
话题越大,废话越多;名字越火星,问题越脑残。
……

劉鑫
March.Liu

2009年7月23日 星期四

Postgres 8.4 的更新内容概要

After many years of development, PostgreSQL has become feature-complete in many areas. This release shows a targeted approach to adding features (e.g., authentication, monitoring, space reuse), and adds capabilities defined in the later SQL standards. The major areas of enhancement are:

经过多年开发后,PostgreSQL 在很多方面都具有了完备的功能。这次发布定位于添加功能(例如授权、监控、空间回收),以及添加最新 SQL 标准中的新功能。主要的增强在于以下几个方面:

  • Windowing Functions Windowing 函数
  • Common Table Expressions and Recursive Queries 通用表表达式和递归查询
  • Default and variadic parameters for functions 函数的默认和动态参数
  • Parallel Restore 并行恢复

  • Column Permissions 列权限
  • Per-database locale settings 每数据库区域设置
  • Improved hash indexes 优化哈希索引
  • Improved join performance for EXISTS and NOT EXISTS queries 对于 EXISTS 和 NOT EXISTS 查询优化了查询性能
  • Easier-to-use Warm Standby 易用的温备
  • Automatic sizing of the Free Space Map 可用空间映射自动伸缩
  • Visibility Map (greatly reduces vacuum overhead for slowly-changing tables) 视界映射 (大大缩减了很少变化的表进行优化的负载)
  • Version-aware psql (backslash commands work against older servers) 依赖版本的 psql (旧的服务器上没有的反斜杠命令)
  • Support SSL certificates for user authentication 支持 SSL 认证的用户身份验证。
  • Per-function runtime statistics 每函数运行时统计
  • Easy editing of functions in psql psql 函数更容易编辑
  • New contrib modules: pg_stat_statements, auto_explain, citext, btree_gin 新的附加模块:pg_stat_statements, auto_explain, citext, btree_gin

说明:

PostgreSQL 8.4 Releas Note 我已经翻译完成,完整版本在 http://zerolab.co.cc/?q=node/6 但是我估计我那个免费空间抗不住这么大访问压力,所以会逐篇把主要内容放在这里。

该文档使用 Emacs Muse 编写。

这段时间我会自己开发或者搞一个 Muse2Wiki 的插件,把文档放到 PG 中文社区的 WIKI 上。

 原文地址 http://www.postgresql.org/docs/8.4/static/release-8-4.html

--
话题越大,废话越多;名字越火星,问题越脑残。
……

劉鑫
March.Liu

2009年6月30日 星期二

jQuery 风格的HTML文本转义

astinus开发过程中,我自己就在不断的使用。有次贴了一些JS代码进去,于是页面显示错误。显然,把源代码直接放进html文本了——好吧,从05年转去做网游以后,一直没有正经的做过web了。

那么,我需要一个方法转义。网上搜了一下,大部分是自己编写一个正则替换。不过我的习惯是尊重既有的资源。有人提出可以用dom的功能。先作为innerTEXT传给一个dom对象,再取innerHTML属性,就可以取到转义后的文本了。

方法不错,不过写法上,有没有取巧的办法呢?

JQuery社区有人给出了办法:假设有文本 context,可以对一个jQuery对象 $(x)进行 $(x).text(context).html(),就会返回一个转义后的文本。

其实刚刚想起来,很多时候完全可以用text()函数对jQuery对象赋值就好了……



--
柳文扬先生逝世两周年祭
……

劉鑫
March.Liu