文章列表
 
2010年08月22日 星期日 下午 6:59
欢迎访问我的新主页:http://huoding.com/

作者:老王

以产品为中心的网站URL

URL设计是个很有讲究的问题,很多网站在这方面显得过于随意。

比如说我们有一个照相机网站,数据库里保存着canon 550d,其id是123,那举一个不好的例子:

http://www.foo.com/pictures/123.html
http://www.foo.com/comments/123.html

为什么说这些URL不好,看看下面的URL设计:

http://www.foo.com/123/pictures
http://www.foo.com/123/comments

这样就好多了,为什么这么说呢,因为对一个产品网站来说,产品是中心,URL的设计无疑应该以产品为中心来设计,而且URL本身就是有层次的,所以善用这种层次就会让网站URL更有条理性。否则如开始那样设计URL就变成以功能为中心来设计URL了,而不是以产品为中心。

不过我们还能做得更好,比如:

http://www.foo.com/canon/550d/pictures
http://www.foo.com/canon/550d/comments

抛弃id这种索然无味的标识符,采用更具语义的品牌名字和型号名字,会得到更友好的URL。

因为这样更有层次感:canon品牌中550d型号的图片(评论),不管是对浏览者而言,还是对搜索引擎而言,这种层次感都更简单易懂。

URL设计相关文章:

关于 URL 的一些不可不知的知识
Guidelines for URI Design

Write mysqlnd plugins in PHP

这算是最近最Coooool的一个新闻了,借助一个PECL扩展mysqlnd_uh,用PHP写Mysqlnd插件,不再有C语言门槛,这意味着你可以用PHP代码实现类似MySQL Proxy的功能,非常有诱惑力。详细信息参见PHP @ FrOSCon: the power of mysqlnd plugins

MySQL衍生版本

1:Percona Server
2:MariaDB
3:Drizzle

MySQL按时间点还原

如果是通过mysqldump备份备份的话,以InnoDB为例,命令大概如下:

mysqldump --all-databases --single-transaction --flush-logs --master-data=2 ...

这里的master-data很重要,这样可以在备份文件里生成类似下面的语句:

CHANGE MASTER TO MASTER_LOG_FILE='...',MASTER_LOG_POS=...;

在还原的时候,就可以通过MASTER_LOG_FILE和MASTER_LOG_POS确定二进制日志的起始位置:

mysqlbinlog --start-date=... --stop-date=...

命令里的flush-logs也是必要的,这样可以生成新的日志文件,查找起来就方便多了。

注意:flush-logs有副作用,如果你没有做好错误日志的备份,那么多次flush-logs后,错误日志会丢失。

Linux下查看硬盘信息


dmesg命令结果里有硬盘信息,但是太乱了,不容易查看。

如果是SCSI硬盘:

# cat /proc/scsi/scsi
Attached devices:
Host: scsi0 Channel: 00 Id: 00 Lun: 00
Vendor: MAXTOR   Model: ATLAS10K5_147SAS Rev: BMU6
Type:   Direct-Access                    ANSI SCSI revision: 05
Host: scsi1 Channel: 00 Id: 00 Lun: 00
Vendor: TEAC     Model: DVD-ROM DV28SV   Rev: D.0J
Type:   CD-ROM                           ANSI SCSI revision: 05


如果是IDE硬盘,估计很少有网站会用,但记一下命令也是值得的:

# find /proc/ide/ide[0-9]/hd? -type f -name model -exec cat {} \;

经过网友提示,还有一个smartctl命令可以查看硬盘信息:

# smartctl -i /dev/sda
smartctl version 5.38 [x86_64-redhat-linux-gnu] Copyright (C) 2002-8 Bruce Allen
Home page is http://smartmontools.sourceforge.net/

Device: MAXTOR   ATLAS10K5_147SAS Version: BMU6
Serial number: J4H05LFK
Device type: disk
Transport protocol: SAS
Local Time is: Mon Aug 23 11:05:50 2010 CST
Device does not support SMART


MySQL与GUID(UUID) COMB

在这里,我把GUID,UUID两个名词看做一样的东西,都是指一个唯一ID。

GUID(UUID)大家并不陌生,但是GUID COMB就不是很常见了。最初的概念由Jimmy Nilsson提出。先看看GUID(UUID),在MySQL里多数人似乎不喜欢使用它,主要原因在于InnoDB的影响:

1:InnoDB是聚集主键,也就是说数据要按照主键物理排序,由于GUID(UUID)的无序性,会打来巨大的磁盘IO操作。
2:InnoDB中非主键索引都包含主键,GUID(UUID)本身都很长,所以索引也会变大。

其中主要瓶颈集中在第一个问题上,至于第二个问题,还可以忍受。

正是为了解决第一个问题,于是出现了GUID(UUID) COMB,对于COMB类型的GUID(UUID)来说,它在保持唯一性的前提下,并不是完全无序的,大体上有一定的顺序性,如此一来就避免了巨大的磁盘IO。

MySQL本身有一个UUID函数,例子如下:

mysql> SELECT UUID();

603fd892-adec-11df-b4c2-676ff78bcf6c
61d8f29b-adec-11df-b4c2-676ff78bcf6c

多执行几次会发现MySQL UUID函数本身有一定的顺序性,但这种顺序性有限,重启服务之类的操作还会影响它。

网上到处搜索了一下,最后才发现MySQL下似乎现在还没有众所周知的GUID(UUID)COMB方案。
 
2010年08月15日 星期日 上午 10:21
欢迎访问我的新主页:http://huoding.com/

作者:老王

我是从《MySQL Admin Cookbook》一书中看到这个问题的,有一定的隐蔽性,遂记之。

友情提示:本文测试所用的MySQL版本是5.1.44-community,其它版本未测试,结果可能有差异。

先看看InnoDB中的情况:

CREATE TABLE enumerator (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
textvalue VARCHAR(30),
PRIMARY KEY (id)
) ENGINE=InnoDB;

然后执行SQL:

INSERT INTO enumerator
VALUES (0,'Zero'),(1,'One'),
(2,'Two'),(3,'Three');

报错:

#1062 - Duplicate entry '1' for key 'PRIMARY'

此时一行也没有被执行。

再次执行同样的SQL:

INSERT INTO enumerator
VALUES (0,'Zero'),(1,'One'),
(2,'Two'),(3,'Three');

结果成功了,表数据如下:

1 One
2 Two
3 Three
5 Zero

再看看MyISAM中的情况:

同样的表结构,只是类型变成了MyISAM,使用同样的SQL测试两次,每次都会报错:

#1062 - Duplicate entry '1' for key 'PRIMARY'

表数据如下:

1 Zero
2 Zero

总结:当表建立后,Autoincrement列的初始值是1,当我们第一次执行SQL,用0插入的时候,实际上就是被当1插入,而后面的数据又存在1, 所以重复报错,不过此时Autoincrement的当前值却已经被更新成了,只是InnoDB把它更新成了5,而MyISAM把它更新成了2,所以当我们执行第二次操作时产生了差异。从结果看,在更新Autoincrement的当前值时,InnoDB把多行插入SQL作为一个整体来看待,而MyISAM则把多行插入SQL中的每一行独立看待。

友情提示:这是引擎处理的差异,不算是Bug,但可能会造成一些非预期的结果,所以还是值得注意的。

补充内容:在InnoDB中,这个特性是可配置的,就是innodb_autoinc_lock_mode,缺省值是1,当值为1或者2时,会发生文中情况,否则不会。
 
2010年07月31日 星期六 下午 6:38
欢迎访问我的新主页:http://huoding.com/

作者:老王

PHP代码审计方面的软件越来越多了,PHPCheckstyle算是最近比较活跃的一个。通过SVN钩子脚本的方式来调用PHPCheckstyle,可以强制代码必须符合预先设定的编码标准(比如PEAR编码标准),有助于在多人合作项目中提高代码整体质量。

PHPCheckstyle的设置:

安装真的没什么可说的,属于接插即用型的,唯一需要设置的就是config目录下的配置文件:缺省使用的是default.cfg.xml,你可以编辑它,按照官方文档适当的增减规则。不过PHPCheckstyle项目诞生时间短,不够稳定,截至0.8版本为止还有不少问题,使用前最好逐条规则进行测试。

最简单的运行方法如下:

php run.php --src /path/to/file

这样的话会生成相关的html文档,如果你想直接输出的话,请使用:

php run.php --format console --src /path/to/file

更多选项可以自己看帮助(php run.php就可以查看相关帮助)

Subversion钩子脚本:

下面设置钩子脚本,具体点说是前置钩子,也就是:pre-commit,通过钩子检查后才被允许提交到版本库。只有添加或更新的文件是需要检查的,如果是要删除的文件,则没有必要检查;还有一个问题,PHPCheckstyle只能检查具体文件的内容,而在提交之前,我们想要检查的文件还不存在,所以我们得生成一个临时文件,检查完再删除,另外,在生成文件时要注意其唯一性,免得多用户一起提交时发生冲突,注意事项了解的差不多了,可以写钩子脚本了:

代码(at pastebin.com):

01 #!/bin/bash
02
03 REPOS="$1"
04 TXN="$2"
05
06 PHP="/usr/local/php/bin/php"
07 SVNLOOK="/usr/bin/svnlook"
08
09 RUNSCRIPT="/path/to/run/php/script"
10
11 CHANGED=`$SVNLOOK changed -t "$TXN" "$REPOS" | grep '^[U|A]' | awk '{print $2}'`
12
13 for FILE in $CHANGED; do
14     if [[ "$FILE" =~ \.php$ ]]; then
15         TEMPFILE=`mktemp`
16         $SVNLOOK cat -t "$TXN" "$REPOS" "$FILE" > $TEMPFILE
17         MESSAGE=`$PHP $RUNSCRIPT --format console --src $TEMPFILE | head -n -2`
18         if [ ! -z "$MESSAGE" ]; then
19             rm -rf $TEMPFILE
20             echo "$MESSAGE" | sed -e "s|$TEMPFILE|$FILE|" 1>&2
21             exit 1
22         fi
23         rm -rf $TEMPFILE
24     fi
25 done

关于Shell,如果有不清楚的可以自己搜索一下,网上有很多类似的文章

钩子脚本还可以做很多事情,比如核对PHP脚本语法(php -l),而且通过管道符不用生成临时文件:

MESSAGE=`$SVNLOOK cat -t "$TXN" "$REPOS" "$FILE" | $PHP -l`

运行后,不用判断MESSAGE是否为空,而是根据退出状态来判断脚本是否有语法问题:

if [ $? -ne 0 ]

PHPCheckstyle配置和使用多少还是有点别扭,有机会试试PHP_CodeSniffer配置钩子脚本更简单

BTW:发现一个PHP Commit Hooks项目,有点意思,可以看看。
 
2010年07月26日 星期一 下午 5:51
欢迎访问我的新主页:http://huoding.com/

作者:老王

分页程序一般由两条SQL组成:

SELECT COUNT(*) FROM ... WHERE ....
SELECT ... FROM ... WHERE LIMIT ...


如果使用SQL_CALC_FOUND_ROWS的话,一条SQL就可以了:

SELECT SQL_CALC_FOUND_ROWS ... FROM ... WHERE LIMIT ...

在得到数据后,通过FOUND_ROWS()可以得到不带LIMIT的结果数:

SELECT FOUND_ROWS()

看上去,似乎SQL_CALC_FOUND_ROWS应该快于COUNT(*),但实际情况并不是这样简单,请看:

To SQL_CALC_FOUND_ROWS or not to SQL_CALC_FOUND_ROWS?

用数据说话,证明了COUNT(*)相对SQL_CALC_FOUND_ROWS来说更快。

不过我觉得这个结论也不全面,某些情况下,SQL_CALC_FOUND_ROWS更有优势,看我的实验:

表结构如下:

CREATE TABLE IF NOT EXISTS `foo` (
`a` int(10) unsigned NOT NULL AUTO_INCREMENT,
`b` int(10) unsigned NOT NULL,
`c` varchar(100) NOT NULL,
PRIMARY KEY (`a`),
KEY `bar` (`b`,`a`)
) ENGINE=MyISAM;


导入一些测试数据:

for ($i = 0; $i <10000; $i++) {
mysql_query("INSERT INTO foo SET b=ROUND(RAND()*10), c=MD5({$i})");
}


先测试COUNT(*)方式:

$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
mysql_query("SELECT SQL_NO_CACHE COUNT(*) FROM foo WHERE b = 1");
mysql_query("SELECT SQL_NO_CACHE a FROM foo WHERE b = 1 LIMIT 100, 10");
}
$end = microtime(true);
echo $end - $start;


结果输出(数据大小视测试机性能而定):0.75777006149292

再测试SQL_CALC_FOUND_ROWS方式:

$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
mysql_query("SELECT SQL_NO_CACHE SQL_CALC_FOUND_ROWS a FROM foo WHERE b = 1 LIMIT 100, 10");
mysql_query("SELECT FOUND_ROWS()");
}
$end = microtime(true);
echo $end - $start;


结果输出(数据大小视测试机性能而定):0.6681969165802

有数据有真相,那为什么我的实验结论和MySQL Performance Blog的结论相悖呢?这是因为:

在MySQL Performance Blog的实验里,COUNT(*)查询是执行的的Covering Index,而SQL_CALC_FOUND_ROWS是执行的表查询;而在我的实验里,因为我定义了适当的索引,COUNT(*)和SQL_CALC_FOUND_ROWS都是执行的Covering Index,所以结论出现了差异。

既然使用了Covering Index,就意味着不能再使用SELECT *的形式了,只能使用类似SELECT id这样的形式了,用的列在索引里都能查到,如此说来,我们需要的实际数据从哪来呢?这个很简单,有了主键之后,实际数据可以通过Key/Value形式的缓存获得,这样的架构很常见。

结论:SQL_CALC_FOUND_ROWS如果执行的是Covering Index的话,是很快的!换个角度看,如果COUNT(*)和SQL_CALC_FOUND_ROWS都只能通过表查询来检索,那么分页时,SQL_CALC_FOUND_ROWS同样会快于COUNT(*),读者可自行测试。

补充:Fast paging in the real world
 
2010年07月21日 星期三 下午 11:29
欢迎访问我的新主页:http://huoding.com/

作者:老王

我在给select元素动态添加option的时候,发现原本在IE/Firefox下好好的效果,放到Chrome下排序就混乱了。

最开始我还以为是我在给select添加option方法的问题:

<select>.options.add(new Option('名字', '值'));
<select>.options[<select>.options.length] = new Option('名字', '值');


但是不管我使用哪个方法,问题都没有解决,后来感觉可能是我遍历数据时使用for/in的问题,于是测试:

代码(at pastebin.com):

01 <script>
02 var data = {
03     '4': 'first',
04     '3': 'second',
05     '2': 'third',
06     '1': 'fourth'
07 };
08
09 var result = '';
10
11 for(var i in data) {
12     result += data[i] + '<br />';
13 }
14
15 window.onload = function() {
16     document.body.innerHTML = result;
17 };
18 </script>


在Firefox下浏览,结果如下:

first
second
third
fourth

在Chrome下浏览,结果如下:

fourth
third
second
first

如果我们把对象属性从数字型换成字符串型会如何呢?

02 var data = {
03     '_4': 'first',
04     '_3': 'second',
05     '_2': 'third',
06     '_1': 'fourth'
07 };


如此一来顺序就正常了。

从结果来看,Chrome下用for/in遍历对象时,如果对象属性名是数字型的话,可能会出现乱序现象,而IE,Firefox则没有此类问题。至于原因,在遍历对象时,Chrome对数字型的属性处理有特殊性所致(官方认为这不是Bug),如果是字符串型的属性则没有问题。

注释:文章内容经网友提醒已做出适当修改。
 
   
 
 
文章存档
 
     
 
最新文章评论
  

回复rjgcs123:有redis nodejs的么?
 

Explain的type显示NULL是什么意思?
 

HTML有个属性叫做placeholder
 

www.yayou123.com
 

Just add Fraction.m in main.m instead to add Fraction.h in main.m. And this wil
   
帮助中心 | 空间客服 | 投诉中心 | 空间协议
©2012 Baidu