你可能不知道的MySQL

March 11th, 2009

前言:

实验的数据表如下定义:

mysql> desc tbl_name;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| uid   | int(11)      | NO   |     | NULL    |       |
| sid   | mediumint(9) | NO   |     | NULL    |       |
| times | mediumint(9) | NO   |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

存储引擎是MyISAM,里面有10,000条数据。

一、”\G”的作用

mysql> select * from tbl_name limit 1;
+--------+--------+-------+
| uid    | sid    | times |
+--------+--------+-------+
| 104460 | 291250 |    29 |
+--------+--------+-------+
1 row in set (0.00 sec)
mysql> select * from tbl_name limit 1\G;
*************************** 1. row ***************************
  uid: 104460
  sid: 291250
times: 29
1 row in set (0.00 sec)

有时候,操作返回的列数非常多,屏幕不能一行显示完,显示折行,试试”\G”,把列数据逐行显示(”\G”挽救了我,以前看explain语句横向显示不全折行看起来巨费劲,还要把数据和列对应起来)。

二、”Group by”的”隐形杀手”

mysql> explain select uid,sum(times) from tbl_name group by uid\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tbl_name
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10000
        Extra: Using temporary; Using filesort
1 row in set (0.00 sec)
mysql> explain select uid,sum(times) from tbl_name group by uid order by null\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tbl_name
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10000
        Extra: Using temporary
1 row in set (0.00 sec)

默认情况下,Group by col会对col字段进行排序,这就是为什么第一语句里面有Using filesort的原因,如果你不需要对col字段进行排序,加上order by null吧,要快很多,因为filesort很慢的。

三、大批量数据插入

最高效的大批量插入数据的方法:

load data infile ‘/path/to/file’ into table tbl_name;

如果没有办法先生成文本文件或者不想生成文本文件,可以一次插入多行:

insert into tbl_name values (1,2,3),(4,5,6),(7,8,9)…

注意一条sql语句的最大长度是有限制的。如果还不想这样,可以试试MySQL的prepare,应该都会比硬生生的逐条插入要快许多。

如果数据表有索引,建议先暂时禁用索引:

alter table tbl_name disable keys;

插入完毕之后再激活索引:

alter table tbl_name enable keys;

对MyISAM表尤其有用。避免每插入一条记录系统更新一下索引。

四、最快复制表结构方法

mysql> create table clone_tbl select * from tbl_name limit 0;
Query OK, 0 rows affected (0.08 sec)

只会复制表结构,索引不会复制,如果还要复制数据,把limit 0去掉即可。

五、加引号和不加引号区别

给数据表tbl_name添加索引:

mysql> create index uid on tbl_name(uid);

测试如下查询:

mysql> explain select * from tbl_name where uid = '1081283900'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tbl_name
         type: ref
possible_keys: uid
          key: uid
      key_len: 4
          ref: const
         rows: 143
        Extra:
1 row in set (0.00 sec)

我们在整型字段的值上加索引,是可以用到索引的,网上不少人误传在整型字段上加引号无法使用索引。修改uid字段类型为varchar(12):

mysql> alter table tbl_name change uid uid varchar(12) not null;

测试如下查询:

mysql> explain select * from tbl_name where uid = 1081283900\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tbl_name
         type: ALL
possible_keys: uid
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10000
        Extra: Using where
1 row in set (0.00 sec)

我们在查询值上不加索引,结果索引无法使用,注意安全。

六、前缀索引

有时候我们的表中有varchar(255)这样的字段,而且我们还要对该字段建索引,一般没有必要对整个字段建索引,建立前8~12个字符的索引应该就够了,很少有连续8~12个字符都相等的字段。

为什么?更短的索引意味索引更小、占用CPU时间更少、占用内存更少、占用IO更少和很更好的性能。

七、MySQL索引使用方式

MySQL在一个查询中只能用到一个索引(5.0以后版本引入了index_merge合并索引,对某些特定的查询可以用到多个索引,具体查考[中文] [英文]),所以要根据查询条件建立联合索引,联合索引只有第一位的字段在查询条件中能才能使用到。

如果MySQL认为不用索引比用索引更快的话,那么就不会用索引。

mysql> create index times on tbl_name(times);
Query OK, 10000 rows affected (0.10 sec)
Records: 10000  Duplicates: 0  Warnings: 0
mysql> explain select * from tbl_name where times > 20\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tbl_name
         type: ALL
possible_keys: times
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10000
        Extra: Using where
1 row in set (0.00 sec)
mysql> explain select * from tbl_name where times > 200\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tbl_name
         type: range
possible_keys: times
          key: times
      key_len: 3
          ref: NULL
         rows: 1599
        Extra: Using where
1 row in set (0.00 sec)

数据表中times字段绝大多数都比20大,所以第一个查询没有用索引,第二个才用到索引。

Tags: , ,

一生所爱

March 4th, 2009

用自建的推荐系统给自己推荐歌曲,排在前面的居然有一首《一生所爱》,看大话西游的时候就特别喜欢这首歌,一直不知道叫什么名字,人生要是如算法那样简单该是多么的好。

一生所爱

曲:卢冠廷
词:唐书琛
编:卢冠廷
唱:卢冠廷

从前现在过去了再不来
红红落叶长埋尘土内
开始终结总是没变改
天边的你飘泊白云外
苦海翻起爱浪
在世间难逃避命运
相亲竟不可接近
或我应该相信是缘份
情人别后永远再不来(消散的情缘)
无言独坐放眼尘世外(愿来日再续)
鲜花虽会凋谢(只愿)但会再开(为你)
一生所爱隐约(守候)在白云外(期待)
苦海翻起爱浪
在世间难逃避命运
相亲竟不可接近
或我应该相信是缘份
苦海翻起爱浪
在世间难逃避命运
相亲竟不可接近
或我应该相信是缘份

自觉好像一条狗。

PHP SESSION解惑

February 28th, 2009

一、PHP SESSION原理

我们知道,session是在服务器端保持用户会话数据的一种方法,对应的cookie是在客户端保持用户数据。HTTP协议是一种无状态协议,服务器响应完之后就失去了与浏览器的联系,最早,Netscape将cookie引入浏览器,使得数据可以客户端跨页面交换,那么服务器是如何记住众多用户的会话数据呢?

首先要将客户端和服务器端建立一一联系,每个客户端都得有一个唯一标识,这样服务器才能识别出来。建议唯一标识的方法有两种:cookie或者通过GET方式指定。默认配置的PHP使用session的时会建立一个名叫”PHPSESSID”的cookie(可以通过php.ini修改session.name值指定),如果客户端禁用cookie,你也可以指定通过GET方式把session id传到服务器(修改php.ini中session.use_trans_sid等参数)。

我们查看服务器端session.save_path目录会发现很多类似sess_vv9lpgf0nmkurgvkba1vbvj915这样的文件,这个其实就是session id “vv9lpgf0nmkurgvkba1vbvj915″对应的数据。

真相就在这里,服务器将session id传递到服务器,服务器根据session id找到对应的文件,读取的时候对文件内容进行反序列化就得到session的值,保存的时候先序列化再写入。

事实就是这样,所以如果服务器不支持session或者你想自定义session,完全可以DIY,通过PHP的uniqid生成永不重复的session id,然后找个地方存储session的内容即可,你也可以学flickr把session存储在MySQL数据库中。

二、使用session之前为什么必须先执行session_start()?

了解的原理之后,所谓的session其实就是客户端一个session id服务器端一个session file,新建session之前执行session_start()是告诉服务器要种一个cookie以及准备好session文件,要不然你的session内容怎么存;读取session之前执行session_start()是告诉服务器,赶紧根据session id把session文件反序列化。

只有一个session函数可以在session_start()之前执行,session_nam():读取或指定session名称(比如默认的就是”PHPSESSID”),这个当然要在session_start之前执行。

三、session影响系统性能

session在大访问量网站上确实影响系统性能,影响性能的原因之一由文件系统设计造成,在同一个目录下超过10000个文件时,文件的定位将非常耗时,PHP支持session目录hash,我们可以通过修改php.ini中session.save_path = “2;/path/to/session/dir”,那么session将存储在两级子目录中(修订:参见david回复),不过好像PHP session不支持创建目录,你需要事先把那么些目录创建好 。

还有一个问题就是小文件的效率问题,一般我们的session数据都不会太大(1~2K),如果有大量这样1~2K的文件在磁盘上,IO效率肯定会很差,PHP手册上建议使用Reiserfs文件系统,不过Reiserfs的前景堪忧,Reiserfs的作者把媳妇给杀了,SuSE也抛弃了Reiserfs。

其实还有很多中存储session的方式,可以通过php -i|grep “Registered save handlers”查看,比如Registered save handlers => files user sqlite eaccelerator可以通过文件、用户、sqlite、eaccelerator来存,如果服务器装了memcached,还有会mmcache的选项。当然还有很多,比如MySQL、PostgreSQL等等。都是不错的选择。

四、session的同步

我们前端可能有很多台服务器,用户在A服务器上登录了,种下了session信息,然后访问网站的某些页面没准跳到B服务器上去了,如果这个时候B服务器上没有session信息又没有做特殊处理,可能就会出问题了。

session同步有很多种,如果你是存储在memcached或者MySQL中,那就很容易了,指定到同样的位置即可,如果是文件形式的,你可以用NFS统一存储

还有一种方式是通过加密的cookie来实现,用户在A服务器上登录成功,在用户的浏览器上种上一个加密的cookie,当用户访问B服务器时,检查有无session,如果有当然没问题,如果没有,就去检验cookie是否有效,cookie有效的话就在B服务器上重建session。这种方法其实很有用,如果网站有很多个子频道,服务器也不在一个机房,session没办法同步又想做统一登录那就太有用了。

当然还有一种方法就是在负载均衡那一层保持会话,把访问者帮定在某个服务器上,他的所有访问都在那个服务器上就不需要session同步了,这些都是运维层面的东西。

就说这么多吧,根据自己的应用来选择使用session,不要因为大家都说session影响系统性能就畏首畏尾,知道问题,解决问题才是关键,惹不起躲得起不适合这里。

Tags: ,

改善MySQL上16进制标识符性能的5种方法

February 13th, 2009

前言:

原文地址:5 ways to make hexadecimal identifiers perform better on MySQL,作者是《High Performance MySQL, Second Edition.》的主要作者。这里是原文的节译。

在我们的业务中,经常会用md5()或者uuid()作为作为数据的标识,比如B2C网站,经常会有一些电子优惠券,常用就是通过md5()获得一个32位字符串,给字符串建立索引,然后用户输入优惠券代码之后系统查出此优惠券对应的折扣率:select discount from coupon where id = ’0cc175b9c0f1b6a831c399e269779527′,这样的做法存在两个问题:数据和索引很大以及非顺序数据,本文忽略“非顺序数据”的问题,因为顺序数据和非顺序数据的优劣很大程度上由技术特性决定。这里讲得是如何在使用16进制大数据的情况下保持好的性能,主要讲的是MySQL数据库,对其他数据库应该也起作用。

一、小心你的字符编码

看一下下面这个SQL语句:

mysql> explain select * from t where id = ’0cc175b9c0f1b6a831c399e269772661′\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 98
ref: const
rows: 1
Extra: Using index

为什么索引是98byte?简单,因为我们用的是UTF-8:

CREATE TABLE `t` (
`id` varchar(32) NOT NULL,
PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

没有必要用UTF-8存储16进制数据,采用UTF-8存储16进制数据不会增加磁盘空间的占用,但是当你使用排序(order by)、统计(group by)、隐式临时表(MySQL查询时自建的临时表)等的时候,需要耗费多达3倍的内存和硬盘空间,至少在MySQL上是这样的。

二、使用固定长度,不要有空值

可以看到上面那个表采用的是varchar字段,我们都知道varchar是一个变长字段,如果你确认所有的数据都一样长(比如像md5()出来的,都是32个字节),最好使用char()定长字段,另外就是如果字段中不可能有空值,最好指定为not null

三、使用二进制数据存储

实际上,你并不需要存储字符串,16进制字符串不过是数字的另一种表现形式,直接保存数字。比如:00000000000000000000000000002E2A是什么呢?这正是16进制数字11818,使用一个4字节(或者更少)的整型代替一个32字节的字符存储更好。

问题是MySQL没有合适的类型来存储这么大的数字,它们比BIGINT还要大很多,不过MySQL允许我们存储到BINARY字段,数据更紧凑比较起来更快速,可以使用HEX()和UNHEX()来转换格式,或者16进制操作符’x’

mysql> select x’7861707262′;
+—————+
| x’7861707262′ |
+—————+
| xaprb         |
+—————+

用BINARY(16)代替varchar(32)之后:

explain select * from t where id = x’0cc175b9c0f1b6a831c399e269772661′\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 16
ref: const
rows: 1
Extra: Using index

索引长度变成16字节了(对比原来98字节),减小了不少,如果你使用的是UUID(),存入之前先用replace()把”-”题换掉。

四、使用前缀索引

很多时候,我们不需要索引全部字段,索引字段的前8~10个字符就可以了,如果你当前存储的是字符串,这很有用,不用转换成BINARY,只是改变索引策略而已。

你可以通过类似下面的SQL语句判断合适的前缀索引个数:

mysql> select count(distinct id), count(distinct left(id, 8)), count(distinct left(id, 9)) from t\G
*************************** 1. row ***************************
count(distinct id): 2
count(distinct left(id, 8)): 2
count(distinct left(id, 9)): 2

找一个差不多行就可以,不一定要索引“唯一”。

五、创建hash索引

直接上代码,不用多余的解释:

mysql> alter table t add crc int unsigned not null, add key(crc);
mysql> update t set crc=crc32(id);
mysql> explain select * from t use index(crc) where id = ’0cc175b9c0f1b6a831c399e269772661′ and crc=crc32(’0cc175b9c0f1b6a831c399e269772661′)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t
type: ref
possible_keys: crc
key: crc
key_len: 4
ref: const
rows: 1
Extra: Using where

使用crc32()获取到字符串的校验值,一般这样的碰撞概率不会太大,索引数字比索引字符不知道要快多少,极力推荐,不仅仅适用16进制字符,任意字符也适合:

mysql> select crc32(‘good good study, and day day up!’);
+——————————————-+
| crc32(‘good good study, and day day up!’) |
+——————————————-+
|                                2265998365 |
+——————————————-+
1 row in set (0.00 sec)

总结:

16进制标识符让表和索引的变大,降低比较和查找的速度,建议非不得已不要使用,如果非要使用,希望上面的五条建议对你有用。

后记:

有时候我们确实必须使用字符串作为唯一标识符,比如我这篇博客地址:http://chaoqun.17348.com/2009/02/5-ways-to-make-hexadecimal-identifiers-perform-better-on-mysql,系统需要通过“5-ways-to-make-hexadecimal-identifiers-perform-better-on-mysql”去找出相应的博文,这个时候使用前面的建议就非常有用,很不幸的是WordPress没有用。

Tags:

扩展Digg和其他的网络应用

February 12th, 2009

前言:

关于Digg的架构,之前Fenng已经写过一篇Digg 网站架构的文章,Fenng在文章开头说“越来越发现其实都是自我重复的劳动,后续的信息都是嚼别人剩下的甘蔗。”,其实是Fenng过谦了,我就是从DBA notes一点一滴开始学习的。

原文在Scaling Digg and Other Web ApplicationsTodd Hoff总能给我们带来一些有意思的文章。这里既不直译也不全译,保持原文骨干加上肤浅的理解。

Digg用户在3000W左右,网站每秒请求数达到13000个,规模算是很大了,Lamp(Linux+Apache+MySQL+PHP)结构,Web2.0网站中钟情PHP的不少,国外的Facebook、Digg、Flickr…,国内的新浪博客、开心网、51.com等等,扩展的不是编程语言而是网站架构,因为编程语言从来都不会是网站瓶颈,每种语言都有自己适合做和不适合做的事情。喜欢比较语言快慢的可以看看http://shootout.alioth.debian.org/,看看而已不要用速度去衡量编程语言的优劣。

Digg的扩展战略:

  • 扩展是有特殊性的,当已有的方案不能满足需求时,你需要根据自己的特殊需求来建立方案。要熟悉自己的业务需求,找出系统的短板,解决问题,避免过度优化。
  • 编程语言不需要扩展,因为语言从来都不是瓶颈,把PHP提速300%不会是什么大问题。
  • 去中心化,分布式处理高并发请求。
  • 水平扩展,用更多更便宜的机器代替更高效更昂贵的机器。
  • 数据库驱动的网站需要作水平分区和垂直分区扩展,水平分区就是把数据放到不同的机器上,比如按照时间划分:2009年的数据一组服务器,2009年的数据一组服务器,或者按照用户ID划分:每200W用户一组服务器;垂直分区把一个表分成多个表,每个表只包含一部分字段,可以按照业务分或者常用的“动静分离”,比如对文章计数的字段大可以和文章内容字段分开。
  • 建立数据应用层,程序不要直接操作数据库分区,这样可以保持好的扩展性。这一点非常重要,如果没有数据应用层API,一旦对数据库分区做了修改,你的程序就需要大改了。程序的稳定性和持续性就不能保证。比较常见的MVC开发模式,数据层和逻辑层分开,一旦数据库底层改变了,只需要对数据层做简单的修改,不影响既有业务。
  • 一致性、高可用性、分区容忍性(Partition Tolerance)三者只能取其二,具体的阅读http://camelcase.blogspot.com/2007/08/cap-theorem.html,一致性:每个人看到的都一样,哪怕当时就有更新;高可用性:部分故障不影响使用;分区容忍性:数据分区还能保持系统所有属性。
  • 数据分区要求反常规化,这是在Digg是大问题,同样的数据要复制到多个对象上去,而且还好保持同步。
  • 采用异步的队列架构,把任务放到队列中交由多台机器处理,而不是一次同步处理,关于异步任务处理可以阅读 Flickr – Do the Essential Work Up-front and Queue the RestThe Canonical Cloud Architecture
  • icons和图片采用MogileFS存储,MogileFS是一个分布式的文件系统,由memcached作者开发,国内的好看簿也有使用MogileFS。
  • 采用APC作为PHP加速器,Facebook用的也是这个,PHP5.30以后将自带APC加速器,同样的产品还有eAcceleratorXcache,国内可能用eAccelerator的比较多,windows的版本我一般用这个网站编译的
  • 缓存策略:永久性缓存和失效期缓存;基本不更新的内容采用文件缓存;动态缓存采用memcached;极少修改的数据采用APC缓存,APC不是分布式缓存,直接缓存的机器内存中,所以速度更快,适合缓存那些配置文件,比如MySQL的配置信息;缓存采用链责任模式,首先看PHP全局变量,如果没有就查APC,再没有就查memcached,最后没有就查数据库,这个觉得不太必要,你的缓存存在哪来你自己还不清楚阿。
  • Digg的推荐引擎是一个自定义的图形数据库,数据库最终保证一致性,先写如一个分区,然后再逐步写到其他的分区。在更新的过程中,可能取出来的数据不一致,是因为有不同的数据库分区处理,最终将会是一致。

MemcacheDB:性能革命性的一步

想象一下Kevin Rose,Digg的创建者,当前有4000个追随者,如果Kevin Rose一天digg一次,将会有4000个写操作,最热门的digg有最多的追随者,这会导致巨大的性能瓶颈:

  • 你不能同时更新4000个追随者的帐号信息,幸运的是我们可以通过前面提到的异步队列方式解决。
  • Digg面临的海量写操作,如果平均每个人有100个追随者,一天就有3亿条写操作,平均到每秒有3000个写操作,每天有5GB的数据存储,在50~60台服务器之间有5TB的数据交换。

如果是MySQL的话估计早就挂了,Digg在笔记本上的测试是memcachedb每秒可以处理15000个写操作,memcachedb的测试结果表明每秒可以支持23000个写操作或者64000个读操作,足以应付Digg洪水般的请求。

memcachedb是一个分布式的kye-value型数据持久化解决方案,提供兼容memcached协议接口,由Sina工程师Steve Chu开发。

提到Digg为什么采用memcachedb,Digg的工程师给出的理由是:需要持久化缓存数据,与memcached协议兼容,Digg已经有很多业务应用了memcached,可以很方便的切换到memcachedb上去,开源。

后记:

很高兴的看到除了Sina,memcachedb又有一个超重量级的应用了,性能和可靠性都得到了极大的检验,大可放心应用生产环境中。我还说今年有空写个玩具版的类memcachedb项目,还是不要了,有时间还是去做些更有意义的事情,无谓重复发明。

Tags: ,

基于Infobright的MySQL数据仓库方案测试

February 10th, 2009

数据仓库之父Bill Inmon在1991年出版的“Building the Data Warehouse”一书中所提出的定义被广泛接受——数据仓库(Data Warehouse)是一个面向主题的(Subject Oriented)、集成的(Integrated)、相对稳定的(Non-Volatile)、反映历史变化(Time Variant)的数据集合,用于支持管理决策(Decision Making Support)。

上面这段是抄的,简单的讲数据仓库是一个面向主题的、集成的、不可更新的、随时间不断变化的数据集合,它用于支持企业或组织的决策分析处理,这一句也是抄的,更直白的举例就是公司的历史交易数据集合,网站的历史访问数据集合,这一句是原创的。

Infobright是开源的MySQL数据仓库解决方案,引入了列存储方案,高强度的数据压缩,优化的统计计算(类似sum/avg/group by之类),下面是Infobright的架构图:

安装篇:

Infobright目前还不支持windows系列操作系统,不过你可以装在Linux虚拟机上,或者从官方直接下载做好的VMWARE虚拟机,这里的测试环境是CentOS 5.2 32bit操作系统。Infobright也不支持以插件的形式集成到已有的MySQL系统中去,官方的说法是对MySQL做了很多修改,不支持以插件的形式使用。

详细的安装参照http://www.infobright.org/wiki/Install_Guide/,需要注意的是如果你的系统中已经有MySQL(默认端口3306),你需要重新设定一下安装参数,比如像我的:

./install-infobright.sh –datadir=/data/infobright/data –cachedir=/data/infobright/cache –port=9527 –config=/etc/my-ib.cnf –socket=/tmp/mysql-ib.sock –user=mysql –group=mysql

配置文件包括MySQL配置文件(/etc/my-ib.cnf)和Infobright配置文件(在安装时候指定的数据目录内,比如/data/infobright/data/brighthouse.ini),如果需要支持MySQL原生的SQL查询,需要修改brighthouse.ini设定

AllowMySQLQueryPath = 1

这样不至于有些SQL语句不能执行,安装完成,启动服务:

/etc/init.d/mysqld-ib start

通过命令行:mysql-ib -uroot直接连接,默认密码为空,修改密码:

/usr/local/infobright/bin/mysqladmin  -u root -p password NEWPASSWORD

测试篇:

测试数据是一个1.5GB大的文本数据,数据格式类似:

用户ID  内容ID  用户打分
765331  3868    5
716091  3868    3
1663216 3868    3
51971   3868    5

在测试数据库中新建两张表,一个为Infobright支持的brighthouse存储引擎,一个为MySQL原生的MyISAM存储引擎,其他内容一致:

CREATE TABLE `t_ib` (
`uid` mediumint(9) NOT NULL,
`cid` smallint(6) NOT NULL,
`rating` tinyint(4) NOT NULL
) ENGINE=BRIGHTHOUSE;

CREATE TABLE `t_mis` (
`uid` mediumint(9) NOT NULL,
`cid` smallint(6) NOT NULL,
`rating` tinyint(4) NOT NULL
) ENGINE=MyISAM

将数据load进表:

load data infile ‘path/to/data.txt’ into table table_name fields terminated by “\t”;

我们比较一下文件大小:

数据类型      数据大小
data.txt      1.5GB
data.tar.gz   429MB
MyISAM表      671MB
Infobight表   280MB

超过5:1的压缩比,虽然没有传说中10:1,但数据的大小比tar.gz过还要小近一半,压缩能力可见一斑。

准备进行SQL的测试,不能在BRIGHTHOUSE存储引擎上建索引,因为根本就不需要建,我们在MyISAM引擎表上建立如下索引:

create index id on t_mis(cid);

执行下列SQL语句,查询内容ID大于9527的条目数(为了节省篇幅,略去结果集,只返回执行时间):

mysql> select count(*) from t_mis where cid > 9527;
1 row in set (41.81 sec)
mysql> select count(*) from t_ib where cid > 9527;
1 row in set (13.66 sec)

Infobright花费的时间只有MyISAM的1/4左右,再测试一下找出被用户打分最多的10条内容:

mysql> select cid from t_mis group by cid order by count(*) desc limit 10;
10 rows in set (1 min 21.30 sec)
mysql> select cid from t_ib group by cid order by count(*) desc limit 10;
10 rows in set (39.02 sec)

Infobright大概只花费了MyISAM 1/3多一点的时间。再查询一下评价最好的10条内容:

mysql> select cid from t_mis group by cid order by avg(rating) desc limit 10;
10 rows in set (6 min 16.15 sec)
mysql> select cid from t_ib group by cid order by avg(rating) desc limit 10;
10 rows in set (1 min 1.25 sec)

不到1/6时间。

后记:

强悍的压缩比率,高效的SQL统计性能,Infobright的表现很是不错。对于网站开发者来说,建立一个Infobright数据仓库系统用来保存网站的历史访问元数据应该是一个不错的选择,尤其是需要OLAP或者更为灵活的统计功能的时候。

Tags: , , ,

Smarty到底怎么了?

February 5th, 2009

早期的PHP开发者发现把PHP代码和HTML代码混在一起,维护起来非常的费劲,于是Smarty应运而生,将PHP代码和HTML代码分离,把MVC模式中的V(View显示)首先剥离,大大提高了程序的可阅读性和可维护性,于是有一大批的使用者,甚至到现在,如果你去找一份PHP的工作,如果不了解Smarty,估计够呛能拿到offer.

Smarty,这个PHP模板引擎曾经的代名词,最近的日子貌似不太好过,3.0Alpha出来了,不过很多人认为换汤不换药,更有甚者用回光返照来形容,真是英雄末路。

国外甚至有个No Smarty的网站,数典Smarty的种种”罪行”,然后给出了很多种替代方案(基本上是主流的PHP框架),个人觉得不太公平,一个PHP模板引擎和一个完备的MVC框架能有可比性?

一个纯粹的PHP模板引擎在当前MVC当道的年代,难免不那么叫好,而且Smarty开发年代久远,有些设计确实不是太好,Smarty最早开发的时候估计是给页面制作人员(builder)来用的,以至于它的语法都和PHP不兼容,比如foreach语句,在Smarty里面就要用{foreach from=$foo item=bar},可是套模板的工作基本上都是PHP程序员在做,不兼容PHP语法还需要重新去学习Smarty,而两种不同的风格足够让人抓狂。

如果是因为效率的问题放弃使用Smarty,大可不必,很多的网站还在用,比如我们,瓶颈不会出现在Smarty模板上,Smarty会把模板代码编译成PHP代码,下次调用的时候检测如果模板没有改动直接调用编译好的PHP代码,应该和原生的PHP代码差别不太大。而且Smarty自带了很多很有用的函数,比如转义,用起来还是很舒服的。

不过,最近Smarty项目已经从PHP官方移除了,访问http://smarty.php.net,提示Smarty is no longer a subproject of the PHP project, and has subsequently moved to its own domain: www.smarty.net,真是祸不单行。

Tags: ,

基于Slope One的相关歌曲推荐算法

February 3rd, 2009

本博客所有原创文章采用知识共享署名-非商业性使用-相同方式共享,转载请保留链接http://chaoqun.17348.com/2009/02/slope-one-for-music-recommender-system/

不知不觉,研究歌曲相关推荐快半年了,第一篇文章利用orange进行关联规则挖掘完成于2008.08.26,到现在基本搞定基于矩阵奇异值分解(SVD)的协同过滤算法,期间得到了很多朋友的帮助,在此致谢。有些收获,将逐步的分享出来,有兴趣的可以参照研习。

对于Slope One算法,不熟悉的可以参照我之前的文章:Slope one:简单高效的推荐算法,已经被很多人证明有很好的推荐效果。

Slope one算法中有一个很重要的步骤是获取用户的打分数据,这个对很多网站都很费劲,很多用户都会听歌,但大多懒得去给歌曲打分,另外用户打分的时候会比较困惑,该打多少分呢?喜欢这首歌,是打4分还是5分呢?费劲。

我这里给出的是另外一种方法,做法是分析用户的听歌记录,一般网站都会记录这样的记录,统计一段时间内用户的听歌记录,我们得到下面格式的数据:

用户ID    歌曲ID    听歌次数

比如某个片段:

3389    9527    23
3389    9528    56
3306    1211    78
3306    9527    45

表示用户3389听歌曲9527的次数是23,听9528的次数是56,诸如此类。这样的数据当然不能直接用来做Slope one,需要把数据格式化到某个区间。我们分析一下用户听歌的行为,一般来说最喜欢的歌曲听的最多,越喜欢的歌曲听的越多,听的少的歌曲自然不那么喜欢。所以我们可以简单的模拟用户对歌曲的打分:

用户对歌曲的打分 = 用户听此歌曲的次数 / 用户听单首歌曲的最大次数

这样就可以把打分数据规整到0~1之间,还是上面的数据:

3389    9527    23/56
3389    9528    56/56
3306    1211    78/78
3306    9527    45/78

用户听的最多的歌曲打分是1,其他歌曲的打分等于听歌次数除以最大次数,我们就获得了用户的打分数据了。剩下的工作就是按照标准的Slope One流程走了,程序代码可以参考:http://code.google.com/p/openslopeone/

贴出几个实例大家看看,第一次做的结果,再去做的话应该比这个要好一些:

歌曲 推荐歌曲
南无大悲观世音菩萨   刘小茜 梵音大悲咒   齐豫
大悲咒   齐豫
观世音菩萨发愿偈.大悲咒   齐豫
大悲咒   邝美云
般若波罗蜜多心经   齐豫
大悲咒   齐豫
清净法身佛   齐豫
阿弥陀佛在心间   小娟
吉祥如意   凤凰传奇
好一朵茉莉花   朱昌耀 理查德-克莱德曼《梦中的婚礼》   合辑(欧美)
茉莉花(汉族民歌)   雷佳
好一朵茉莉花-笛子   合辑(内地)
最浪漫的事   赵咏华
沧海一声笑   罗文
how can i keep from singing   Enya
生死不离   成龙
羞答答的玫瑰静悄悄地开   孟庭苇
过三关   吴卓羲 别怪她   吴卓羲
生命有一种绝对   五月天
春日(电视剧’春日’主题曲)   吴卓羲
爱玛仕小姐   吴卓羲
别人问起   吴卓羲
别怪她(Dance Remix)   吴卓羲
别怪她 – 吴卓羲   合辑
米老鼠   五月天
One Last Dance   Craig David
新不了情   薛凯琪

Tips:

做Slope one之前最好过滤掉那些超热门的歌曲,因为很多人都听过了,会让这些歌曲经常出现在推荐结果里面,我的感觉是过滤掉Top100就可以了。

Tags: , , , ,

Berkeley DB:网站数据缓存方案测试

January 28th, 2009

本博客所有原创文章采用知识共享署名-非商业性使用-相同方式共享,转载请保留链接http://chaoqun.17348.com/2009/01/bdb-cache/

做网站,好像大家都比较喜欢文件缓存,把那些读操作多写操作少的内容缓存成一个文件放服务器硬盘上,下次直接读取或者更新。针对每一个key缓存成一个文件,当需要缓存的内容多的时候,文件数也就相应的多,这么多文件的同步和备份都是大问题,数据的可靠性也无从保证。

可是为了”效率”,我们也忍了,但文件缓存的效率真的好吗?有没有更好的方案?当然,memcached是很好的解决方案,今天这里测试另外一种方案:使用Berkeley DB作为网站数据缓存方案。

测试环境:CentOS 5.2,Core2 T5500@ 1.66GHz,1.5G内存,Ext3文件系统,apache 2.2.3,php 5.2.8 with php_dba

数据初始化:为了不至于在一个目录下文件数目过多,文件缓存分了两级hash目录,初始化数据数10万条。

/**
 * 初始化缓存
 *
 * @param int $limit
 */
function init_cache($limit = 100000)
{
    $str = 'good good study,and day day up.';
    $bdb = dba_open('./bdb.db', 'c', 'db4');
    for ($i = 0; $i < $limit; $i++)
    {
        $data = str_repeat($str, rand(1,100));
        dba_insert($i, $data, $bdb);
        $cache_file = get_cache_file($i);
        file_put_contents($cache_file, $data);
    }
    dba_close($bdb);
}
 
/**
 * 获得缓存文件
 *
 * @param string $cache_key
 * @param string $md5_key
 * @return string
 */
function get_cache_file($cache_key, $md5_key = '9527')
{
    $dir_md5 = md5($cache_key . $md5_key);
    $file_md5 = md5($cache_key);
    $dir = './cache/' . substr($dir_md5, 0, 2);
    is_dir($dir) || mkdir($dir);
    $dir .= '/' . substr($dir_md5, 2, 2);
    is_dir($dir) || mkdir($dir);
    return  $dir  . '/' . $file_md5 . '.txt';
}

同样的缓存内容,占用磁盘大小:

文件缓存:348.9MB
BDB缓存:296.5MB

BDB缓存占用磁盘空间要小一些。

测试程序尽可能的模拟真实环境,90%的读操作,10%的写操作,很多应用可能98%的读操作,2%的写操作,另外还模拟了一些新增缓存。

文件缓存测试程序代码:

/**
 * 文件缓存测试程序
 */
require_once './common.php';
// 随机一个key
$key = rand(0, 109999);
 
$cache_file = get_cache_file($key);
 
// 读写标记
$flag = rand(0,9);
 
if (file_exists($cache_file) && 9 > $flag)
{
    echo file_get_contents($cache_file);
}else
{
    file_put_contents($cache_file, str_repeat('learn from LeiFeng!', rand(1,100)));
    echo file_get_contents($cache_file);
}

BDB缓存测试程序代码:

/**
 * BDB缓存测试程序
 */
// 随机一个key
$key = rand(0, 109999);
 
// 读写标记
$flag = rand(0,9);
 
$bdb = dba_popen('./bdb.db', 'w', 'db4');
 
$data = dba_fetch($key, $bdb);
 
// dba_close($bdb);
 
if ($data && 9 > $flag)
{
    echo $data;
}else
{
    dba_replace($key, str_repeat('learn from Comrade LeiFeng!', rand(1,100)), $bdb);
    echo dba_fetch($key, $bdb);
}
dba_close($bdb);

文件缓存测试结果:

ab -n10000 -c200 http://127.0.0.1/labs/TestCache/file.php

Requests per second: 189.92 [#/sec] (mean)
Time per request: 1053.055 [ms] (mean)
Time per request: 5.265 [ms] (mean, across all concurrent requests)

BDB缓存测试结果:

ab -n10000 -c200 http://127.0.0.1/labs/TestCache/bdb.php

Requests per second: 220.69 [#/sec] (mean)
Time per request: 906.249 [ms] (mean)
Time per request: 4.531 [ms] (mean, across all concurrent requests)

反复测试了几次,文件缓存每秒大概能处理170~200个请求,BDB缓存每秒大概能处理190~230个请求,貌似BDB还比文件缓存要快一些,其实BDB还可以更快一些,我们在测试代码里面用了不管是读操作还是写操作都用了$bdb = dba_popen(‘./bdb.db’, ‘w’, ‘db4′);写的方式,其实我们很多的应用只是读操作,如果把BDB测试代码换成只有在写的时候才用写操作:

/**
 * BDB缓存测试程序
 */
// 随机一个key
$key = rand(0, 109999);
 
// 读写标记
$flag = rand(0,9);
 
$bdb = dba_popen('./bdb.db', 'r', 'db4');
 
$data = dba_fetch($key, $bdb);
 
if ($data && 9 > $flag)
{
    echo $data;
}else
{
    dba_close($bdb);
    $bdb = dba_popen('./bdb.db', 'w', 'db4');
    dba_replace($key, str_repeat('learn from Comrade LeiFeng!', rand(1,100)), $bdb);
    echo dba_fetch($key, $bdb);
    dba_close($bdb);
}

测试结果:

ab -n10000 -c200 http://127.0.0.1/labs/TestCache/bdb.php

Requests per second: 361.62 [#/sec] (mean)
Time per request: 553.068 [ms] (mean)
Time per request: 2.765 [ms] (mean, across all concurrent requests)

有点吓人,速度快要翻翻了。

后记:

php_dba扩展中建立数据库句柄,有dba_open和dba_popen两个函数,dba_popen建立的是持久连接,我在测试中发现使用dba_popen的性能要大大好于dba_open,测试中使用亦非常稳定。

其实memcachedb的核心还是BDB,引入了缓存和兼容memcached协议的socket接口,memcachedb当然会比单纯的BDB快,如果没办法用memcachedb,纯粹的BDB也是一种不错的方案,照测试看,各个方面比文件缓存都要好一些,当然,写频繁的操作可能要差些,因为存在文件锁的问题,换个思路想,写频繁缓存又有什么用呢。

Tags: ,

Key-Value型数据存储

January 20th, 2009

本博客所有原创文章采用知识共享署名-非商业性使用-相同方式共享,转载请保留链接http://chaoqun.17348.com/2009/01/key-value-stores/

早上看到博客Anti-RDBMS: A list of distributed key-value stores(抵制关系型数据库:分布式key-value存储列表),最近的项目中也有用到类似Key-Value的存储。

memcached是最典型的key-value型存储,估计也是使用最广泛,但是由于memcached数据存放在内存中,服务重启或者系统down机之后就会丢失,只能用来缓存非关键数据(丟了没关系,重建就可以),或者考虑memcached的持久化方案:memcachedb,不过还是建议缓存次关键数据,比如文章点击数,写入失败或者丢了数据也不是太要命。

有时候我们的数据本身就是key-value型的,比如字典数据,就两列:单词->释义,这样的数据存mysql之类的关系型数据库就有点太”重”了,大可采用key-value型的存储方案,Berkeley DB就足够用了。

一直在关注CouchDB,已经是Apache的顶极项目了,系统稳定和版本更新自然有保障一些,居于Erlang语言开发,高并发也不是问题,relication也集成在里面,分布式也很容易做到,提供HTTP Restful接口,方便调用。个人觉得最适合股票类的网站,需要实时更新股票价格,用这个简直就是就像是定制的,不知道新浪财经的同事有没有关注。

Anti-RDBMS: A list of distributed key-value stores还提到HyperTable,百度居然赞助了这个项目,实在难得,说明项目确实不错,希望百度也分配一些工程师进去,完善开发HyperTable,到那个时候平民百姓也能用到BigTable了。

今年的也准备写个key-value的项目,计划中,关键字:Python,stackless,UDP,BDB ,memcached protocol,Low mem use.

Tags: ,