作者归档:alex

mysql版本升级导致的一些错误

在项目过程遇到一些因为数据库版本原因发生的一些错误

mysql服务器升级数据还原后表结构不能显示

mysql服务器升级后,数据库数据还原后,在使用navicat进行desc表结构是出现了

“1558 – Column count of mysql.proc is wrong. Expected 20, found 16. Created with MySQL 50018, now running 50146. Please use mysql_upgrade to fix this error.”这样的错误内容

我的mysql服务器是从5.1升级到了5.5版本,而原数据库表的备份与恢复是直接copy mysql data目录;数据都是完好的。搜索查明

解决方案就是:

mysql.proc
这个是5.1里面的系统表来的,用来记录存储过程或函数的信息.你的数据库肯定作过升级或用不同的版本进行备份迁移恢复.

使用命令:mysql_upgrade 就可以解决

mysql_upgrade -u root –datadir=/opt/mysql –basedir=/opt/mysql/data –password=123456

MySQL utility for upgrading databases to new MySQL versions.

5.6.10 mysqldump: Couldn’t execute ‘SELECT @@GTID_MODE’ 执行错误

mysqldump: Couldn’t execute ‘SELECT @@GTID_MODE’: Unknown system variable ‘GTID_MODE’ (1193)

不知道系统变量GTID_MODE,google搜索在mysql官方找到一片bug报告

mysqldump –set-gtid-purged=AUTO does not detect if mysqld has GTIDs

5.6.5 introduced GTIDs. mysqldump 5.6 was made
compatible  to use GTIDs
2. In versions < 5.6, where GTIDs are not
even defined, mysqldump 5.6 fails as it cannot
execute ‘SELECT @@GTID_MODE’
3.  using –set-gtid-purged=AUTO, should detect whether
the server has GTIDs enabled or not.  However, this
option works only for 5.6 versions.

WORKAROUND: if you are using mysqldump 5.6 to backup mysql-5.5  and old database
versions, use –set-gtid-purged=OFF option.

Fix:  THe fix is to check for the server version before executing ‘select @@gtid_mode’

5.6.5版本引入GTIDs;如果mysql version 小于5.6,GTIDs是没有定义的,mysqldump5.6不能执行’SELECT @@GTID_MODE’ ;应该使用–set-gtid-purged=AUTO用来检测GTIDs是否启用,但是这个选项只能在5.6版本mysql上工作

我要备份的远程服务器是

原先按正常的mysqldump语法如下则会报错

所以应该用mysqldump 5.6 对mysql server版本小于5.6版本或者是更老的版本进行数据备份时,会首先执行“SELECT @@GTID_MODE”这样的语句(尽管select @@gtid_mode 得到的属性值它是off)

则要有加上 –set-gtid-purged=OFF 属性了,导出成功

 

但是对于这个gtid_mode属性是什么意思,我不是很明白,查阅了一下与事物数据同步有关想了解的请看16.1.4.5. Global Transaction ID Options and Variables

“这是因为在启用GTID模式下,如果对非事务表更新时,无法保证事务一致性,因此设置 disable-gtid-unsafe-statements = 1 确保主从数据一致性。”[MySQL FAQ]系列 — 启用GTID & binlog新安装完的MySQL提示无法登录

 

扩展阅读

从mysql5.6导出的sql备份文件要还原到小于5.6版本或更老版本时可能会出现如下的问题

GTID_PURGED variable makes mysql dump non backward compatible

A dump produced from 5.6 server by 5.6 mysqldump is not supposed to be restorable on all or some old server and client versions,
Last version for which we had a compatibility option in mysqldump was 4.0 !!!
Hence, what you ask for would be some 5.5 compatibility version.
We have also asked our Documentation team to further document this limitation in clear terms.

mysqldump 5.6导出的sql备份文件不支持老版本的数据恢复

题为“缓存的应用与设计模式”的技术分享活动准备文档

在公司举行的技术分享活动上, 这周轮到我进行一个主题的讲解了;在上周六开始在定下了“缓存” 为主题的内容,主要是有感于之前缓存在项目中的应用。主要思路就是在用户从浏览器发送请求,然后得到服务器的响应内容这样一个流程中,有哪些地方能够实现缓存的最大化利用,提高用户的体验以及服务器的响应速度。文档的主要构成是一些从网上找到的具体配置信息,概念性的内容;还有自己程序上的一些应用技巧,加上自己的理解;也借鉴了一些其他大牛的博客内容、理念等。

文稿如下:

在web应用的一个请求中,用户从浏览器发送请求,服务器接收到请求后进行数据的收集,根据参数信息调用对应的service或model,model从DB中查询检索到数据信息返回给服务器,服务器将得到的数据内容进行处理封装,响应发送给用户的浏览器。那在这个请求的流程中,缓存在不更改代码或少改代码情况下可以很好的提高服务器的响应处理速度,比如在服务器的缓存,内存层面的高速缓存,浏览器的本地缓存,程序应用上的缓存等。而有关php脚本优化以及源码编译后的opcode动态缓存的加速器如zend optimizer,eAccelerator等不在这次讨论范畴内。

服务器级缓存

Apache

mod_cache  mod_disk_cache  mod_mem_cache  mod_file_cache module的缓存实现

Apache 的缓存方式有两种,一种是基于硬盘文件的缓存,由mod_disk_cache实现,另一种是使用内存缓存,由mod_mem_cache实现,不过它们都是依赖mod_cache模块的,mod_cache模块提供了一些缓存配置的指令供它们使用,而mod_file_cache模块是搭配mod_mem_cache模块使用的

Mod_disk_cache以磁盘文件形式进行缓存

<IfModulecache_module>

CacheDefaultExpire  3600

CacheMaxExpire  86400

CacheLastModifiedFactor  0.1

<IfModuledisk_cache_module>

CacheEnable disk /

CacheRoot”D:\Program Files\Wamp\Apache2.2\cache”

CacheDirLevels 5

CacheDirLength 5

CacheMaxFileSize 1048576

CacheMinFileSize 10

</IfModule>

</IfModule>

使用缓存需要注意如下事项:

要使用缓存,必须使用指令CacheEnable启用它,目前可用的缓存类型为 disk 或mem,禁止缓存可以使用CacheDisable,如CacheDisable /private

待缓存的 URL 返回的状态值必须为: 200、203、300、301 或 410

URL 的请求方式必须是 GET 方式

发送请求时,头部中包含“Authorization: ”的字符串时,返回的内容将不会被缓存

URL 包含查询字符串,如问号?后的那些东西,除非返回的内容包含“Expires:”,否则不会被缓存

如果返回的状态值是 200,则返回的头部信息必须包含以下的一种才会被缓存:Etag、Last-Modified、Expires,除非设置了指令CacheIgnoreNoLastMod On

如果返回内容的头部信息“Cache-Control:”中包含“private”,除非设置了指令CacheStorePrivate On,否则不会被缓存

如果返回内容的头部信息“Cache-Control:”中包含“no-sotre”,除非设置了指令CacheStoreNoStore On,否则不会被缓存

如果返回内容的头部信息“Vary:”中包含了“*”,不会被缓存

配置完成后,重启apache后即可,测试一个脚本

<?php

Header(‘Last-Modified: ’. gmdate(‘D, d M Y H:i:s’, time()) . ’ GMT’);

Var_dump($_SERVER);

?>

在服务器指定的cachedir目录下面可以看到后缀为.data 与.header的文件;.data是当前请求页面的所有文档内容,而.header则是当前请求头信息。CacheDefaultExpire可以设定这些文件的过期时间。

Mod_file_cache缓存模块配合mod_mem_cache模块来使用

如果你的网站有几个文件的访问非常频繁而又不经常变动,则可以在 Apache 启动的时候就把它们的内容缓存到内存中(当然要启用内存缓存系统),使用的是mod_file_cache模块,具体如下:

有多个文件可以用空格格开

MMapFile  var/wwwml/index.html var/wwwml/articles/index.html

上面是缓存文件的内容到内存中,除此之外,还可以只缓存文件的打开句柄到内存中,当有请求进来的时候,Apache 直接从内存中获取文件的句柄,返回内容,和MMapFile指令很像,具体如下:

CacheFile var/wwwml/index.html var/wwwml/articles/index.html

上面两个指令所缓存的文件如果有修改的话,必须重启 Apache 或使用 graceful 之类的方式强制使 Apache 更新缓存数据,否则当用户访问的时候获取的不是最新的数据。

有时候需要根据某些特殊的头部信息来决定是否进行缓存,则可以使用如下指令:

当头部信息中包含 Set-Cookie 时则跳过不进行缓存操作

CacheIgnoreHeaders Set-Cookie

有时候需要缓存的时候跳过 URL 中的查询字符串?使用如下指令:

CacheIgnoreQueryString On

Apache 还有一个模块mod_expires,这个模块控制header中的cache-control参数的max-age指令

ExpiresActive On

#ExpiresDefault A600

ExpiresByType image/x-icon A2592000

ExpiresByType application/javascript A2592000

ExpiresByType text/css A604800

ExpiresByType image/gif A2592000

ExpiresByType image/png A2592000

ExpiresByType image/jpeg A2592000

ExpiresByType text/plain A86400

ExpiresByType application/x-shockwave-flash A2592000

ExpiresByType video/x-flv A2592000

ExpiresByType application/pdf A2592000

#ExpiresByType text/html A600

可以看到js文件

Cache-Control     max-age=2592000

Connection Keep-Alive

Date  Thu, 21 Mar 2013 07:21:31 GMT

Expires       Sat, 20 Apr 2013 07:21:31 GMT

Keep-Alive timeout=5, max=100

Server        Apache/2.2.19 (Win32) PHP/5.3.6

Vary  Accept-Encoding

X-Pad         avoid browser bug

自动加上了Expires过期时间了

扩展:

压缩模块

除了缓存服务器轻松配置一下mod_deflate gzip就能返回压缩后的数据,压缩率可以达到60%-75%之间。

扩展:

Nginx也有对应的fastcgi-cache /HttpHeadersModule /gzipmodule

内存级缓存

Memcache

Memcache是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。简单的说就是将数据调用到内存中,然后从内存中读取,从而大大提高读取速度。

Memcache现在的版本是v1.4.15,使用memcache的网站一般流量较大;为了缓解数据库的压力,让memcache作为前端数据的一个缓存区域,将部分信息保存到内存中,让内存作为硬盘的高速缓存。因为单台服务器的memcache缓存容量是有限的,不过memcache是支持分布式的,所以我们可以根据实际情况进行数据的分布存储,比如社交类的网站以user为主,可以考虑把userid 为1开头的存放到编号为1的memcache服务器上,userid为2的放到编号为2的memcache服务器上。只是这样的话在程序业务上面就需要对userid进行判断了。

Memcache服务器端是直接通过客户端连接后操作的,没有进行任何的验证过程;在平时开发环境中可以这样,但是生产环境中就不能这样了。Memcache不能直接暴露在互联网上,否则很容易被人查看,或者服务器被入侵。所以我们可以以

1、   内网访问memcache服务器,服务器一般有两个网卡,一内一外;我们就让web服务器通过机房内网来访memcache。 Memcache的服务器上启动的时候就监听内网的IP地址和端口,内网间的访问能够有效阻止其他非法的访问。

# memcached -d -m 1024 -u root -l 192.168.0.200 -p 11211 -c 1024 -P /tmp/memcached.pid

Memcache服务器端设置监听通过内网的192.168.0.200的ip的11211端口,占用1024MB内存,并且允许最大1024个并发连接

2、   设置防火墙也是简单有效的方式,如果memcache需要通过外网ip来进行访问,那么我们可以设置服务器的iptables

# iptables -F

# iptables -P INPUT DROP

# iptables -A INPUT -p tcp -s 192.168.0.2 –dport 11211 -j ACCEPT

# iptables -A INPUT -p udp -s 192.168.0.2 –dport 11211 -j ACCEPT

这里的意思就是指定只允许来自192.168.0.2的服务器进行访问。

Redis

Redis是一种面向“键/值”对类型数据的分布式NoSQL数据库系统,特点是高性能,持久存储,适应高并发的应用场景。目前最新版本2.6.11

Redis是一个NOSQL数据库系统,支持各种数据存储类型,普通字符串,list,set,order sets;

那么redis与memcache都是内存级别的数据缓存,他们之间有什么区别呢?应该怎么选择呢。

1、memcache是一个高性能分布式的缓存系统,而redis是高性能分布式NOSQL数据库系统。

2、memcache数据常驻内存,memcached挂掉后数据消失;而redis数据内容也是常驻内存,且还可以定期的保存到磁盘中

3、memcache数据不可恢复,而redis数据丢失后可以通过aof恢复

4、redis有着更丰富的数据结构选择,在redis有着更丰富的操作;在memcache中只能拿到客户端中进行类似的操作在set进去,加大网络的IO次数

等等

Memcache与redis各有各的优势,memcache内存利用率高,数据存储维持简单搞笑的键值对模式,可以单纯的作为一个cache层,对于前后台的数据传输有着一个中转站的作用,而且有着成熟的分布式方案,同事各个语言都有着优秀的客户端。Redis是面向对象的键值对高性能分布式的数据库系统,适用于高并发,持久存储的场景,有着完善的数据存储类型且数据操作都是支持incr原子操作的,redis客户端也覆盖了各种语言,能很方便的使用。

对于数据同步以及持久化有很高要求的可以使用redis,对于仅仅是作为一个数据缓存层且不想维持复杂的数据类型结构那么可以选择memcache。

浏览器本地缓存

HTTP网页缓存

Cache-Control     max-age=2592000

Connection Keep-Alive

Date  Mon, 25 Mar 2013 07:31:50 GMT

Expires       Wed, 24 Apr 2013 07:31:50 GMT

一般一个http响应的header信息,包括如Etag  Last-modified Cache-control Expires 等这些信息,那么浏览器根据这些信息一并与网页内容进行本地缓存。

If-None-Match   “427fe7b6442f2096dff4f921548565486”

If-Modified-Since   Fri, 27 Jul 2012 01:35:39 GMT

浏览器再本地的缓存中发现last-modified信息,那么就以If-Modified-Since加入到header信息中发送至服务器,询问下服务器上面最后的一次访问更新是什么时候呢,那么apache服务器就会核对一下这个文件的最后更新时间,如果大于这个时间那么就会返回一个200的响应,重新下载这个文件。如果文件还是没有更新那么服务器就返回一个304 Not Modified,浏览器还是继续用本地缓存。

http的缓存,不仅可以节省服务器处理资源;还减少了网页的下载次数,节约网络带宽。但有时候我们经常是动态编程,比较少会整体缓存整个网页,不过对于网页所需要的附属内容如css、js、图片等内容,网站结构所需的图片js等内容我们可以缓存时间长点如30day,而用户图片数据内容可以进行5day的有效缓存。

如google这样的智能搜索引擎可以识别资源的状态信息,使用http cache之后,只有在文本内容真正变化后才会被爬取,那么我们可以直接对爬虫返回304 not modified,这样不但降低服务器负载与爬虫造成的网络带宽消耗,实际上也提高了google爬虫的效率。

历史帖子,一段时间后就很少人访问了;但是爬虫还是会每天记录访问造成服务器的高消耗,使用http cache后,爬虫爬过一次后,以后无论多少次爬取都可直接返回304了。

HTML5 DOM Storage本地存储API

在web storage api出现之前,服务器端需要存储客户端与浏览器之间交换的数据信息,如ajax请求的数据信息。而现在浏览器可以将需要重复请求的数据内容存储到web storage上,同时在关闭浏览器之后还可以恢复数据。

那同样是进行浏览器数据存储的cookie与web storage有什么区别?我们知道cookie是服务器端与浏览器进行文本传输的内置机制,我们可以用cookie来存储服务器生成当前连接的会话标识,可以用来实现购物网站的购物车,可以用来存储数据供接下来的页面使用。Cookie对于web开发程序确实有很大的用处,但是cookie也是有一些缺点的

1、   Cookie大小受限制,4KB数据

2、   只要有请求浏览器就会自动将cookie进行传输,cookie数据在网络上是可见的,存在安全风险;同时无论加载哪个url,cookie的数据都会消耗网络带宽。

而web storage目前可以存储高达5MB的数据内容,主流浏览器都支持。Web storage 有两种主要实现sessionstorage与localstorage,sessionstorage主要是用于会话的数据存储,关闭浏览器之后自动失效;而localstorage是永久存储在浏览器中的。

通过调用对象的setItem()与getItem()方法进行设置与获取的实现

window.sessionStorage.setItem(‘k’, ‘v’);

window.sessionStorage.getItem(‘k’);

window.sessionStorage.removeItem(‘k’);

也可以通过对象属性方式进行设置获取

window.sessionStorage.firstKey = ‘val’;

alert(window.sessionStorage.firstKey);

那么这样与普通的javascript数据对象有什么区别呢。最大的一个区别就是在于“作用域”普通的js对象在当前页面刷新后丢失已经不奇怪了,但是对于sessionstorage只要不关闭标签或浏览器,一个页面设置的sessionstorage值,只要是同源的网页中,其他页面可以依据Key获取到值。但是因为sessionstorage不能持久保存所以对于不是很重要的数据,但是短时间内存在的流程(比如对话框、向导),多个页面需要该数据的情况下可以存储在其中,而在以前这些数据需要用form或cookie进行来回传递的。

Localstorage代码使用上没有区别,但是要注意的是localstorage是持久化存储,除非用户删除是永远不会删除的。

扩展

IndexedDB

大容量索引型的数据结构存储可以看看IndexedDB,以K/V键值对形式进行数据存储。

程序应用层缓存

在应用开发过程中,需要了解缓存可以有效的提高程序的响应能力与处理能力。

那哪些数据写入缓存?

大量非实时更新的数据内容,如文章,站点信息,tag分类,rss list信息

Mpf魔炫网上面一个专题里面有着视频信息、专题信息、相关文章信息、微博信息等内容。

在项目上线运营之初没有考虑到具体的情况,后来分析发现在视频详细页与专题页是有着最高的访问量的,最高是达到了1万人同时在线访问,那段时间导致后台压力巨大,用户打开页面缓慢;之后做出调整,在视频、专题信息填写完成之后很少进行改动,所以我们决定把一些如视频信息,专题信息,相关文章信息等内容加入到缓存区,用户在进行访问是可以说服务器几乎不用查询mysql了。能够很大的提高用户的访问量,服务器数据能快速的返回。

ORM对象,大量的sql的查询主要作用于服务器磁盘的IO,在一定程度上mysql的反应速度体现在磁盘IO的速度上。那么在实现ORM对象缓存是,需要考虑的是

我们是以减少服务器磁盘IO为目的,不是为了减少发送到数据库的sql条数;使用ORM会增减sql语句条数。

既然决定采用ORM模式作为主要的数据读写操作,那么在数据库表结构的设计上尽量越细越好,经常查询访问的大数据量单独列表。

同时在构建sql语句是尽量避免多表关联查询,拆成多表的主键查询。(某个location附近的event)

保存缓存的数据同步

加入缓存后,就存在这样的问题;要保存缓存与数据库数据的一致性!否则数据不一致会加大程序排除问题的难度。那么数据一致性通常是在insert,update,delete时。可以以回调的方式实现缓存的同步。

Insert数据到数据库时,在insert成功后,将得到的最新last_insertid作为key,当前的数据内容作为value存入到缓存。数据在被调用时,直接就是获取缓存了。

Update 对于缓存的数据更新可以有两种方式。

1、   主动更新

2、   被动更新

主动更新就是update记录成功后,直接回调update缓存机制。

$cache->Delete($id);

$cache->set($id, $data);

先删除,后写入保持数据的最新。

被动更新则是,在用户访问这个页面时程序调用发现没有缓存,第一次去数据库读取内容返回,且写入缓存;用户第二次访问这个页面时则返回的是缓存数据内容了。

那么我个人推荐的是第一种,即在更新数据时直接同时回调将缓存数据内容也更新,这样避免在用户第一次访问时可能会得到一个较慢的响应。

案例:

某个网站的文章缓存是以文章id+文章的updatetime来作为key进行数据缓存的,在文章更新时updatetime改变,用户访问程序进行缓存查询时没有找到,进而查询数据库返回并且写入到cache中,注意这时原先的缓存并没有删除且也可能没有过期,那么就是一直存在其中缓存系统之中,直到缓存系统自动的垃圾回收。

1、   这样需要用户访问,被动式更新

2、   加重缓存系统的压力,需要管理更多的实际上对程序来说已经过期了的数据内容。

缓存的过期数据处理

缓存的过期数据处理的实现主要是两个方面的

1、数据库记录在删除时,同时回调删除缓存中的数据内容。

2、缓存对象系统的自动清除

建议是在数据库记录delete成功后回调清除cache中的记录信息,保持缓存系统中数据的清洁度。

Kohana中的缓存实现

Kohana内核框架只有一个简单的file cache方式,core.php里面的kohana:cache($key, $data, $lifetime = NULL)方法。实现方式就是当前时间与文件内容修改时间比对。

If($data === NULL){

If( (time() – fimemtime( $file)) < $lifetime ){

Return unserialize(file_get_content($file));

}else{

// delete expires cache file

}

Return NULL;

}

file_put_contents($file, serialize($data)); //写入

在项目复杂的业务逻辑中,这样的缓存机制肯定是不够用的了。所以kohana采用cache module用来专门的做缓存实现。且缓存实现在框架内核中多次用到,所以内核框架是有几个必须依赖的module的,cache就是其中之一。

今cache module实现的缓存驱动有apc,file,memcache,sqlite,wincache这几种,而我们常用的就是file,memcache。

Kohana中的缓存应用主要体现在

1、   类实例对象的缓存

2、   配置信息的缓存应用

分享总结

昨天讲解完毕之后,反响不咋地;你自己理解不一定能完整清晰的表达出来,有些问题模糊不清,自己也没弄明白,确实没有讲好;整体内容很全面,但是没有具体到哪一点,好像什么都概括了,但是没有侧重某一点深入讲开。

而且演讲的技巧要加强啊,整个过程就是自己个人概述没有一个活跃的气氛,让人昏昏欲睡。

总结下来就是几点

1、  整体内容比较多,较平。介绍用户一个请求过程中哪些地方可加入缓存机制,没有侧重某一点。

2、  内容概念多,实质涉及代码较少;需要把概念与代码结合起来。

3、  有些地方的问题较模糊,讲述不够清楚。

4、  事前对于提出需求的人想了解的内容,没有深入进行讲解。事先沟通没有做好。

5、  缓存应用的程序深度应用需要加强,内容较为浅显;案例分析代码讲解较少。

MongoDB实现mysql主键autoincrement

MongoDB新建表默认有一个_id字段来作为autoincrement自增实现,而这个_id字段类型是objectid类型(objectid 是12字节的BSON类型)。ObjectId的详细解释。

而现在因为数据库迁移,将项目中原mysql的一些locations表移植到mongodb上面。对于locations 使用mongodb 2d loc 索引能够更快的进行数据检索,同时避免原mysql需要经过复杂的经纬度查询。

那么数据迁移后主要的要求有

1、数据库表的迁移不会影响表与表之间的关系,继续保持表之间的主外键关系。

2、lat 与 lng 字段在mongodb 新表中合并为一个字段loc,同时对loc加以2d索引。

对于第二点主要影响是在代码之中,可以看看这里。就不做详细描述了。

那么现在主要要做的事情就是,维持表与表的关系与主外键关系。我们以event 与 对应的 event_locations为例

event中有一个外键location_id,location_id就是event_locations的主键id;而现在mongodb中新建event_locations表,同时要保持event_locations与event的主外键引用关系。

因为mongodb表中默认有着_id这样一个主键,那现在我们的数据迁移以及mongodb event_locations表结构构建有两种选择

1、event_locations 主键以_id,即将原mysql event_locations 的id 替换为 _id。也就是如下表结构


{ "_id" : ObjectId("51243bdb383ca83413000000"),  "topic_id" : 103874, "name" : "xixi aaahah", "address" : "xxxbbbb cccccc", "loc" : [ 30.11, 112.02 ], "created" : 1361329114, "updated" : 0 }

优缺点分析

优点就是 使用默认的_id主键值,速度快检索方便。_id自增不必维护,有mongodb自行完成。

缺点很明显 event中的location_id外键,原来类型是int,现在改用_id objectid类型作为主键id,location_id类型必须要更改为char(32)了。且程序代码还需要一定的更改。

最大的缺点就是目前项目已经上线,直接更改数据库表可能会带来不可预知的问题;对于运营维护可能带来很大压力。且对于目前event已有的location_id数据需要进行同步更新。

2、保持原来的event_locations表结构,使用id继续维持与event location_id的主外键关系。


{ "_id" : ObjectId("51243bdb383ca83413000000"), "id" : 2354, "topic_id" : 103874, "name" : "xixi aaahah", "address" : "xxxbbbb cccccc", "loc" : [ 30.11, 112.02 ], "created" : 1361329114, "updated" : 0 }

优缺点分析

优点 不必去更改event location_id的数据类型,且location_id的数据不必修改;只用将event_locations数据完整导入到新表中即可。

缺点 要维持id的自增属性,而mongodb默认是_id才有自增属性的,所以我们要用某种方法实现id的自增实现。_id会占用存储空间(默认的_id原本可以删除的,但考虑到以后可能会用到就保存了)。

我现在采用的是第2种方式,主要原因如下

1、项目已上线,且已经有了用户数据;对于直接更改数据表与操作数据会带来很大的风险。强烈不推荐。

2、event_locations数据几乎不用同步,只需要单向导入到mongodb 新表就好。

3、程序代码变动最小,目前程序代码更改就是新的loc(lng+lat)存储与id的自增实现。而第一种除了这些,还需要把所有的id替换为_id,数据类型也要从int变为string。

在确定了解决方法之后那么现在主要的难题就是怎么实现id的自增?

目前也有两种方法

1、传统实现,每次在event_locations insert之前,全表id desc排序得到最大的id值,对id值+1既得到了最新的id值从而实现了自增。

2、使用mongodb的findAndModify()方法实现,独立一个ids表用来记录所有可能需要id自增的值。

同样简要说明下这两种实现id自增的方法优劣。

第一种方法

优点 不用构建ids表(工具表),免于维护

缺点 每次都要进行全表索引排序且查找到最大id那条记录,在进行+1

第二种

优点 findandmodify() 原子操作(了解原子操作请查看这里),查找并修改,直接返回当前操作的这个文档内容。

缺点 要构建维护ids表,即项目中所有表可能需要自增的表都有一条记录存在于其中。

无论采用哪种方法都要修改程序代码,即id autoincrement部分需要程序实现。我采用的是第二种方法。

主要考虑还是性能问题,避免每次insert之前都要对event_locations进行全部的索引排序。而维护ids这样的表会很简单且性能上忽略不计,详细表结构如下


{ "_id" : ObjectId("5147ca1a08c2c1bc12abdb4c"), "id" : 2356, "tablename" : "topic" }

// _id 默认主键,mongodb自增

// id  表中自增主键值

// tablename 表名

获取下一个id的核心代码,继承了mongodb的ids Model。其中一个方法专门来做这个事情。


/**
* 构建ids用于存储所有表中需要自增的id值,结合findandmodify()来返回对应表最新的id
* @param string $tablename 表名
* @return int
*/
public function get_next_id($tablename = 'topic'){

// db.ids.findAndModify({update:{$inc: {id:1}},query:{tablename:'topic'},new:true});
$data = array(
'findAndModify' => 'ids',
'update' => array( '$inc' => array( 'id' => 1)),
'query' => array('tablename' => $tablename),
'new' => true
);
$idrecord = $this->db()->command( $data );
$newid = $idrecord['value'];

return $newid['id'];
}

// 等同于db.ids.findAndModify({query: {tablename: 'topic'}, update: {$inc: {id: 1}}});

这样在获取到最新的id值后,insert event_locations 时需要将id加入到sql语句中(原先id是autoincrement,id字段没有在sql中)。之后也就是同样的业务流程了。

扩展:

有时候可能会遇到一些奇怪的需求,比如一张表里面有两个自增id要实现。。。如此,上面的表结构就不能实现了。

<pre>// _id 默认主键,mongodb自增
// fieldname  表中自增字段名
// fieldval 表中自增id值
// tablename 表名</pre>

如此就行了哦

有时候也会碰到一些奇怪的需求,比如要从指定的值如200开始id自增。

那么其实用这样一句就可以了

db.ids.findAndModify({query: {tablename: 'topic'}, update: {id: 200,tablename: 'topic'}});
代码实现上就是注意要update时要把tablename也要传递进去了,因为update会更新所以的记录,如果不写上tablename那么返回的文档就只有一个id字段了。。。有需要的自己扩展下吧

需要注意的是event_locations id现在不是主键也没有任何索引,除非你加上了;所以这里还是强烈推荐加上索引

db.event_locations.ensureIndex({id: 1});

参考

在MongoDB中模拟Auto Increment

Create an Auto-Incrementing Sequence Field

findandmodify命令

“笔记”项目缓存应用

有感于Web应用的缓存设计模式这篇文章里面的缓存使用设计。

其实缓存方面确实可以大做文章,看看这位大牛的理念

  • 以减少数据库服务器磁盘IO为最终目的,而不是减少发送到数据库的SQL条数。实际上使用ORM,会显著增加SQL条数,有时候会成倍增加SQL。
  • 数据库schema设计的取向是尽量设计 细颗粒度 的表,表和表之间用外键关联,颗粒度越细,缓存对象的单位越小,缓存的应用场景越广泛
  • 尽量避免多表关联查询,尽量拆成多个表单独的主键查询,尽量多制造 n + 1 条查询,不要害怕“臭名昭著”的 n + 1 问题,实际上 n + 1 才能有效利用ORM缓存

拆分n+1条查询的方式,看起来似乎非常违反大家的直觉,但实际上这是真理,我实践经验证明:数据库服务器的瓶颈往往是磁盘IO,而不是SQL并发数量。因此 拆分n+1条查询本质上是以增加n条SQL语句为代价,简化复杂SQL,换取数据库服务器磁盘IO的降低 当然这样做以后,对于ORM来说,有额外的好处,就是可以高效的使用缓存了。

这里我看到了这样的一个新概念,就是“数据库的瓶颈往往是磁盘IO,而不是SQL的并发数量”;这个说法我也不知道是否正确的,没有经历过百万千万数据级别的研究与维护;不过可以学习一下。

确实如同该博主所说,对于一个没有太多访问量的网站有些过度设计,不过如果用户或数据量突然飙升情况下可以以较少的代码更改,实现更高的承载力;当然了,在网站最初设计时,这些因素也应当考虑进去;之前我做的mpf就是没有考虑这些问题,导致高峰时期(1w+ 并发)都访问一个专题页面内容,而次次几乎都会读取数据库服务器,差点导致宕机,后来对于专题内呈现的非动态数据内容都进行了memcached缓存,这样应该好多了。

从数据读取已经ORM有效性来说,最小化schema表设计确实很有效果

1、主键查询mysql表会buffer数据缓存。

2、ORM关系很透明明了

3、ORM model对象可以直接进行数据缓存(目前公司有一个项目就是如此)

如一个微博系统,微博一张表,微博附属信息一张表,微博插入的音乐,图像,视频,评论登录都是独立一张表,全部用id与微博主表关联起来。

按照column拆表实现细粒度对象缓存

数据库的瓶颈往往在磁盘IO上,所以应该尽量避免对大表的扫描。传统的拆表是按照row去拆分,保持表的体积不会过大,但是缺点是造成应用代码复杂度很高;使用ORM缓存的办法,则是按照column进行拆表,原则一般是:

  • 将大字段拆分出来,放在一个单独的表里面,表只有主键和大字段,外键放在主表当中
  • 将不参与where条件和统计查询的字段拆分出来,放在独立的表中,外键放在主表当中

按照column拆表本质上是一个去关系化的过程。主表只保留参与关系运算的字段,将非关系型的字段剥离到关联表当中,关联表仅允许主键查询,以Key-Value DB的方式来访问。因此这种缓存设计模式本质上是一种SQLDB和NoSQLDB的混合架构设计

数据库设计分表这个应该很常见了,常用字段一个表,非常用字段可以另一个表;同时大数据字段单独一表。

数据一致性问题

使用缓存,必须要考虑数据一致性问题。写入保持同步即可,在读取时需要首先检索cache里面有没有这样的数据,有着读取,无则读取数据并写入缓存;

主要麻烦的地方就是在于update与delete,在原数据信息更新时,缓存数据也必须更新了,否则数据会不一致。不论是内存缓存还是文件脚本缓存或是html页面缓存都是这样的。区别在于是程序主动更新还是用户访问的被动更新。

有些开源cms 采用的就是用户被动访问更新,有人访问;采取cache中去读取更新。不过我更喜欢程序主动更新。如上文博主采用的以文章update更新时间+id作为key来存储缓存信息,在文章原数据update之后,update更新时间一定会被修改,那么读取时cache中就没有了这样的文章内容了,但是原来的文章数据缓存还是存在的,只能等待cache服务器进行自动抛弃更新。这样的话cache中就可能会多很多冗余数据了,对于cache服务器检索查询一定有影响吧。

我的选择就是在文章数据更新时直接update cache或者是不update cache 但是一定把原文章 cache数据清除掉。

在用户删除文章时,先对cache里面的数据进行清除,然后进行数据库的原数据删除。

今天抽空把daynote.sinaapp.com 中的markdown转换后的数据那一层进行缓存,缓存时间是7200s,对于此类非公开的私密日记。用户访问查看后一般都会选择离开,不会时时刻刻进行刷新数据请求,7200s数据缓存搓搓有余了。

原先的xhzd.sinaapp.com也是使用大量的memcache缓存,其中列表都使用了;但是考虑到sae上面的memcache额度,字详细那一块没有缓存。目前几乎没什么访问量,也就没有进行缓存操作了。如果访问量大,那么可以把几乎整个字典都存入了缓存了。

SAE上面使用memcache的方法请看这里

混沌

年复一年,唯一在变的就是时间了吧。

几年都是这样,永远对当前不满意;自我感觉总是憋屈,憋屈不谈,年纪长了却又要受婚姻催促之苦。真是麻烦啊。

 

对于相亲这种事情,在双方父母首先见面相谈甚欢之后总是以先入为主的思想;“恩,这样的亲家很好相处,你们要是搞成了会很好”,而我在见过一面之后就已经确定明白以及肯定的知道这事是不可能的,对于这样的女生,第一次我对你多好或多差,那么后几次也就是这样了,关系不会因为时间的改变而进一步,但是肯定会因为时间流逝而慢慢疏远到最后就是从此只是路人,我从来不相信男女之间有那种可以长远的友谊。说实话我是外貌协会,对于第一眼不喜欢的,之后怎样我也无法对目前的关系更进一步了。虽然我已经对妈这样说明以及强调了,但是她还是不停的说着“如果你们将来怎样了,xxxx”,“我看她妈的性格之后呢,就觉得这姑娘肯定跟我搞的来”,云云。说实话我很反感,只能继续强调了。但是另一方面却又碍于对方的情面以及父母的压力不得不去赴那种我根本不想搭理的约,所以果不其然既无聊又累人。

 

于是在各种不配合之后,老妈放狠话了,“你这也不喜欢,那也不喜欢你喜欢哪样的呢?”是啊,我喜欢哪样的,第一就是看上眼,不要求很漂亮,但是希望160cm要有吧,喜欢较瘦纤细一点的,如果是黑长直就更好了。第二性格善良一定要善良,一个不善良的女人不可能有贤惠,温柔,通情达理的气质。第三你一定是我喜欢的爱的,当然我也希望我也是你喜欢的爱的,虽然妈总是跟我说宁愿找一个女生喜欢你的,这样那个女生就会对你言听计从,至少会万事都向着你,但是对于这样的问题我的回答是“这样两个人根本是不平等的,双方没有在一个高度,一定会吵架争执”,也许妈是根据她与爸的这几十年婚姻得出的结论,尽管他们也经常吵架,但是我觉得他们之间还是很爱对方的,不过我还是坚持我的看法。在坚持我的看法之后,老妈再次放狠话“在不管你这些事了,看你怎么办等你象三舅那样(ps:三舅36才结婚)”对此我也是很是担心,三舅年轻时不乏最求者与被最求者,但是因为各种原因(主因还是三舅瞧对方不上)没有结成婚,以至于错过了,而现在三舅娶的三舅娘比他小十多岁,现在天天在家里吵闹。于是老妈总是依次为例来告诫我,我确实担心,但是担心的不是这个;我担心的是什么样的年纪做什么样的事情,在学生时没有打架,打篮球,初恋好好的做一个书呆子,却也没有得到对应的一个成绩就这样无为的度过了学生时代,在我的理解就是学也没学好,玩也没玩好;而现在确实到了婚嫁的年龄了,同学朋友同事都开始迈入婚姻了,我却还是独行一人。你只是参加他人婚礼,你的婚礼呢。这种境遇我确实不想遭遇,拿爸妈的一句话来说“你还不快点结婚,这几年人情都送了多少出去了;还有你送出去的,到时候结婚晚了,关系淡了别人哪还来啊”。这个确实是我在意的,我为什么老是落后于人呢?!奈何是个性使然么,不自信不果断。还记得初中时起外号对同学我自称“独来独往独大侠”,那时呈一时之快就脱口而出了,其实我一次都没用过也不喜欢这个外号。

 

其实我也理解父母的这种感受,他们不容易,而且现在也快50了;他们常言“你快点结婚,我帮你带几年孩子,你们也轻松些,也趁我们还带的动,过几年孩子大了就好了”,对此之前我总以为他们这是自私,这是为了早点完成自己的任务;但是后来我看到了知乎上的一个问答,中国的父母为什么有催孩子结婚的传统?

被赞同最多的答案里面确实很有道理

“所以我觉得除了情感因素,父母催婚还隐含着对中国国情的经济学考虑。目前中国家庭生活的国情,首先是中国一向有赡养父母的传统,不单单是经济上,还有身心 上的照顾,老人的身体就像是老花瓶,不断地破碎和修补,每一次修补后都变得更加脆弱。其次,中国的社保养老体系离完善还很远,前景具有很大不确定性。最 后,中国长期的独生子女政策将使未来几十年出现严重的人口结构老龄化问题,年轻人将承担非常大的负担。而早点生子在已定程度上可以缓解这项压力,或者说将 中年时养儿和养老的压力分散开,把养儿的压力提前些交给年轻力壮时来承担,且爷爷奶奶也可以帮忙分担一部分这方面的压力,而到了爷爷奶奶年老时,孙儿已经 长大成人,孙儿又可以分担一些赡养老人的压力。”

确实这些事情是我之前没有想到的,我在父母的羽翼下待的太久了。很多事情都是父母出面完成,而我只是坐享其成的。所以目前还是自己做好自己,然后加油吧。

还有就是“你看人家比你小的xx娃都已经会走了”言下之意我是听得出来的,父母也想早点抱孙子以不用羡他人,这点不论是在城市或者是在农村只要是有中国人的地方都会有着这种比较。前几天邻居家的小孩的婴儿车暂时放在我家里,一个已有孙子的太婆跑进来突然说了一句“抱孙子了啊”,妈没有搭理,我也没有搭理,那SB太婆无趣走了;明知故问,话里有话我也很不爽!我还是比较传统的对此我只能说QNMGEB,然后心里默默的喊加油。

 

但是如此如此,有时候又故意放纵自己;不去找女朋友,自己躲着撸。确实很无耻啊。混沌度日,感觉每天都是如此一成不变,对于路上遇见女生又不敢上前搭讪,总是希望有一天有一个美女突然从天而降砸到你头上,然后从此过上了快乐幸福的日子。不过这种概率应该比陨石解体砸到你头上的概率还要低吧。且人家花枝招展前凸后翘的美女岂会看上你这样无车无房无存款的屌丝呢?但是灰心几天后又开始打鸡血各种联系各种聊,对未来媳妇的出现充满信心,坚信桃花不是今日就是明日会开在你头上的,但是没过几天又开始心灰意冷放纵自己,过一天算一天那种心态,就是这样轮回过完了一年。然后等到年尾再次被各种亲属催促,各种朋友鄙视。

 

今年却真的不想这样了。对于不喜欢的早点完事直接表达不喜欢,不要让自己受累;伤人伤己不好。对于确定目标喜欢的,执着追求奋不顾身;安静时一个人好好的做好自己,喧嚣时冷静头脑遵从自己的内心选择。希望上表的菩萨保佑,麻烦您了。

 

 

 

mongodb初学习

目前学习使用mongodb,主要是一个项目中使用到了地理位置服务相关的内容。比如发布一个活动,客户端自动将当前位置获取到一同存储到后端;可以利用数据库中的地理位置信息实现如“附近的活动,附近的人;在地图上展现附近的活动、人”等功能。

原先的这些location功能实现是一并采用mysql建表实现的。

[mysql]

CREATE TABLE `i_event_locations` (
`event_id` int(10) unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘活动id’,
`name` varchar(255) NOT NULL DEFAULT ” COMMENT ‘Name’,
`address` varchar(255) NOT NULL COMMENT ‘Address’,
`lat` double NOT NULL DEFAULT ‘0’ COMMENT ‘纬度’,
`lng` double NOT NULL DEFAULT ‘0’ COMMENT ‘经度’,
`created` int(10) unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘创建时间’,
`updated` int(10) unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘更新时间’,
UNIQUE KEY `event_id` (`event_id`) USING BTREE
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT=’Location Table’

[/mysql]

表结构就是如此,需要说明的是活动event与location是一对一的关系,event_id就是活动id即是外键也是主键。

这样的结构在后端业务中怎么实现呢,主要思路就是通过传入经纬度得到一个表达式distance,在event与location进行join联表查询时按distance进行desc的排序,得到最近的event list;如果需要限制在10km之间,那么就需要加上where distance < 10000


/**
* 返回计算距离的SQL语句表达式
* @param $lat
* @param $lng
*/
private function get_distance_exp($lat, $lng){
return DB::expr("(2 * 6378.137* ASIN(SQRT(POW(SIN(PI() * ($lat - lat)/360), 2) + COS(PI() * {$lat} / 180)
* COS(lat * PI() / 180) * POW(SIN(PI() * ({$lng} - lng) / 360), 2))))*1000");
}

就是上面这个东东,我也没看明白是什么原理。总之它返回的是以米为单位的一个数据值。

弊端确实有,需要联表查询且还要计算表达式可能会比较慢。(详细性能测试暂没有进行)

确实需要了解下性能方面的问题了,不能只听别人说mysql这样处理很慢就觉得慢,听别人说mongodb用的快就用;

mongodb是文档化schema free(自由格式)的数据库,开源有商业团队支持,高速发展,应用广泛;与mysql不存在谁取代谁的问题,只是什么环境下哪种数据库更适合。

回到那个地理位置检索的问题,将i_event_locations转变为mongodb collection后,可以根据建立的loc二维索引进行当前location 经纬度的检索,这样就得到了所有的event_id值,在通过这些event_id的到符合需求条件的event活动;

这样也就不用进行联表查询以及地理位置的表达式运算(这部分目前直接交予索引处理了);

MongoDB的特性 来自blog.nosqlfan.com

  • 简单的查询语句,没有Join操作
  • 文档型存储,其数据是用二进制的Json格式Bson存储的。其数据就像Ruby的hashes,或者Python的字典,或者PHP的数组
  • Sharding,MongoDB提供auto-sharding实现数据的扩展性
  • GridFS,MongoDB的提供的文件存储API
  • 数组索引,你可以对文档中的某个数组属性建立索引
  • MapReduce,可以用于进行复杂的统计和并行计算
  • 高性能,通过使用mmap和定时fsync的方法,避免了频繁IO,使其性能更高

MongoDB的优点

  • 高性能,速度非常快(如果你的内存足够的话)
  • 没有固定的表结构,不用为了修改表结构而进行数据迁移
  • 查询语言简单,容易上手
  • 使用Sharding实现水平扩展
  • 部署方便

使用MongoDB,你得记住以下几点:

  • MongoDB 假设你有大磁盘空间
  • MongoDB 假设你的内存也足够大于放下你的热数据
  • MongoDB 假设你是部署在64位系统上的(32位有2G的限制,试用还可以)
  • MongoDB 假设你的系统是little-endian的
  • MongoDB 假设你有多台机器(并不专注于单机可靠性)
  • MongoDB 假设你希望用安全换性能,同时允许你用性能换安全

MongoDB在下面领域不太擅长

  • 不太稳定,特别是auto-sharding目前还有很多问题
  • 不支持SQL,这意味着你很多通过SQL接口的工具不再适用
  • 持久化,MongoDB单机可靠性不太好,宕机可能丢失一段时间的数据
  • 相关文档比较少,新功能都有这个问题
  • 相关人才比较难找,这也是新功能的问题之一

安装mongodb

要学习使用,首先下载mongodb

windows 32位平台下也可以安装但是数据容量最大只有2G,可以作为测试学习之用,不推荐作为工作生产环境。

Linux 64位则最大可以达到128T,所以推荐linux平台上部署。

mongodb下载后解压到目录后

进入bin里面执行下面命令

–dbpath 是mongodb数据库文档存储目录,–logpath 是mongodb的日志文件,–install 则是将mongodb安装到系统服务里面去。要了解更多命令执行mongod –help查看。

执行完成后检查mongodb.log要是没有错误信息,则通过http://localhost:27017可以看到

You are trying to access MongoDB on the native driver port. For http diagnostic access, add 1000 to the port number
则说明成功了。而访问http://localhost:28017 可以查看mongodb的监控信息。

注意这里第一次安装没有成功时,mongodb_data里面新生成的的mongod.lock 可能会被锁住,再次安装时会产生

dbexit: really exiting now

这时需要删除掉mongod.lock,重新进行安装。

注意在windows xp 下面安装时可能会出现“无法定位程序输入点 InterlockedCompareExchange64 于动态链接库KERNEL32.dll上”

原因就是这样

“Mongodb最新的开发分支已经不再支持xp,也就是说vista是最陈旧支持的客户端,windows server2003是最陈旧的windows服务器版本,但是2.0的分支任然支持着xp,V2.0.6是最新也是最后一个新版本支持xp。因此最好还 是在 linux 下开发吧。”

我的windows xp安装版本是

db version v2.0.6, pdfile version 4.5
Wed Feb 27 22:24:02 git version: e1c0cbc25863f6356aa4e31375add7bb49fb05bc

所以还是推荐最新平台最新版本吧。

MongoDB shell命令

MongoDB自带一个JavaScript shell, 以命令行与MongoDB实例交互。

可以执行管理操作,检查运行实例等

启动Shell:

$ ./mongo

可以运行任何JavaScript程序,还可以利用JavaScript的标准库。所以所有的对象方法都是区分大小写的,输入命令时要注意!

可以定义和调用JavaScript函数。

可以使用多行命令。

show dbs; — 查看数据库

show databases;  –查看数据库

use dbname; –选择数据库

show collections; –查看表

show tables; –查看表

db.tablename.find(); — tablename的数据查询,

db.tablename.find({a: ‘k’}); — 对a字段进行数据k的匹配检索查询

db.tablename.find({a:’k’}).sort({id: -1}); –对a字段进行数据k的匹配检索查询且按照id进行倒叙排列,反之1则是正序排列了

db.tablename.find({a:’k’}).limit(5); –对a字段进行数据k的匹配检索查询只返回最初5条记录

db.tablename.find({a:’k’}).skip(2).limit(5); –对a字段进行数据k的匹配检索查询从第二条开始返回5条记录。skip()限制返回记录的开始点,就等于是mysql里面的offset

db.tablename.save({a: ‘haha’, id:’22}); — 添加新的记录

db.tablename.update({id: 22},  {$set: {a: ‘xixi’}}); — 对id为22的字段a进行修改;即第一个参数是query(也就是mysql中的where),第二个参数修改内容集合,还有第三个参数就是update时的一些设置;

db.tablename.remove({id:22}, 1); — 删除id为22的记录,第一个参数为where,第二个参数为limit,比如为1则只删除查询结果中的1条

db.tablename.drop(); –删除当前的表,drop()不同于remove(),remove只是清空数据,drop直接数据连同表删除了

db.dropDatabase(); –删除当前的数据库

mongodb crud manual

db.location.ensureIndex({loc: 2d}) –对location构建2d 地理位置的索引,{id: 1}设置为1则是正序索引,-1倒序索引;1(ascending),-1(descending)

db.location.getIndexes(); –查看location的索引。

db.location.getIndexKeys(); –查看location的索引keys。

db.location.dropIndex(‘loc’); –按索引名字删除索引,注意这里不是字段的名字

db.location.count(); –查询表中记录数

 

mongodb数据导出,不是进入到mongo shell里面去,

mongodump -d databasename -o D:/dirname -u username -p  –等于是mysql的mysqldump命令一样。

有导出当然有导入咯。

mongorestore -d test D:\mongodb_bak\test -u admin -p –还原test数据库

mongodb不推荐使用mongoexport与mongoimport来进行导出与导入;而是推荐使用mongodump与mongorestore来进行。

 

 

mongodb权限验证

mongodb 默认是无权限验证就可访问的。所以需要开启auth用户验证,需要在mongod安装时加上 –auth参数

Control Access to MongoDB Instances with Authentication

加入–auth参数安装好mongodb后;可以使用下面的进行用户设置

— 超级管理员,所有数据库都能访问

use admin

db.addUser(“username”,”password”);

— 普通管理员

user exampledb

db.addUser(“alex”,”alex”);

alex只能对exampledb进行查看管理

— 只读管理员,登录后只有读的权限

user exampledb

db.addUser(“alex”,”alex”,true);

 

对比之前的无认证环境,可以看到现在都出现了need login的信息内容了。

那首先就login

db.auth(“username”,”password”);  返回的是1,表示这个用户匹配上了,也就是登录成功了。

 

还有一种就是keyFile的方式,

Specify the path to a key file to store authentication information. This option is only useful for the connection between replica set members.

指定的密钥文件的路径存储身份验证信息。此选项只适用于副本集成员之间的连接。(没明白这个副本集成员之间的链接是什么意思)

同样是在安装时指定 mongod –keyFile filepath

keyFile 必须小于1kb,只是包含base64的字符;密钥文件没有组且具有 “world” permissions on UNIX systems

+++++

unix world permissions

unix的世界权限是个啥呢?Unix Permissions

找到了一篇解答

The third group of permission bits corresponds to the world permissions. These are the permissions granted to everyone.

也就是每一个人都具有这个权限,任何人都有这个权限。

+++++

在windows system上面不检测权限。

更多请看:Security Considerations for Replica Sets

 

 

 

更多命令看这里

学习资料推荐

MongoDB Manual

MongoDB资料汇总专题

MySQL和MongoDB设计实例对比

Php Mongodb Manual

MongoDB php driver

Php mongodb-odm

Kohanaframework mongodb module

从PHP客户端看MongoDB通信协议

2012这一年

现在已经是2013 2月13号了,大年初四;在元旦就开始计划需要写下2012这年所发生的事情以及验证检验下年初制定的计划完成度。

首先回顾下去年的总结文章。

2011年末总结系列之工作篇

2011年末总结系列之个人的生活篇

2011年末总结系列之个人情感篇

工作

2012 年前半年还是在忙mpf项目内容,还是一如既往的discuzx二次开发;下半年的时候再次看那些N复杂已经绕弯弯的业务需求真是不知道当初是怎么做出来 的。。。而且最关键的是,时间一长就忘了了这些代码为什么这样写(要是由他人后期维护真的会很纠结。)且不论需求的复杂度;就是一点被他们今天要一个A, 明天要一个B,且最关键的一点是我们的建议他听不进去,当初作死做活的功能,现在一句话就不要了。虽然公司是不亏的,但作为程序员来说这种东西一点成就感 也没有。

mpf从3月开始一直开发到7月,7月-8月就是由我一个人开发了。另一组员L调至一个移动应用开发组里面去了。然后其中主要开发 的一个需求是“将只能通过虚拟货币购买积分改为不仅能通过虚拟货币购买积分,还能直接通过RMB直接进行积分购买”。原先设计就是只能是余额足够的情况下 进行虚拟商品的购买,即只有记录的概念而没有订单的概念。而现在要的是在余额不足情况下,需要自动跳转至支付平台进行在线支付完成购买。

功 能设计不算复杂,但是最最麻烦的地方在于以现有代码的基础上进行修改。也就是改比新开发还有麻烦的事情被我碰上了。。。既然如此,现在网站以上线,且数据 量都已存在;那么现在的网站数据库结构就不能大调整了,只能加不能减。即代码最后都作用于新的数据库表上面,原始数据库表只要把数据导入到新表中,保持用 户的数据一致就行了。在完成设计后开发大概用了2个星期左右,又用了两个星期进行检查,测试,数据导入;集成了paypal,对discuzx自有的 alipay、tenpay进行了修改。(话说如果时间长了,估计我都不知道为什么这样写了,不过还好注释不少不少)

在mpf告一段落后, 公司决定暂时搁置这个项目(主要是给钱不积极啦)我就加入到了informatree项目组中,这个项目只是大概了解。公司从3月份开始立项,一直开发至 今;是一款主攻欧美国外市场的IOS、Android平台社交活动类的一款APP,大家可以再App store 里面搜索“intreest”下载安装。官网这里

intreest 这个项目周期也是比较长,app最新已经1.2版本了。移动应用采用前端+后端服务器的模式,与之前常见的b/s模式不同,在初期加入开发的过程中不是很 习惯啊。大致请求流程就是app通过TOKEN验证的api,向服务器发送一个请求,服务器验证token+有半小时时间期限的hash值(这个hash 主要用于api权限验证,只有app与服务器知道,避免有人知晓token后无限次的请求访问),验证通过后服务器将数据值json格式化返回给app。 到此完成一次请求。这个项目里面确实学到了不少东西,主要是oo方面构建对象后对数据的操作,以及各种微博系统,地理位置的业务逻辑熟悉使用,搜索方面使 用Apache solr进行数据检索。当然技术方面还是略显不足,很多地方只是原搬照用,还要加强学习。还有一个比较遗憾的地方就是在这个项目期间有三位开发方面的人员 离职了。

这 一年里,个人的技术风格以及定位有一个初步的认识,但并没有完全做好打算去怎样怎样,这也是自己不自信的原因。没错,你是做了很多事,但是这些事情都是 他人可以直接替代的,你的特长时什么?你的风格是什么?你未来的道路是什么?现在我还不能完全答复。这也是要在2012年里面更加的清晰和明了的。很多事 情充满了变数,又有很多事情难以改变的。比如你的性格,我的强迫症。

这是去年的一段话,我想把2012换成2013也是适用于今年的。。。改变一点都没看见啊。技术风格?特长,优势?哎。。。继续前行吧。

 

生活

生 活还是如此,平静而简单。今年就是多了一个看球,由于恒大的牛x,在亚冠中超牛xx的。向我这种伪球迷当然也就是“趋之若鹜”了,不是我不喜欢英超西甲, 而是我始终认为那种情况离我太遥远,没有一种情感代入感;所以还是好好看看家门口的中超吧,尽管现在他的水平还很低,但还是一如既往的支持。2013还看 恒大!当然武汉队也要看看!

老爸手工业制作者,房租突然涨了一倍,真的很贵快3000/月了。但是房子没有变大,地理位置也没有变得优势;万恶的房东啊,另一个房东却还是维持原价,差距怎么这么大呢!这样生意将变得更加难了,希望2013年里面好做,蛇年万事如意!

在1月的时候老爸新买一个“五菱之光”微面,4w多元,老妈说这是多年攒下的钱了。。。老爸一直希望有个车,这下终于买了,得了那就安心好好挣钱,平安开车吧。

朋友联系的还好,他们现在都还不错;只是见面相聚较少,真是越长大与孤单啊。

公司同事相比第一年来到时,2012大家出来相聚的时间更少了,可能人真的会厌倦吧。

表哥腊月24的结婚了,是介绍认识的,大概半年吧。其实我始终认为这样的婚姻感觉有那么些不自主,当然也就偏见的认为这不是男女主人自己想要的。但现在不管怎样,祝他们幸福。

 

情感

“在之前的生活篇里面也提到了,2012年龙年,第二个本命年里,我最大的期望与理想就是找到一位适合的、相爱的、谈婚论嫁的女朋友!这一目标在2012年里要坚定不移的执行实施!立字为证,明年这个时候,写文汇报。”

这文是写不出了,因为在自己的本命年里面,女朋友这一重要元素还是竹篮打水一场空。尽管做过一些努力但是很多时候我都不知道我喜欢什么样的,看这个也喜欢,看那个也喜欢。得到一个真理,网络上的都是假的!!还不如勇敢的在现实里面搭讪吧。

蛇年里面一定要有新动向啊!要不然就要被逼婚了。。。%>_<%

 

 

2013路还是继续走,希望能顺利一点。

mysql表分区创建使用学习

表分区的测试使用,主要内容来自于其他博客文章以及mysql5.1的参考手册

mysql测试版本:mysql5.5.28

mysql物理存储文件(有mysql配置的datadir决定存储路径)格式简介

数据库engine为MYISAM

frm表结构文件,myd表数据文件,myi表索引文件。

INNODB engine对应的表物理存储文件

innodb的数据库的物理文件结构为:

.frm文件

.ibd文件和.ibdata文件:

这两种文件都是存放innodb数据的文件,之所以用两种文件来存放innodb的数据,是因为innodb的数据存储方式能够通过配置来决定是使用共享表空间存放存储数据,还是用独享表空间存放存储数据。

独享表空间存储方式使用.ibd文件,并且每个表一个ibd文件

共享表空间存储方式使用.ibdata文件,所有表共同使用一个ibdata文件

创建分区

分区的一些优点包括:

·         与单个磁盘或文件系统分区相比,可以存储更多的数据。

·         对于那些已经失去保存意义的数据,通常可以通过删除与那些数据有关的分区,很容易地删除那些数据。相反地,在某些情况下,添加新数据的过程又可以通过为那些新数据专门增加一个新的分区,来很方便地实现。

通常和分区有关的其他优点包括下面列出的这些。MySQL 分区中的这些功能目前还没有实现,但是在我们的优先级列表中,具有高的优先级;我们希望在5.1的生产版本中,能包括这些功能。

·         一些查询可以得到极大的优化,这主要是借助于满足一个给定WHERE 语句的数据可以只保存在一个或多个分区内,这样在查找时就不用查找其他剩余的分区。因为分区可以在创建了分区表后进行修改,所以在第一次配置分区方案时还不曾这么做时,可以重新组织数据,来提高那些常用查询的效率。

·         涉及到例如SUM() 和 COUNT()这样聚合函数的查询,可以很容易地进行并行处理。这种查询的一个简单例子如 “SELECT salesperson_id, COUNT(orders) as order_total FROM sales GROUP BY salesperson_id;”。通过“并行”, 这意味着该查询可以在每个分区上同时进行,最终结果只需通过总计所有分区得到的结果。

·         通过跨多个磁盘来分散数据查询,来获得更大的查询吞吐量。

简而言之就是 数据管理优化,查询更快,数据查询并行

检测mysql是否支持分区


mysql&gt; show variables like
&quot;%partition%&quot;;
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| have_partitioning | YES   |
+-------------------+-------+
1 row in set

RANGE 分区:基于属于一个给定连续区间的列值,把多行分配给分区。


DROP TABLE IF EXISTS `p_range`;

CREATE TABLE `p_range` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` char(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE (id)
(PARTITION p0 VALUES LESS THAN (8) ENGINE = MyISAM) */;

range分区就是 partition by range(id) 表示按id 1-7的数据存储在p0分区;如果id大于7了则数据不能写入了,因为没有对应的数据分区来存储;
所以这时在创建分区时需要使用maxvalues关键字了

PARTITION BY RANGE (id)
(
PARTITION p0 VALUES LESS THAN (8),
PARTITION p1 VALUES LESS THAN MAXVALUE)

这样就表示,所有id大于7的数据记录存在在p1分区里。

RANGE分区在如下场合特别有用:

·         当需要删除“旧的”数据时。如果你使用上面最近的那个例子给出的分区方案,你只需简单地使用 “ALTER TABLE employees DROP PARTITION p0;”来删除所有在1991年前就已经停止工作的雇员相对应的所有行。对于有大量行的表,这比运行一个如“DELETE FROM employees WHERE YEAR(separated) <= 1990;”这样的一个DELETE查询要有效得多。

·         想要使用一个包含有日期或时间值,或包含有从一些其他级数开始增长的值的列。

·         经常运行直接依赖于用于分割表的列的查询。例如,当执行一个如“SELECT COUNT(*) FROM employees WHERE YEAR(separated) = 2000 GROUP BY store_id;”这样的查询时,MySQL可以很迅速地确定只有分区p2需要扫描,这是因为余下的分区不可能包含有符合该WHERE子句的任何记录。

LIST 分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。

list分区可以理解为按一个键的id区间进行数据存储,比如类型表 1,2,3,4的所有记录存储在p0里面,5,6,7,8存在在p1分区里面

这里与range分区一样,如果现在有条记录typeid是9,那么这条记录是不能存入的;

需要注意的是:LIST分区没有类似如“VALUES LESS THAN MAXVALUE”这样的包含其他值在内的定义。将要匹配的任何值都必须在值列表中找到。


DROP TABLE IF EXISTS `p_list`;

CREATE TABLE `p_list` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`typeid` mediumint(10) NOT NULL DEFAULT '0',
`typename` char(20) DEFAULT NULL,
PRIMARY KEY (`id`,`typeid`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY LIST (typeid)
(PARTITION p0 VALUES IN (1,2,3,4) ENGINE = MyISAM,
PARTITION p1 VALUES IN (5,6,7,8) ENGINE = MyISAM) */;

HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。

HASH分区主要用来确保数据在预先确定数目的分区中平均分布。在RANGE和LIST分区中,必须明确指定一个给定的列值或列值集合应该保存在哪个分区中;而在HASH分区中,MySQL 自动完成这些工作,你所要做的只是基于将要被哈希的列值指定一个列值或表达式,以及指定被分区的表将要被分割成的分区数量。

要使用HASH分区来分割一个表,要在CREATE TABLE 语句上添加一个“PARTITION BY HASH (expr)”子句,其中“expr”是一个返回一个整数的表达式。它可以仅仅是字段类型为MySQL 整型的一列的名字。此外,你很可能需要在后面再添加一个“PARTITIONS num”子句,其中num 是一个非负的整数,它表示表将要被分割成分区的数量。如果没有包括一个PARTITIONS子句,那么分区的数量将默认为1。


DROP TABLE IF EXISTS `p_hash`;

CREATE TABLE `p_hash` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`storeid` mediumint(10) NOT NULL DEFAULT '0',
`storename` char(255) DEFAULT NULL,
PRIMARY KEY (`id`,`storeid`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY HASH (storeid)
PARTITIONS 4 */;

InnoDB引擎

简单点说就是数据的存入可以按 partition by hash(expr); 这里的expr可以是键名也可以是表达式比如YEAR(time),如果是表达式的情况下

“但是应当记住,每当插入或更新(或者可能删除)一行,这个表达式都要计算一次;这意味着非常复杂的表达式可能会引起性能问题,尤其是在执行同时影响大量行的运算(例如批量插入)的时候。 ”

在执行删除、写入、更新时这个表达式都会计算一次。

数据的分布采用基于用户函数结果的模数来确定使用哪个编号的分区。换句话,对于一个表达式“expr”,将要保存记录的分区编号为N ,其中“N = MOD(expr, num)”。

比如上面的storeid 为10;那么 N=MOD(10,4) ;N是等于2的,那么这条记录就存储在p2的分区里面。

如果插入一个表达式列值为’2005-09-15’的记录到表中,那么保存该条记录的分区确定如下:MOD(YEAR(‘2005-09-01’),4)  =  MOD(2005,4)  =  1 ;就存储在p1分区里面了。

“MySQL 5.1 还支持一个被称为“linear hashing(线性哈希功能)”的变量,它使用一个更加复杂的算法来确定新行插入到已经分区了的表中的位置。

线性哈希分区和常规哈希分区在语法上的唯一区别在于,在“PARTITION BY” 子句中添加“LINEAR”关键字;线性哈希功能使用的一个线性的2的幂(powers-of-two)运算法则

按照线性哈希分区的优点在于增加、删除、合并和拆分分区将变得更加快捷,有利于处理含有极其大量(1000GB)数据的表。

它的缺点在于,与使用常规HASH分区得到的数据分布相比,各个分区间数据的分布不大可能均衡。”

KEY 分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL 服务器提供其自身的哈希函数。必须有一列或多列包含整数值。


DROP TABLE IF EXISTS `p_key`;

CREATE TABLE `p_key` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`keyname` char(20) DEFAULT NULL,
`keyval` varchar(1000) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=12 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY KEY (id)
PARTITIONS 4 */;

按照KEY进行分区类似于按照HASH分区,除了HASH分区使用的用户定义的表达式,而KEY分区的 哈希函数是由MySQL 服务器提供。MySQL 簇(Cluster)使用函数MD5()来实现KEY分区;对于使用其他存储引擎的表,服务器使用其自己内部的 哈希函数,这些函数是基于与PASSWORD()一样的运算法则。

“CREATE TABLE … PARTITION BY KEY”的语法规则类似于创建一个通过HASH分区的表的规则。它们唯一的区别在于使用的关键字是KEY而不是HASH,并且KEY分区只采用一个或多个列名的一个列表。

与hash的区别就是,hash使用用户定义的表达式如YEAR(time) ;而key分区则是由mysql服务器提供的。同样KEY也是可以使用linear线性key的,与hash linear是相同的算法。

子分区:是分区表中每个分区的再次分割。


DROP TABLE IF EXISTS `p_subpartition`;

CREATE TABLE `p_subpartition` (
`id` int(10) DEFAULT NULL,
`title` char(255) NOT NULL,
`createtime` date NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8
/*!50100

PARTITION BY RANGE (YEAR(createtime))
SUBPARTITION BY HASH (MONTH(createtime))
(PARTITION p0 VALUES LESS THAN (2012)
(SUBPARTITION s1 ENGINE = MyISAM,
SUBPARTITION s2 ENGINE = MyISAM),
PARTITION p1 VALUES LESS THAN (2013)
(SUBPARTITION s3 ENGINE = MyISAM,
SUBPARTITION s4 ENGINE = MyISAM),
PARTITION p2 VALUES LESS THAN MAXVALUE
(SUBPARTITION s5 ENGINE = MyISAM,
SUBPARTITION s6 ENGINE = MyISAM)) */;

可以看到p_subpartition有三个分区p0,p1,p2;而这三个分区每一个又进一步分为2个分区。那么整个表都就分为6个小分区;

可以看到代表p_sobpartitionp0.myd的文件消失了,取代的是p_subpartition#p#p0#sp#s1.myd

在MySQL 5.1中,对于已经通过RANGE或LIST分区了的表再进行子分区是可能的。

子分区是分区表中每个分区的再次分割,子分区既可以使用HASH希分区,也可以使用KEY分区。这 也被称为复合分区(composite partitioning)。

1,如果一个分区中创建了子分区,其他分区也要有子分区

2,如果创建了了分区,每个分区中的子分区数必有相同

3,同一分区内的子分区,名字不相同,不同分区内的子分区名子可以相同(5.1.50不适用)

分区注意点

1、重新分区时,如果原分区里面存在maxvalue则新的分区里面也必须包含maxvalue否则就错误。
alter table p_range2x
reorganize partition p1,p2
into (partition p0 values less than (5), partition p1 values less than maxvalue);

[Err] 1520 – Reorganize of range partitions cannot change total ranges except for last partition where it can extend the range

2、分区删除时,数据也同样会被删除

alter table p_range drop partition p0;

3、如果range分区列表里面没有maxvalue则如有新数据大于现在分区range数据值那么这个数据是无法写入到数据库表的。

4、修改表名不需要 删除分区后在进行更改,修改表名后分区存储myd myi对应也会自动更改。

如果希望从所有分区删除所有的数据,但是又保留表的定义和表的分区模式,使用TRUNCATE TABLE命令。(请参见13.2.9节,“TRUNCATE语法”)。

如果希望改变表的分区而又不丢失数据,使用“ALTER TABLE … REORGANIZE PARTITION”语句。参见下面的内容,或者在13.1.2节,“ALTER TABLE语法” 中参考关于REORGANIZE PARTITION的信息。

5、对表进行分区时,不论采用哪种分区方式如果表中存在主键那么主键必须在分区列中。表分区的局限性。

6、list方式分区没有类似于range那种 less than maxvalue的写法,也就是说list分区表的所有数据都必须在分区字段的值列表集合中。

7、在MySQL 5.1版中,同一个分区表的所有分区必须使用同一个存储引擎;例如,不能对一个分区使用MyISAM,而对另一个使用InnoDB。

8、分区的名字是不区分大小写的,myp1与MYp1是相同的。

分区的管理

range与list分区的改变动作不能适用于hash与key方式的分区。删除与添加动作是都能使用的。

以下面的例子


DROP TABLE IF EXISTS `p_list`;

CREATE TABLE `p_list` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`typeid` mediumint(10) NOT NULL DEFAULT '0',
`typename` char(20) DEFAULT NULL,
PRIMARY KEY (`id`,`typeid`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY LIST (typeid)
(PARTITION p0 VALUES IN (1,2,3,4) ENGINE = MyISAM,
PARTITION p1 VALUES IN (5,6,7,8) ENGINE = MyISAM) */;

range与list分区的管理
删除分区
ALTER TABLE tr DROP PARTITION p1;
需要注意的是删除分区后,该分区的所有数据都没有了。同时删除后存在一个重大影响也就是typeid为5,6,7,8的记录是不能写入到该表了的!

清空数据

如果想要保留表结构与分区结构可以使用 TRUNCATE TABLE 清空表

更改分区保留数据

ALTER TABLE tbl_name REORGANIZE PARTITION partition_list INTO (partition_definitions);

如果想保留数据进行分区的更改

ALTER TABLE p_list REORGANIZE PARTITION p0 INTO (
 PARTITION s0 VALUES IN(1,2),
 PARTITION s1 VALUES IN(3,4),
);

这样就能进行分区的合并了,那怎么进行拆分呢

ALTER TABLE p_list REORGANIZE PARTITION s0,s1 INTO (
 PARTITION p0 VALUES IN(1,2,3,4),
); 使用 REORGANIZE PARTITION进行数据的合并与拆分,数据是没有丢失的。
在使用REORGANIZE进行重新分区时,需要注意几点:
1、用来确定新分区模式的PARTITION子句使用与用在CREATE TABLE中确定分区模式的PARTITION子句相同的规则。(partition 分区子句必须与创建原分区时的规则相同)
2、partition_definitions 列表中分区的合集应该与在partition_list 中命名分区的合集占有相同的区间或值集合。 (不管是合并还是拆分,s0,s1到p0;p0到s0,s1 里面的区间或者值都必须相同)
3、对于按照RANGE分区的表,只能重新组织相邻的分区;不能跳过RANGE分区。(比如按range年份 p0 1990,p1 2000 ,p2 2013三个分区;在合并时partition p0,p2 into()
   这样是不行的,因为这两个分区不是相邻的分区;)
4、不能使用REORGANIZE PARTITION来改变表的分区类型;也就是说,例如,不能把RANGE分区变为HASH分区,反之亦然。也不能使用该命令来改变分区表达式或列。

增加分区
ALTER TABLE p_list ADD PARTITION (PARTITION p2 VALUES IN (9, 10, 11));
但是不能使用
ALTER TABLE p_list ADD PARTITION (PARTITION p2 VALUES IN (9, 14));
这样mysql 会产生错误1465 (HY000): 在LIST分区中,同一个常数的多次定义
hash与key分区的管理在改变分区设置方面,按照HASH分区或KEY分区的表彼此非常相似,但是它们又与按照RANGE或LIST分区的表在很多方面有差别。
关于添加和删除按照RANGE或LIST进行分区的表的分区
不能使用与从按照RANGE或LIST分区的表中删除分区相同的方式,来从HASH或KEY分区的表中删除分区。但是,可以使用“ALTER TABLE ... COALESCE PARTITION”命令来合并HASH或KEY分区。
DROP TABLE IF EXISTS `p_hash`;

CREATE TABLE `p_hash` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`storeid` mediumint(10) NOT NULL DEFAULT '0',
`storename` char(255) DEFAULT NULL,
PRIMARY KEY (`id`,`storeid`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY HASH (storeid)
PARTITIONS 4 */;

如p_hash的分区数为4个; 要减少分区数为2个 ALTER TABLE p_hash COALESCE PARTITION 2 对于按照HASH,KEY,LINEAR HASH,或LINEAR KEY分区的表, COALESCE能起到同样的作用。

COALESCE不能用来增加分区的数量,如果你尝试这么做,结果会出现类似于下面的错误:

mysql> ALTER TABLE clients COALESCE PARTITION 18;
错误1478 (HY000): 不能移动所有分区,使用DROP TABLE代替

要增加顾客表的分区数量从12到18,使用“ALTER TABLE … ADD PARTITION”,具体如下:

ALTER TABLE clients ADD PARTITION PARTITIONS 18;

注释:“ALTER TABLE … REORGANIZE PARTITION”不能用于按照HASH或HASH分区的表。

分区维护

重建分区

这和先删除保存在分区中的所有记录,然后重新插入它们,具有同样的效果。它可用于整理分区碎片。

ALTER TABLE t1 REBUILD PARTITION (p0, p1);

优化分区

如果从分区中删除了大量的行,或者对一个带有可变长度的行(也就是说,有VARCHAR,BLOB,或TEXT类型的列)作了许多修改,

可以使用“ALTER TABLE … OPTIMIZE PARTITION”来收回没有使用的空间,并整理分区数据文件的碎片。

ALTER TABLE t1 OPTIMIZE PARTITION (p0, p1);

分析分区
读取并保存分区的键分布
ALTER TABLE t1 ANALYZE PARTITION (p3);

修补分区: 修补被破坏的分区。
ALTER TABLE t1 REPAIR PARTITION (p0,p1);

检查分区
可以使用几乎与对非分区表使用CHECK TABLE 相同的方式检查分区。
ALTER TABLE trb3 CHECK PARTITION (p1);
这个命令可以告诉你表t1的分区p1中的数据或索引是否已经被破坏。如果发生了这种情况,使用“ALTER TABLE ... REPAIR PARTITION”来修补该分区。

获取分区信息

在mysql服务器信息数据库里面的partitions存放着服务器所有表的分区信息。


-- explain partitions命令
explain partitions select * from p_hash
+----+-------------+--------+-------------+------+---------------+------+---------+------+------+-------+
| id | select_type | table  | partitions  | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+--------+-------------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | p_hash | p0,p1,p2,p3 | ALL  | NULL          | NULL | NULL    | NULL |   10 |       |
+----+-------------+--------+-------------+------+---------------+------+---------+------+------+-------+

-- 获取到p_list表的分区详细信息。

select * from information_schema.`PARTITIONS` where TABLE_NAME = 'p_list';
-- 分区的创建信息
show create table p_list;

 

更多阅读:

mysql5.5 reference manmul partition

mysql表分区
mysql分区功能详细介绍以及实例

discuzx2系列之session的实现机制

discuzx2系列文章

discuzx2系列之群组积分排行

我们知道discuzx是没有采用php默认的session机制的,而是自己采用数据库表自己进行了构建一套session用户状态识别机制;而不是简单的把php session采用存入数据库的方式。

直接看代码

几个顶级的control入口是引用了下面的class_core.php;也就是discuzx核心类文件。


require './source/class/class_core.php';
$discuz = & discuz_core::instance();

$discuz->init();

可以再discuz_core类里面的,初始化了一系列模块


function init() {
if(!$this->initated) {
$this->_init_db();
$this->_init_memory();
$this->_init_user();
$this->_init_session();    // 初始化session
$this->_init_setting();
$this->_init_mobile();
$this->_init_cron();
$this->_init_misc();
$this->_init_connect();
}
$this->initated = true;
}

那就来看看_init_session()  这个函数的主要作用初始化session,检测已有session或者创建新的session作为当前连接的唯一标识
其中附加功能包括,当前连接用户ip的白名单,黑名单检测;确定用户的详细属性信息,如是新登陆的用户则更新用户的状态信息和在线统计信息


function _init_session() {
$this->session = new discuz_session();  // 实例化数据库session类

if($this->init_session)    {
// $this->var 就是超全局变量$_G 可以看到$this->var = & $_G;是在 _init_env()中引用赋值的
// 将目前cookie为sid的值和用户个人信息传入init;discuz_session 的init方法始终会返回一个实例对象,来表示当前的用户或游客
$this->session->init($this->var['cookie']['sid'], $this->var['clientip'], $this->var['uid']);

$this->var['sid'] = $this->session->sid;
$this->var['session'] = $this->session->var; // 当前用户对象的session数据

// 如果当前返回sid 不等于浏览器中名为sig cookie的值,则设置新的sid值1天后过期
if($this->var['sid'] != $this->var['cookie']['sid']) {
dsetcookie('sid', $this->var['sid'], 86400);
}
// $isnew 表示是否为网站新的用户,也就是新创建的session
if($this->session->isnew) {
// 是新来访用户,检测用户的ip是否在系统的可访问名单($_G['setting']['ipaccess'])或是否在系统的黑名单(common_banned禁止访问表;cache_ipbanned.php)中
if(ipbanned($this->var['clientip'])) {
// ip不在可访问名单或者ip在系统的黑名单中;则将用户移入 groupid=6的“禁止ip”组;游客的groupid是7
$this->session->set('groupid', 6);
}
}

// 如果是“禁止ip”组,显示系统提示信息
// "抱歉,您的 IP 地址不在被允许,或您的账号被禁用,无法访问本站点"
if($this->session->get('groupid') == 6) {
$this->var['member']['groupid'] = 6;
sysmessage('user_banned');
}
// 存在当前登录用户;用户是新登录的或者lastactivity最后活动时间在十分钟以前了
if($this->var['uid'] && ($this->session->isnew || ($this->session->get('lastactivity') + 600) &lt; TIMESTAMP)) {
// 重新设置lastactivity,接下来在footer.html中调用updatesession();时更新当前用户session记录信息 主要作用就是更新lastactivity;避免按在线时间$_G['setting']['onlinehold']过期被清除掉
$this->session->set('lastactivity', TIMESTAMP);
if($this->session->isnew) {
// 新的登录用户,更新用户的状态
DB::update('common_member_status', array('lastip' => $this->var['clientip'], 'lastvisit' => TIMESTAMP), "uid='".$this->var['uid']."'");
}
}

}
}

discuz_session类;主要作用对session进行操作,对应的是common_session记录表。其中$discuz->session 返回的就是浏览器当前连接用户的session实例对象了。($discuz就是discuz_core的引用实例)


class discuz_session {

var $sid = null;
var $var;
var $isnew = false;
var $newguest = array('sid' => 0, 'ip1' => 0, 'ip2' => 0, 'ip3' => 0, 'ip4' => 0,
'uid' => 0, 'username' => '', 'groupid' => 7, 'invisible' => 0, 'action' => 0,
'lastactivity' => 0, 'fid' => 0, 'tid' => 0, 'lastolupdate' => 0, 'country' => 0, 'email' => ''); //add country ,email

var $old =  array('sid' =>  '', 'ip' =>  '', 'uid' =>  0);

// 实例化,初始化session默认数据
function discuz_session($sid = '', $ip = '', $uid = 0) {
$this->old = array('sid' =>  $sid, 'ip' =>  $ip, 'uid' =>  $uid);
$this->var = $this->newguest;
if(!empty($ip)) {
$this->init($sid, $ip, $uid);
}
}

function set($key, $value) {
if(isset($this->newguest[$key])) {
$this->var[$key] = $value;
} elseif ($key == 'ip') {
$ips = explode('.', $value);
$this->set('ip1', $ips[0]);
$this->set('ip2', $ips[1]);
$this->set('ip3', $ips[2]);
$this->set('ip4', $ips[3]);
}
}

function get($key) {
if(isset($this->newguest[$key])) {
return $this->var[$key];
} elseif ($key == 'ip') {
return $this->get('ip1').'.'.$this->get('ip2').'.'.$this->get('ip3').'.'.$this->get('ip4');
}
}
// 初始化返回session实例对象
function init($sid, $ip, $uid) {
$this->old = array('sid' =>  $sid, 'ip' =>  $ip, 'uid' =>  $uid);
$session = array();
// cookie sid存在;读取记录
if($sid) {
$session = DB::fetch_first("SELECT * FROM ".DB::table('common_session').
" WHERE sid='$sid' AND CONCAT_WS('.', ip1,ip2,ip3,ip4)='$ip'");
}
// 不存在或存在session记录但与新的uid值不匹配
if(empty($session) || $session['uid'] != $uid) {
// 重新创建session记录
$session = $this->create($ip, $uid);
}

$this->var = $session;
$this->sid = $session['sid'];
}

// 返回新设置的session对象
function create($ip, $uid) {

$this->isnew = true;
$this->var = $this->newguest;
$this->set('sid', random(6));
$this->set('uid', $uid);
$this->set('ip', $ip);

//写入区域信息
include_once libfile('function/misc');
$ipaddress = convertip($ip);
//$ipaddress = mb_convert_encoding($ipaddress, 'UTF-8', 'GB2312');
$country = mb_substr($ipaddress, 2, 2, 'UTF-8');  // 获取两个字存入数据库

include_once libfile('local', 'include');
if (array_key_exists($country, $local_name)) { //如果数据在数组中
$country = $local_name[$country];
} else {// 如果不是则随机获取一个
$country = $local_name[array_rand($local_name)];
}
$this->set('country', $country);

$uid && $this->set('invisible', getuserprofile('invisible'));
$this->set('lastactivity', time());
$this->sid = $this->var['sid'];

return $this->var;
}

// 清除session记录,也就是清除掉已过期的连接
function delete() {

global $_G;
// onlinehold 系统设置的用户在线时间(分钟) 15
// 这里的$_G['setting']['onlinehold']已经换算成秒了;build_cache_setting()
$onlinehold = $_G['setting']['onlinehold'];
// 游客活动时间 1分钟
$guestspan = 60;
$onlinehold = time() - $onlinehold;
$guestspan = time() - $guestspan;

$condition = " sid='{$this->sid}' ";
$condition .= " OR lastactivity&lt;$onlinehold ";  // 活动时间距离现在超过了15分钟了
$condition .= " OR (uid='0' AND ip1='{$this->var['ip1']}' AND ip2='{$this->var['ip2']}' AND ip3='{$this->var['ip3']}' AND ip4='{$this->var['ip4']}' AND lastactivity>$guestspan) "; // 同一ip,访问活动时间在1分钟以内
$condition .= $this->var['uid'] ? " OR (uid='{$this->var['uid']}') " : ''; // 存在uid 也可按uid删除
DB::delete('common_session', $condition);
}

// 更新记录
function update() {
global $_G;
if($this->sid !== null) {

$data = daddslashes($this->var);
// 新创建用户session记录;且对符合提交的session记录进行清除
if($this->isnew) {
$this->delete();
DB::insert('common_session', $data, false, false, true);
} else {
// 更新session记录
DB::update('common_session', $data, "sid='$data[sid]'");
}
$_G['session'] = $data;
// 更新sid过期时间,等于是每次刷新都会重新更新
dsetcookie('sid', $this->sid, 86400);
}
}

// 在线人数统计 登录用户/游客统计
function onlinecount($type = 0) {
$condition = $type == 1 ? ' WHERE uid>0 ' : ($type == 2 ? ' WHERE invisible=1 ' : '');
return DB::result_first("SELECT count(*) FROM ".DB::table('common_session').$condition);
}

}

对应的php session中的有效期,回收机制就是delete()方法;写入与读取对应的就是update();那用户在浏览器端刷新后是怎么进行session的验证与返回的呢?session开启后,用户每次请求服务器都会进行数据验证与返回的;所以discuzx每次请求都会执行这个update()

discuzx采用的是页面载入模板的形式,对应的 template/default/common/footer.htm是每个页面都可能会加载的;有一些公共内容在这里,其中就有用户的session更新函数调用

<!–{eval updatesession();}–>

updatesession()函数


function updatesession($force = false) {

global $_G;
static $updated = false;

if(!$updated) {
// 登录用户
if($_G['uid']) {
// 获取当前用户最后活动时间且authcode编码后写入到浏览器cookie中
if($_G['cookie']['ulastactivity']) {
$ulastactivity = authcode($_G['cookie']['ulastactivity'], 'DECODE');
} else {
$ulastactivity = getuserprofile('lastactivity');
// 有效期长达一年
dsetcookie('ulastactivity', authcode($ulastactivity, 'ENCODE'), 31536000);
}
}
// 实例化核心类
$discuz = & discuz_core::instance();
// $discuz->session 当前这个用户的session对象

// 用户在线时间更新时长(分钟) 也就是在线用户多久更新一次自己的“在线时间”
// Discuz! 可统计每个用户总共和当月的在线时间,本设置用以设定更新用户在线时间的时间频率。例如设置为 10,则用户每在线 10 分钟更新一次记录。本设置值越小,则统计越精确,但消耗资源越大。建议设置为 5~30 范围内,0 为不记录用户在线时间
$oltimespan = $_G['setting']['oltimespan'];
// session记录中的“用户在线时间更新”的最后时间
$lastolupdate = $discuz->session->var['lastolupdate'];

// 登录用户 且存在“在线时间更新”频率 且时间段已过则进行用户的个人在线时间更新。
// 等于是用户在线时间是按每次10分钟,这样叠加的;间隔越小越准确。
if($_G['uid'] && $oltimespan && TIMESTAMP - ($lastolupdate ? $lastolupdate : $ulastactivity) > $oltimespan * 60)      {
DB::query("UPDATE ".DB::table('common_onlinetime')."
SET total=total+'$oltimespan', thismonth=thismonth+'$oltimespan', lastupdate='" . TIMESTAMP . "'
WHERE uid='{$_G['uid']}'");
if(!DB::affected_rows()) {
DB::insert('common_onlinetime', array(
'uid' => $_G['uid'],
'thismonth' => $oltimespan,
'total' => $oltimespan,
'lastupdate' => TIMESTAMP,
));
}
// 重新设置 “用户在线时间更新”的最后更新时间
$discuz->session->set('lastolupdate', TIMESTAMP);
}
foreach($discuz->session->var as $k => $v) {
if(isset($_G['member'][$k]) && $k != 'lastactivity') {
$discuz->session->set($k, $_G['member'][$k]);
}
}

foreach($_G['action'] as $k => $v) {
$discuz->session->set($k, $v);
}

// 这里进行session状态更新
$discuz->session->update();

$updated = true;
// 更新当前登录用户的统计信息与用户状态信息
if($_G['uid'] && TIMESTAMP - $ulastactivity > 21600) {
if($oltimespan && TIMESTAMP - $ulastactivity > 43200) {
$total = DB::result_first("SELECT total FROM ".DB::table('common_onlinetime')." WHERE uid='$_G[uid]'");
// 换算为分钟写入用户个人统计信息表
DB::update('common_member_count', array('oltime' => round(intval($total) / 60)), "uid='$_G[uid]'", 1);
}
dsetcookie('ulastactivity', authcode(TIMESTAMP, 'ENCODE'), 31536000);
DB::update('common_member_status', array('lastip' => $_G['clientip'], 'lastactivity' => TIMESTAMP, 'lastvisit' => TIMESTAMP), "uid='$_G[uid]'", 1);
}
}
return $updated;
}

session在程序中的流程。用户访问页面,初始化discuz_session实例;读取客户端cookie sid 查找对应的session;已存在则返回记录信息,设置cookie sid的过期时间;不存在则创建新的session记录,也就是一个新的用户,在创建新用户前,对session记录表按系统设置的用户在线时间、sid、uid、同一ip1分钟内活动记录这几个条件进行清理;(很关键的一点是只有在创建新用户session记录时,才会进行session记录的删除整理) 登陆用户与session的关系这里是一个很简单的实现,也就是

_init_session(); // 也就是$this->var[‘uid’] 是否是有值得,也就是$_G[‘uid’]是否存在。uid等用户个人信息赋值的过程是在login成功后调用setloginstatus($member,$lifetime);函数(function_member.php)

$this->session->init($this->var[‘cookie’][‘sid’], $this->var[‘clientip’], $this->var[‘uid’]);

这样用户uid就直接与session记录关联起来了。同时登陆用户会进行“个人在线时间信息”“用户个人状态”的数据更新处理。

common_session表结构

CREATE TABLE pre_common_session (
sid char(6) NOT NULL default ” COMMENT ‘sid’,
ip1 tinyint(3) unsigned NOT NULL default ‘0’ COMMENT ‘IP段’,
ip2 tinyint(3) unsigned NOT NULL default ‘0’ COMMENT ‘IP段’,
ip3 tinyint(3) unsigned NOT NULL default ‘0’ COMMENT ‘IP段’,
ip4 tinyint(3) unsigned NOT NULL default ‘0’ COMMENT ‘IP段’,
uid mediumint(8) unsigned NOT NULL default ‘0’ COMMENT ‘会员id’,
username char(15) NOT NULL default ” COMMENT ‘会员名’,
groupid smallint(6) unsigned NOT NULL default ‘0’ COMMENT ‘会员组’,
invisible tinyint(1) NOT NULL default ‘0’ COMMENT ‘是否隐身登录’,
`action` tinyint(1) unsigned NOT NULL default ‘0’ COMMENT ‘当前动作’,
lastactivity int(10) unsigned NOT NULL default ‘0’ COMMENT ‘最后活动时间’,
lastolupdate int(10) unsigned NOT NULL default ‘0’ COMMENT ‘在线时间最后更新’,
fid mediumint(8) unsigned NOT NULL default ‘0’ COMMENT ‘论坛id’,
tid mediumint(8) unsigned NOT NULL default ‘0’ COMMENT ‘主题id’,
UNIQUE KEY sid (sid),
KEY uid (uid)
) ENGINE=MEMORY DEFAULT CHARSET=utf8 COMMENT=’会员认证表’;

需要注意的是session表采用的是memory引擎,优点是常驻内存存取速度快,方便操作与管理;缺点则是如果服务器重启则数据都被清空,只读或读为主的访问模式(短时间用户激增写入量会很大)

更多了解:

MySQL Memory存储引擎:优势及性能测试

一天一笔记

上班也有三年多了,大概是从10年时候有一个对当天工作情况记录的习惯;都是记录在笔记本上面的,去年写完了一本,今年也买了一个新的笔记本,在9月份的时候突然笔没有墨水了,又多次搞忘记了买笔芯;所以转念一想为什么不自己搭建一个网上笔记记录的平台呢?

目前部署在sina sae平台上面想看的请点http://daynote.sinaapp.com

首页(图片都还是本地测试数据)

于是就计划开始做了,要最快的做出来;所以使用的是同事@ljyf5593用kohanaframework写的一个cms,后台功能很完善,用户、权限、后台工具等等都直接采用,也就是只需要做下前台以及数据的展示就ok了。

要说记笔记这样的事情,其实有很多现成的软件可用;各种云平台,云笔记等等,同事就是用的盛大麦库云笔记,如果你还想极客一点可以使用各种云平台的开放api开发一个自己的云笔记应用。不过我觉得目前自己用的这个就挺好;直接web页面访问,也不用安装各种客户端,也不想去折腾云平台api了。所以就先用着呗。

要说自己认为的亮点,我觉得有这么几个

#标记管理

不用搞成每一个note笔记前都放置一个checkbox,在建一个checkbox submit进行删除、导出、发送等其他操作。且用户点击note即选择,取消则再点击一次就ok。结果集操作nav可以随着用户的结果记录是否存在而自动的显示与隐藏。js很简单,看源码ok。

#导出,email发送

在用户选择标记后,note集合可以用来删除,导出,email发送。其中导出就是导出为excel到线下。

导出

本来想把同一天的所有不同时段内容进行分组的,不过我觉得phpexcel进行数据处理真的太难搞了。所以就直接一条笔记对应一条记录。kohana 使用phpexcel真的很方便,直接加module就好了。github上就有,开源的贡献者真伟大。

email发送

标记标签后发送到邮箱,就是着过样子啦。。。ps: title 上面已经修改好了striptags

吐槽:sae其实真的很强大了,但是也为了安全运维等因素导致sae环境与lamp环境还是有区别的。比如禁止本地io,.htaccess采用了config.yaml。所以导致一些类库代码在 sae上面跑不起来,也没有一个具体的详细错误,对于排除起来真的好麻烦。所以目前显线上sae平台上面,导出,email发送都是空白空白。。。还得找个时间单独的把phpexcel与email模块进行sae测试下,进行具体的代码排查。

=== 2013年1月4日21:34:54 更新===

phpexcel在sae上能正常数据导出了,原因是这样的,phpexcel默认是采用Excel2007进行数据导出的,所以也是采用的Excel2007实现类。而Excel2007实现类需要zip扩展来实现的(也就是zipArchive类,不过可以用zlib来实现对于zip读写,不过成本就太大了)。官方也考虑到了可能不会支持zip扩展的情况,于是就使用xml实现了PHPExcel_Settings::PCLZIP的PHPExcel_Shared_ZipArchive类,不过在sae环境上测试时提示”XMLWriter class  not found “也就是需要xmlwriter的扩展支持(PHP 5 >= 5.1.2, PECL xmlwriter >= 0.1.0) sae上面没有支持。

//writer/Excel2007.php
$zipClass = PHPExcel_Settings::getZipClass();
$objZip = new $zipClass();

本来想替换另一种实现或者是直接csv导出的,又看到phpexcel里面还可用Excel5进行数据导出;抱着试一试的态度看看,测试正常数据导出!这说明Excel5没有采用xml进行excel创建,详细还没看代码。

改为Excel5数据导出很简单


$objWriter = PHPExcel_IOFactory::createWriter($this->_spreadsheet, 'Excel5'); // 第二参数把Excel2007改为Excel5就行
$objWriter->save('php://output');
exit;

#支持markdown语法

markdown很流行,开源平台,大公司各种支持。不过markdown听闻版本分化严重啊,希望能有一个统一标准。确实很好用,如果没有一个格式体现,全是一段段文字看起来很累。https://github.com/michelf/php-markdown

#笔记note编写自动保存

这个很简单,直接setIntval 15秒自动保存,就是要区分创建与修改的动作。如果是创建则自动写入数据库,把note创建成功的noteid返回到表单,这时点击发布时实际上市修改了。如果你没有点击发布而是刷新了页面,那么这时还只是草稿,草稿数则+1;

#note笔记归档

用户笔记可以按照月份、年份进行排序实现。便于用户进行自我梳理查找。我觉得这里的作用应该大于笔记搜索;让用户可以看看自我的成长经历。个人就这挫设计,其实有更好的ui 设计,比如现在很流行的时间线。

其他还想实现的功能,比如可以让textarea支持table的(tabIndent.js)不过有时候我经常在touch,移动设备上面写,table这时现的很多余所以暂时没有加上去。还有同步发送到微博,不过貌似笔记是比较私密的事情,当然也有可以公开的暂时也没有加。

有喜欢的可以注册用下哟。

一个有关当前时间的sql排序实现

在项目中遇到了这样的一个需求

“在用户参与的一个活动列表中,获取当前时间中刚刚开始了(也就是刚才开始的,不是最先开始)的一条活动;如果当前时间没有活动开始,则获取将要开始的一条活动记录”且必须在一次数据库查询操作中完成。

也就是必须要在数据库中做活动的状态区分。

现在具象化一点的理解这个需求,有这样一个表t_time

里面只有三个字段

id,starttime,endtime

我们假定现在每一条记录就是一个活动。

现在可以把需求抽象的分析为“当前时间大于starttime且小于endtime的按desc到序排列获取第一条记录,当前时间小于starttime且小于endtime的按asc正序排列获取第一条记录”且同样是要在一条sql语句中完成。

于是开始进行sql编写,在sql要完成两种状态的辨别,那就肯定是需要条件分支类似的语句控制。

mysql中的条件语句分支,常用的就是if ,case when 语句。

在找到if then 的用法后,开始第一次if then语句的测试


if (SELECT * FROM `t_time` where starttime < UNIX_TIMESTAMP() and endtime >UNIX_TIMESTAMP())
then ( SELECT * FROM `t_time` where starttime < UNIX_TIMESTAMP() and endtime >UNIX_TIMESTAMP() order by starttime desc )
else if(SELECT * FROM t_time WHERE starttime > UNIX_TIMESTAMP() and endtime > UNIX_TIMESTAMP())
then ( SELECT * FROM `t_time` where starttime > UNIX_TIMESTAMP() and endtime >UNIX_TIMESTAMP() order by starttime asc )
end if;

结果是;华丽丽的报错啊!!!

Err] 1064 – You have an error in your SQL syntax;

考虑到语法没有问题啊。mysql真是有待学习啊,在网上搜索后了解到“if then endif只能在procedure或是function里用”,手册也没看,就乱来。而且不能为了这么一个处理还单独去创建一个存储过程或函数。

 

再查看case when语句,这次是能在sql中直接执行的。


select * from t_time where endtime > UNIX_TIMESTAMP()
and (case when starttime < UNIX_TIMESTAMP() then starttime < UNIX_TIMESTAMP() else starttime > UNIX_TIMESTAMP() end) order by starttime desc;

结果

但是还是没有达到预期效果,case when 没有起到作用,加上case when语句别名后的结果

可以看到case when 返回的是boolean结果;再次无效

 

于是继续查看手册还有没有办法,if()函数也能进行分支判断,可以一试。

IF(expr1,expr2,expr3)
如果 expr1 是TRUE (expr1 <> 0 and expr1 <> NULL),则 IF()的返回值为expr2; 否则返回值则为 expr3。IF() 的返回值为数字值或字符串值,具体情况视其所在语境而定。

if()函数可以返回数值或者是字符


SELECT *,if(starttime < UNIX_TIMESTAMP(), 'starttime < UNIX_TIMESTAMP()', 'starttime > UNIX_TIMESTAMP()') as t
FROM `t_time` where endtime > UNIX_TIMESTAMP() and t order by starttime desc;

 

 

结果还是语法错误,[Err] 1054 – Unknown column ‘t’ in ‘where clause’

把 and t去掉;sql语句是能正常执行的。结果如下

if()函数确实是执行了,但是返回的字符 别名为t,sql语句还是不认识。这个没搞明白。
无奈,只好求教于公司同事w;在简单说明后,他说项目里面有一处地方用到了这个,已经有了一个sql语句实现了;原来这个项目里面已经处理过这样的业务需求了,他说那时候第一次研究出来的,因为这个项目我是半路进来的,前面的开发都没参与;但隐约记得那一段时间他们组里的人都在说“写出一个牛x的sql排序了”,不过因为各种原因,我没去了解。哎,总归还是遇上了。

于是请w去讲解下这个处理方式。

在查看到项目源代码后,他说简而言之就是一个排序的实现,sql里面还是要进行两个状态的判断;

就是通过if()函数构建一个线性的排序


SELECT *,if( starttime < UNIX_TIMESTAMP(), starttime - UNIX_TIMESTAMP(), (starttime - UNIX_TIMESTAMP()) + 2000000) as t
FROM `t_time` where endtime > UNIX_TIMESTAMP() order by t asc;

分析if()函数的作用。

为true
也就是已经开始记录的排序,执行 starttime – UNIX_TIMESTAMP() 按得到的值;starttime asc排序这样就得到了“最先开始的活动记录,也就是按starttime asc排序”列表
为false
执行 (starttime – UNIX_TIMESTAMP())+20000000
starttime – UNIX_TIMESTAMP()这样得到了 “starttime距离现在最近的,也就是马上将要开始的starttime”记录列表;由于 “未开始记录的”starttime – UNIX_TIMESTAMP() 得到的值可能小于“已开始记录的”starttime – UNIX_TIMESTAMP() 的值,这种情况下排序就混乱了,所以要加上一个很大的值20000000避免这种情况,这样就能保证未开始记录的starttime永远大于已开始记录的 starttime值,这样未开始的值记录排在了已开始记录之后了。

( 图上的时间是假定的,就是为了说明已经开始的记录是哪几条。其实执行的时间也是算得出来的:-) )

这里我们的需求就快要实现了,可以看到已开始记录是按starttime  asc排序的。也就是得到的是“最先开始的活动记录”,而我们要的是“刚刚开始的活动记录”,实现这个其实很简单了

starttime < UNIX_TIMESTAMP() ,那么starttime – UNIX_TIMESTAMP() 则是一定为负数的;starttime越大,得出的绝对值越小。反之starttime越小,得出的绝对值则越大,但加上负号,则是starttime越小的,得出的负值越小了,最后sql语句都是asc排序的。结果集里面已开始的记录也就是asc排序的。

 

所以如果要starttime 正序desc排序,那就是 UNIX_TIMESTAMP() – starttime 表达式了,starttime越大的,得到的值越小;starttime越小的,得到值越大;因为最后sql语句是asc排序,结果集里面已开始的记录从排列上来说是desc倒序的。


SELECT *,if( starttime < UNIX_TIMESTAMP(), UNIX_TIMESTAMP() - starttime, (starttime - UNIX_TIMESTAMP()) + 20000000) as t
FROM `t_time` where endtime > UNIX_TIMESTAMP() order by t asc;

可以看到id为2的记录排到第一了。

 

这个业务需求在与当前时间有关且记录是否过期进行排序的列表中常应用到,一次请求后数据处理过程中;可以用endtime与当前时间进行对比,对于已过期的记录进行标记,这样显示时可以很明了了。

 

这个之前在ppc上面提问题了的,查看可以点击这里

 

Apache Solr使用简介

在一个项目中需要使用全文检索功能,架构w推荐了Apache Solr一个独立的企业级别搜索服务器。于是就顺势了解下这个solr搜索服务器。

简介

官网地址:http://lucene.apache.org/solr/

solr是基于lucene的一个搜索服务器,lucene也是Apache的一个开源项目;

Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,即它不是一个完整的全文检

索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。

Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起

完整的全文检索引擎。–来自百度百科

可以简单的说Lucene就是一个搜索引擎架构,而Solr则是一个搜索服务器的实现。

solr是使用java开发实现,目前已经到4.0版本了,运行需要java1.6或以上版本;基于http get进行xml/json/cvs/binary等格式类型的数据传输。solr的数据都是以document对象进行存储,每一个document都由许多field组成,且每一个document都有一个默认的uniqueid,默认的是id。

solr的安装很简单,下载完成后解压,进入solr/example目录

user:~/solr/example java -jar start.jar

输入http://ip:8983/solr/即可看到solr服务器的管理界面。

solr需要一个java服务器,solr服务器里面自带了jetty服务器所以不需要另立服务器,如果有tomcat也可以直接新开一个端口用来运行。

Dashboard是solr服务器的搭建路径,版本,运行环境,机器的物理内存,File Descriptor Count ,jvm内存使用情况等

Logging 则是运行的记录,包括错误,警告等

core admin则是 Schema 的管理界面,可以查看某个Schema的情况,同事还能新建,删除,优化等

java properties 即运行solr的java的所有详细参数

thread dump 运行线程的详细情况,各种数据跟踪

collection1 则是建立的Schema,可以查看Schema的记录数,版本数;还有如ping,query,config等管理操作

solr与php的结合实现

php的solr接口实现已现有代码实现了。项目地址:http://code.google.com/p/solr-php-client/

不过这里需要注意的是因为solr现在是4.0版本了,该php client实现里面还有几个需要注意的,如service.php 中实现add接口的addDocument方法,4.0版本的add属性参数已有改变,废弃了其中的allowDups,overwritePending,overwriteCommitted这几个参数所以需要将这几个参数去掉,否则solr服务器会报错。

一个很简单的例子

<?php

require_once 'Apache/Solr/Service.php';

$transportIntance = new Apache_Solr_HttpTransport_Curl();
//$transportIntance = new Apache_Solr_HttpTransport_CurlNoReuse();
// 如果这里有不同的Schema如ASchema与BSchema,则传入的第三个参数就是'/solr/ASchema/'与'/solr/BSchema/'
$solr = new Apache_Solr_Service('192.168.1.10', '8983', '/solr/');

// 检测solr服务是否可用
if($solr->ping()){
$solr->deleteByQuery('*:*');

//
// Create two documents to represent two auto parts.
// In practice, documents would likely be assembled from a
//   database query.
// 这里需要注意的是,使用的field必须存在于Schema中,否则document写入不到solr服务器,且检索时会产生
  // org.apache.solr.common.SolrException: ERROR: [doc=50cdce141f792] unknown field 'partno'的错误
  // 这是需要将partno等schema里面没有的field全部都加入到apache-solr-4.0.0/example/solr/collection1/conf/schema.xml <fields>里面
  // 写入到schema的 field 需要注意name与attribute,详细请看http://wiki.apache.org/solr/SchemaXml
  // 保存完成后重启服务器
$parts = array(
'spark_plug' => array(
'partno' => 1,
'name' => 'Spark plugdw',
//      'model' => array( 'Boxster', '924' ),
'year' => array( 1999, 2000 ),
'price' => 25.00,
'inStock' => true,
),
'windshield' => array(
'partno' => 2,
'name' => 'Windshield',
//      'model' => array('911', '111'), //multiple values encountered for non multiValued field model:
'year' => array( 1999, 2000 ),
'price' => 15.00,
'inStock' => false,
)
);

$documents = array();

foreach ( $parts as $item => $fields ) {
$part = new Apache_Solr_Document();
$part->id = uniqid();
foreach ( $fields as $key => $value ) {
if ( is_array( $value ) ) {
foreach ( $value as $datum ) {
$part->setMultiValue( $key, $datum );
}
}
else {
$part->$key = $value;
}
}

$documents[] = $part;
}

//var_dump($documents);

// Load the documents into the index

try {
$solr->addDocuments( $documents );
    // 这里需要注意在加入document后必须要commit提交到检索索引里面,否则最新写入的内容不会被检索到
$solr->commit();
   // optimize索引优化这里会重建索引,每次adddocument时不必要进行,应该放到用户搜索很少的时刻定时执行
   // 如每周末的24点执行,crontab也是一个选择
$solr->optimize();
}
catch ( Exception $e ) {
echo $e->getMessage();
}

// Run some queries. Provide the raw path, a starting offset
//   for result documents, and the maximum number of result
//   documents to return. You can also use a fourth parameter
//   to control how results are sorted and highlighted,
//   among other options.
//
$offset = 0;
$limit = 10;

$queries = array(
'partno:1',
'name:*w*'
);

foreach ( $queries as $query ) {
$response = $solr->search( $query, $offset, $limit );

if ( $response->getHttpStatus() == 200 ) {
// print_r( $response->getRawResponse() );

if ( $response->response->numFound > 0 ) {
echo "$query <br /><br/>";

foreach ( $response->response->docs as $doc ) {
echo "$doc->partno $doc->name <br />";
}

echo '<br />';
}
}
else {
echo $response->getHttpStatusMessage();
}
}

}else {
die('no connection solr service...');
}

?>

solr的query语法

根据不同的需要进行选择。

/**
* query语法
* 查询语法 http://lucene.apache.org/core/3_6_0/queryparsersyntax.html
* 1) title:”The Right Way” AND text:go // 标题的值匹配,AND text:go 表示对title默认的值为go的项进行筛选,但不是必须的
* 2) mod_date:[20020101 TO 20030101] //mod_date日期在20020101与20030101之间的
* 3) title:{Aida TO Carmen}     // 标题在Aida 与Carmen之间的集合,但不包括Aida Carmen
* 4) content:”jakarta apache” AND “Apache Lucene” // 搜索的字段必须包含 jakarta apache与Apache Lucene;可以使用&&代替AND
* 5) content:+jakarta lucene     // 必须包含jakarta 或可能包含lucene
* 6) content:”jakarta apache” jakarta // 结果中包含jakarta apache或jakarta ;或使用content:”jakarta apache” OR jakarta。可以使用||代替OR
* 7) content:”jakarta apache” NOT “Apache Lucene” // 包含jakarta apache,但不包含Apache Lucene;可以使用! 代替NOT
* 8) content:”jakarta apache” -“Apache Lucene” // 包含jakarta apache,但不包括Apache Lucene;与NOT区别还没明白
* 9) content:(jakarta OR apache) AND website // 使用括号实现子查询,包含jakarta OR apache以及website
* 10) title:(+return +”pink panther”)        // 使用括号将多个子句合并为一个字段,标题中必须包含单词return以及短语”pink panther”
* 11) title:tes? // 使用? * 来实现内容的搜索
* 12) 支持转移字符的查询,需转移字符包括+ – && || ! ( ) { } [ ] ^ ” ~ * ? : \ 可以使用\来转义字符,如title:\(1\+1\)\:2
* 13) title:roam~ // ~表示模糊查询,romam~将查找到foam或roams得单词;roam~0.8表示将得到roam 80%的相似度单词
* 14) content:”jakarta apache”~10 // 临近搜索,检索相隔10个单词的”apache”和”jakarta”
*/

solr的搜索是自动进行分词的,当然这是指英文的空格分词;如果中文需要分词怎么办呢?当然也有有心人已经做出来了,就是这个mmseg4j 作者博客上的简介 作者对于lucene solr很有研究,值得一看。

更多阅读

Solr Tutorial

Apache Solr Wiki

淘宝网综合业务平台团队博客 solr文章

Lucene/Solr Optimize相关总结

Solr监控

js顺序执行与input吸引浏览器焦点的体现

在电子商务的充值、支付等动作时;用户确认订单后,执行提交动作,我们把这个页面称为“订单提交页面”,订单打开一个新的页面跳转至支付平台进行订单的处理,这个页面即为“支付平台处理页面”,而订单提交页面则显示一个遮罩层,提示用户在新页面完成订单且有“已完成支付”与“支付出现问题”两个按钮。

原型图片xx

而现在我就是要实现这样的一个业务流程。待整个业务流程完成后,使用firefox进行测试充值等,一切良好(当然架构,还有开发都没那么快与容易,详细可以再写一篇了)。完成后内部测试,使用各种浏览器,firefox,chrome,ie,360等浏览器进行操作。同事Y提醒在使用ie进行购买时,业务上也没有特别大的问题,但就是在进行确认订单提交动作后,form新页面打开,浏览器焦点在“支付平台处理页面”上面,不过这时“支付平台处理页面”突然最小化失去浏览器焦点;浏览器焦点又再次回到“订单提交页面”的弹出层上面。

很显然功能确实不受影响能正常使用,但是对于用户的体验不好,用户在完成订单提交后,浏览器焦点应该直接在“支付平台处理页面”上,用户可以直接进行支付平台处理操作。而现在用户则必须再次点击“支付平台处理页面”重新获取浏览器的焦点。

只能再次检查代码,这一流程使用的是很简单很原始的逻辑。下面的代码可以直接在IE8及其以下的浏览器(浏览器新打开页面必须是在新窗口的形式,如果是以新标签打开页面则没有这样的问题)中打开点击submit按钮即可出现这样的问题。


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>input test</title>

<script language="javascript" src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
<script language="javascript" src="http://malsup.github.com/jquery.blockUI.js"></script>
<style type="text/css">

.loginform { width: 400px; height: 200px; border: 1px solid #ccc; margin: 0 auto;}
.message { width: 400px; height: 160px; border: 2px solid #ccc;  display: none; background:#fff;}
.message .i{ width: 80px; height: 20px; margin:10px; display:block; border: 1px solid #ccc;}
h4 { text-align: center; }
</style>
</head>

<body>

<div>
<form action="http://baidu.com" target="_blank" id="goform">
<h4>username:<input type="text" name="name" /></h4>
<h4>password:<input type="text" name="pwd" /></h4>
<h4><input type="button" id="go" name="go" value="submit" /></h4>
</form>

</div>

<div id="message">
<h4>提示信息</h4>
<h4><input type="button" value="ok" /><input type="button" value="no" /></h4>
<!--- <h4><a>ok</a><a>no</a></h4>--> <!-- 解决方法-->
</div>

<script language="javascript">

$(document).ready(function() {

$('#go').click(function(){

$.blockUI({
message: $("#message"),
timeout: false,
onUnblock: false,
css: {
top: '20%',
left: ($(window).width()-400)/2,
cursor: 'pointer',
margin: '0 auto',
background: 'none',
border: 'none'}
});
$('.blockUI').attr('title','点击关闭').click( $.unblockUI );
$('#goform').submit();
});
});
</script>

</body>
</html>

订单form提交的动作主要就是由

$(‘#go’).click(function(){
// 遮罩处理

// 表单提交
$(‘#goform’).submit();
});

这样的代码完成的,以js至上而下的执行流程。首先完成遮罩的处理,然后进行form订单的提交动作。左思右想应该是没什么问题的啊,但是还是如此。

于是开始考虑延时执行form表单提交的动作,意思就是让遮罩处理的动作完成后,在过几秒进行执行。

setTimeout(function(){$(‘#goform’).submit();  }, 10);

再次执行,弹出层正常执行,但因为IE提示安全警告拦截了新页面的弹出。因为IE不认为表单的提交是用户的手动操作,如要顺利弹出需要用户将该站加入允许弹出新页面的列表,这样更加不可取。

进而开始考虑实现浏览器的sleep,即休眠暂停当前线程;确实真的找到了有人实现sleep的东西,这里查看 。不过sleep的核心实现都是循环执行其他代码或者是指定一个时间如果时间到了,则跳出

<script type="text/javascript">
function sleep(milliSeconds){
var startTime = new Date().getTime(); // get the current time
while(new Date().getTime() < startTime + milliSeconds); // hog cpu
}
</script>

不过上面的代码其实并没有让脚本解释器sleep下来,而且有让CPU迅速上到高负荷的附作用。浏览器甚至会在该段时间内处于假死状态。不可取。

在迷惑了半天还是不知道为什么,于是只好请教大牛同事w了。问题简要的说明后,w看过代码后表示按顺序执行是完全没问题的,也进入了我之前的思维,使用setTimeout,更改动作逻辑。同样行不通,最后w注意到弹出层的内容,看到里面有两个input标签;于是就是直接把它改为a标签。再次尝试果然就好了,w说input会自动获取焦点的,而a标签则不会。

在把input替换为a标签后,没有出现这样的情况了。这是为什么呢。在对网页用table选择不同标签时,页面标签都能正常获取到焦点。浏览器加载网页时a与input的焦点获取原理是怎样的?

discuzx2系列之群组积分排行

很长一段时间内都在忙着一个用discuzX2二次开发的项目。刚开始接触discuzx,查看里面的代码的时候感觉就像渣渣一样。虽然功能很强大,但是觉得代码真是太混乱了。不过随着后来项目定制的深入以及慢慢对discuzx的了解,discuzx真的很强大。尽管有些代码不是很规范,比如全局变量的滥用,函数里面require文件,输入数据的检测,模板里面的逻辑混乱可阅读性不高,整体架构只是类mvc还是很传统的函数编程等等还有很多。不过discuzx确实是一款成功的论坛类cms的系统,是一款很好的产品,可扩展性高,数据承载量大,功能强大,快速高效。现在就介绍下遇到的问题以及对这些问题的处理方法。

discuzx的资料:

discuzx的技术文库 http://dev.discuz.org/wiki

discuzx主站 http://www.discuz.net/

现在discuzx已经更新到2.5了。听说3.0现在正在研发中,2.5版本的整体架构变更比较大,很多之前直接写sql的以及用函数的地方都使用类库来替代了,不过代码还没有细看详细了解自行看源码。

这次要记录的内容是discuzx群组模块里面的一个功能扩展,这个功能就是群组的积分排行以及群组的当月积分排行。

可以看出群组排行可以按两项排行,一是总积分排行,二是当月积分排行。

一群组总积分排行,这个其实很简单。

我们知道用户创建的群组数据都存储在forum_forum表中,我们通过查看数据字典可以很轻松的知道该表中的字段所代表的意义。

如上图commoncredits就是“群组公共积分”,那么现在就可以很轻松的按这个字段的值进行desc排序了。不过群组的公共积分是怎样计算的呢?下面在详细了解。

二群组当月积分的总排行

这个会比较麻烦,因为discuzx木有这个功能啊(侧面说明discuzx很强大,很多功能都不需要进行改动直接拿来用)。不过我们可以马上进行扩展。

首先就是要搞明白群组的“公共积分”是怎么得来的,不管是总积分排行还是当月积分排行,数据更新的来源都是同一处。不过现在无处下手不知道积分增加的代码在哪,不过积分增加一定是与用户在群组内的操作有关的,比如发帖,回复,回帖等。所以只好监控表数据,通过回帖操作,发现总积分commoncredits数据+1,不过第二次操作时数据就没有变化了,那就说明回帖的时候一定进行了数据的更新。

可以查找到帖子回复的代码文件是source/include/post/post_newreply.php

不过再次查找就是麻烦了,一段一段的读代码时间也太长了,所以简单的办法来了,直接source搜索commoncredits,commoncredits数据变更一定是有“commoncredits=commoncredits+1”的sql语句了,于是搜之就出现结果了,就是function_grouplog.php这个文件里面,只有这一个函数。然后再用“updategroupcreditlog”在post_newreply.php里面搜索是在哪调用的就知道了

我们还可以具体了解到updategroupcreditlog是在哪些的动作中调用的。

可以看到发帖、回帖、发布商品动作都会对群组的公共积分进行增加。

那么接下来要做的事就是了解updategroupcreditlog函数了。

查看源代码可以了解到这个函数对于增加积分的行为。使用cookie可以避免用户每次回帖或发帖都检测数据库,同时在服务器端forum_groupcreditslog中保存用户今天在该论坛是否回帖的数据是很有必要的。因为这样可以规避用户更改客户端时间或删除cookie信息来为增加群组的积分。接下来就是通过检查$_G是否存在当前群组的信息;然后再检测群组的等级,判断群组是否足够积分进行升级。


<?php

/**
 *      [Discuz!] (C)2001-2099 Comsenz Inc.
 *      This is NOT a freeware, use is subject to license terms
 *
 *      $Id: function_grouplog.php 16644 2010-09-11 03:33:30Z monkey $
 */

if(!defined('IN_DISCUZ')) {
 exit('Access Denied');
 }

function updategroupcreditlog($fid, $uid) {
 global $_G;
 if(empty($fid) || empty($uid)) {
 return false;
 }
 $today = date('Ymd', TIMESTAMP);
 $month = date('Ym', TIMESTAMP);

$updategroupcredit = getcookie('groupcredit_'.$fid);
 if($updategroupcredit < $today) {
 $status = DB::result_first("SELECT logdate FROM ".DB::table('forum_groupcreditslog')." WHERE fid='$fid' AND uid='$uid' AND logdate='$today'");
 if(empty($status)) {

DB::query("UPDATE ".DB::table('forum_forum')." SET commoncredits=commoncredits+1 WHERE fid='$fid'");
 DB::query("REPLACE INTO ".DB::table('forum_groupcreditslog')." (fid, uid, logdate) VALUES ('$fid', '$uid', '$today')");
 if(empty($_G['forum']) || empty($_G['forum']['level'])) {
 $forum = DB::fetch_first("SELECT name, level, commoncredits FROM ".DB::table('forum_forum')." WHERE fid='$fid'");
 } else {
 $_G['forum']['commoncredits'] ++;
 $forum = &$_G['forum'];
 }
 if(empty($_G['grouplevels'])) {
 loadcache('grouplevels');
 }
 $grouplevel = $_G['grouplevels'][$forum['level']];

if($grouplevel['type'] == 'default' && !($forum['commoncredits'] >= $grouplevel['creditshigher'] && $forum['commoncredits'] < $grouplevel['creditslower'])) {
 $levelid = DB::result_first("SELECT levelid FROM ".DB::table('forum_grouplevel')." WHERE type='default' AND creditshigher<='$forum[commoncredits]' AND creditslower>'$forum[commoncredits]' LIMIT 1");
 if(!empty($levelid)) {
 DB::query("UPDATE ".DB::table('forum_forum')." SET level='$levelid' WHERE fid='$fid'");
 $groupfounderuid = DB::result_first("SELECT founderuid FROM ".DB::table('forum_forumfield')." WHERE fid='$fid' LIMIT 1");
 notification_add($groupfounderuid, 'system', 'grouplevel_update', array(
 'groupname' => '<a href="forum.php?mod=group&fid='.$fid.'">'.$forum['name'].'</a>',
 'newlevel' => $_G['grouplevels'][$levelid]['leveltitle']
 ));
 }
 }
// 当月积分排行的处理
// 当前月份财富记录判断
 $monthstatus = DB::result_first("SELECT *  FROM ".DB::table('forum_groupmonthcredits')." WHERE fid='$fid' AND month='$month'");
 // 已存在当月财富记录
 if( $monthstatus){
 DB::query("UPDATE ".DB::table('forum_forum')." SET monthcommoncredits=monthcommoncredits+1 WHERE fid='$fid'");
 DB::query("UPDATE ".DB::table('forum_groupmonthcredits')." SET credit=credit+1 WHERE fid='$fid' AND month = {$month}");
 }else {
 // 需创建新的记录
 DB::query("INSERT INTO ".DB::table('forum_groupmonthcredits')." (fid, month, credit) VALUES ('$fid', '$month', '1')");
 DB::query("UPDATE ".DB::table('forum_forum')." SET monthcommoncredits=1,creditmonth = {$month} WHERE fid='$fid'");
 }

}
 dsetcookie('groupcredit_'.$fid, $today, 86400);
 }
 }


那么我们的目的明确了,接下来就是要分析处理了。

记录群组每月的增长财富值,并按当前月份的财富值进行排行。

群组表forum_forum新加入字段
monthcommoncredits // 当前月份财富值,积分值
creditmonth          // 财富值的当前月份,便于群组排序是与当前月份进行匹配

第一步
用户每天发帖,群组财富积分会增长。

第二部
借此机会可以实现每月的财富积分记录
新建一个数据表“群组每月的财富积分记录”

groupmonthcredits
fid  // 群组id
month // 月份
credit // 当月积分
可以用来记录统计某个群组的每个月的积分,对应可以扩展实现群组的当月积分趋势
普通用户if(empty($status)) 今天第一次进行发帖时
群组总积分+1
判断 groupmonthcredits 表的当前月份的财富积分记录是否存在?
如存在 则当前月份财富积分 credit 继续+1,群组表中的公共积分 monthcommoncredits 继续+1
否则不存在,说明现在进入新的月份,在 groupmonthcredits 创建新的当前月财富积分记录,credit默认为1(因为用户今天在群组内的第一次操作有财富积分+1奖励)。
同时forum_forum表中的当月财富积分 monthcommoncredits 更新为1,将当前财富月份 creditmonth 更新为当前的月份
第三步
群组按当前月份财富额排行,可以直接使用群组表 forum_forum 中的monthcommoncredits 进行desc排序。
但这里会存在一个bug,就是某群组8月的“当月财富积分”为1000,排名第一,这时候进入9月
但群组中的任何一位成员都没有进行发帖、回复等操作。于是该群组的财富积分还是 1000 ,还是高居榜首,原因就是第二步中的所有当月财富积分的判断都需要在用户进行发帖,回复等操作下进行处理。这时候要解决这个问题就需要用到 creditmonth 财富值月份字段了。只查询与当前月份匹配的群组记录

这样就可以避免了。尽管这样的可能性比较小,但是还需要做考虑。
对于财富积分的增长,始终放在用户手中。

[转]网页中自定义鼠标样式css cursor属性

原文地址:http://qianxuechao.blog.163.com/blog/static/9181349920112309638218/

今天突然用到cursor属性来自定义用户的鼠标手势,cursor:url(shared/mousedown.cur),pointer; 在firefox 与chrome中都能正常显示,但是ie系列都不行。

搜到一篇文章解释说明ie对cursor不认识相对路径的cur文件,必须要用相对根目录或者是绝对路径才能显示。即cursor:url(/common/skin/shared/mousedown.cur),pointer; 或 cursor:url(“http://www.xx.com/images/skin/mousedown.cur”), pointer;这样。

 

 

属性的值可以是多个,其间用逗号分隔。假如第一个值不可以被客户端系统理解或所指定的光标无法找到及显示,则第二个值将被尝试使用。依此类推。假如全部值都不可用的话,则此属性不会发生作用。光标不会被改变。

标准写法:
cursor: url(images/pen_il.cur),auto; /* windows系统的光标文件存放在 C:\WINDOWS\Cursors */
cursor: url(mouse-notexist.cur), url(vote.gif), auto;

也可:
cursor:url(images/logo_baidu.gif) 100 20,auto; /* 100 20 是cursor 的坐标*/

FF支持jpg、gif、cur等文件,不支持.ani
IE则只支持cur、ani

有的时候我们会用到自定义鼠标指针cursor,当我们将做好了的cur图标放在图片文件夹下面,在CSS文件中写上如下代码:

.setMouse{cursor:url("../images/skin/mouse.cur"), auto;}

却发现图标在IE浏览器中没有正常显示。经过测试,原因找到了,IE不支持在CSS文件中写相对路径的cursor。
解决办法很简单,改成相对根路径:

.setMouse{cursor:url("/images/skin/mouse.cur"), auto;}

或者是绝对路径:

.setMouse{cursor:url("http://www.vpedi.com/images/skin/mouse.cur"), auto;}

一些事情

早上9点闹钟响了,尽管昨天晚上决定今天早起,睡到9点就够了于是定下闹钟,不过被吵醒的我果断的有按下继续睡觉,再次醒来已是11.19了。而今天是11.17了。

说起来就快12月了,年底了。最近事情还是比较多啊,半死不活的一个项目又重新开始改了,合作近两年的同事昨天离职了,与其他同事的关系一般一般还可能有点坏,听闻表哥在家订婚了年底就结婚,以前一个同事邀我去做事。。。

时间好像在年初与近年底的时候感觉特别快,快年底了要有点料给自己个交代,但现在呢没有一个达到目标;个人不好习惯呢。从小时候就开始,就像在高考前三个月紧张了,肚里没货,但就要交差了。

患得患失,不活在当下。

昨天与同事一起坐车去附近工大路的小四川,离职的同事L请大伙吃饭,不过也不是全部人都到,每个人人总是有这些那些事的。我问一起坐车的搞IOS开发的同事X,“目前这个公司是你工作的第几个公司了?”,“第三个啊”,我又问“你是今年来的吧”,他答“是啊,5月份来的”,“时间过的好快”,我又感叹了下,X没答话,我问他“我感觉今年什么事情都没有做,就到年底了;你今年做了哪些事啊”,他还是没有答话,我又笑着说“不过你今年才到这儿来,你今年算是一个时间节点,至少有这件事情”。X笑了笑。而我也就结束了这没营养的对话。

 

吃饭还是热热闹闹,白酒橙汁一起来,继续哄哄闹闹的喝酒吃菜,讲些趣事或者故意劝酒抑或假意的挖苦讽刺某位同事。好不热闹。喝到差不多,饭快吃完了。产品同事S突然端起一杯水站起来对我们说了大意就是今天是同事L离职,但是离职也只是人离开了,但是感情还是真正留在这里,留在每个人的心里、留在这一杯杯的酒里。尽管他在讲之前也自嘲的说不怕你们说我矫情,说我装。但是我想说哥们你还真没装,有些话或者我们是羞于表达或者不屑与表达,但不如你这么端着酒杯映着红通的脸说出来的爽快。你对L敬酒的时候也说了一句“只有L对我的态度是最好的”,马上被同事一句“你说出一个最好,那就必须说出一个最坏的”,S知道说的过于直白了,笑笑也不答话,只是饮酒。其实他说的也没错,确实如此。S到公司工作恰好满一年,去年的11月份我、L、S合作做了一个小项目。那是S第一次做产品,我估计流程都没怎么搞清楚吧,做错了一些需求,以至于可能会修改程序乃至重做。我就没给他好脸色看,L还是一脸和气没说什么。

 

L早我2个月进入公司,那时我刚到公司,新环境你就是菜鸟误以为你是老员工,跑过去问你一个公司框架的问题,你答我其实也是刚来的,实习的。哦,我哦了一声,还是问了他一些事情。L确实是来实习的,属于半路出家,他之前也是学计算机方面的,而且之前一个工作是一个肥差,主要移动的基站架设方面的事情。一个月工作5、6k,不过他说那种工作一点技术含量都没有,他想有点做点有技术的事情。工作中确实表现的是实习的状态,不过L很好学,进步的很快,恰巧公司的项目那段时间也比较多,他都有参与到,经常问我一些问题,什么都有前端后端、切图等等。到了7月份我们开始第一次合作一个项目,很多事情他直接一个人在做,到后来我也开始写后端了,讨论了很多逻辑问题,解决方案等,尽管客户真的很极品,不过我们合作的还算是比较顺利的。当然L的进步也是很快,很多时候可以直接自己一个人处理问题了。到后来你直接专研到kohana框架里面去了,不得不说现在谁都没有你熟悉这个了,你还用这个创建了一个CMS,把它托管到了github上面,现在公司的一个项目已经用到这个CMS了,你也是主要开发人员之一,尽管现在你离开了,详细原因也不便过问,但总是有你自己的理由。而我说不出又不善于表达出像S说的那些话,我只能端着酒向邻座的你说“反正也fork了你的cms项目,有时间就加点新的功能吧”

 

而我现在不能继续混沌着了,事情有好有怀,多而繁杂。或许某一天我也会离开这里,不过既然在这里就做好每一件事情。时间永远是向前行进的,不用管年底或年初,不用管是周末或上班日,好好做好自己的事情,继续学习。

 

今天姚晨大婚,恭喜恭喜!一直很欣赏的好公民好演员。借用她的微博勉励你我。

姚晨很长一段时间,我总觉得生命慎始。


但得而不待,时不再来。生命无关途中际遇,兀自向前。

终于明白,幸福不在终点,幸福就是此生此路。
请珍视每一寸光阴,珍视每一个共同渡过的人。相信同伴就是一种珍贵。—Alfred D’Souza

 

 

 

[转]Apache的Order Allow,Deny 详解

原文地址:http://www.cnblogs.com/top5/archive/2009/09/22/1571709.html

对于.htaccess的用法不胜了解,常用的重定向、规则重写、ErrorDocument也都是复制粘贴了。对于一些files filesmatch ,Order Deny,Allow的使用不是很明白。转载一篇自我学习。

 

Allow和Deny可以用于apache的conf文件或者.htaccess文件中(配合Directory, Location, Files等),用来控制目录和文件的访问授权。

所以,最常用的是:
Order Deny,Allow
Allow from All

注意“Deny,Allow”中间只有一个逗号,也只能有一个逗号,有空格都会出错;单词的大小写不限。上面设定的含义是先设定“先检查禁 止设定,没有禁止的全部允许”,而第二句没有Deny,也就是没有禁止访问的设定,直接就是允许所有访问了。这个主要是用来确保或者覆盖上级目录的设置, 开放所有内容的访问权。

按照上面的解释,下面的设定是无条件禁止访问:
Order Allow,Deny
Deny from All

如果要禁止部分内容的访问,其他的全部开放:
Order Deny,Allow
Deny from ip1 ip2
或者
Order Allow,Deny
Allow from all
Deny from ip1 ip2

apache会按照order决定最后使用哪一条规则,比如上面的第二种方式,虽然第二句allow允许了访问,但由于在order中 allow不是最后规则,因此还需要看有没有deny规则,于是到了第三句,符合ip1和ip2的访问就被禁止了。注意,order决定的“最后”规则非 常重要,下面是两个错误的例子和改正方式:

Order Deny,Allow
Allow from all
Deny from domain.org
错误:想禁止来自domain.org的访问,但是deny不是最后规则,apache在处理到第二句allow的时候就已经匹配成功,根本就不会去看第三句。
解决方法:Order Allow,Deny,后面两句不动,即可。

Order Allow,Deny
Allow from ip1
Deny from all
错误:想只允许来自ip1的访问,但是,虽然第二句中设定了allow规则,由于order中deny在后,所以会以第三句deny为准,而第三句的范围中又明显包含了ip1(all include ip1),所以所有的访问都被禁止了。
解决方法一:直接去掉第三句。
解决方法二:
Order Deny,Allow
Deny from all
Allow from ip1

 

下面是测试过的例子:
——————————–
Order deny,allow
allow from all
deny from 219.204.253.8
#全部都可以通行
——————————-
Order deny,allow
deny from 219.204.253.8
allow from all
#全部都可以通行
——————————-
Order allow,deny
deny from 219.204.253.8
allow from all
#只有219.204.253.8不能通行
——————————-
Order allow,deny
allow from all
deny from 219.204.253.8
#只有219.204.253.8不能通行
——————————-
——————————-
Order allow,deny
deny from all
allow from 219.204.253.8
#全部都不能通行
——————————-
Order allow,deny
allow from 219.204.253.8
deny from all
#全部都不能通行
——————————-
Order deny,allow
allow from 219.204.253.8
deny from all
#只允许219.204.253.8通行
——————————-
Order deny,allow
deny from all
allow from 219.204.253.8
#只允许219.204.253.8通行
——————————-
——————————–
Order deny,allow
#全部都可以通行(默认的)
——————————-
Order allow,deny
#全部都不能通行(默认的)
——————————-
Order allow,deny
deny from all
#全部都不能通行
——————————-
Order deny,allow
deny from all
#全部都不能通行
——————————-
对于上面两种情况,如果换成allow from all,则全部都可以通行!
——————————-
Order deny,allow
deny from 219.204.253.8
#只有219.204.253.8不能通行
——————————-
Order allow,deny
deny from 219.204.253.8
#全部都不能通行
——————————-
Order allow,deny
allow from 219.204.253.8
#只允许219.204.253.8通行
——————————-
Order deny,allow
allow from 219.204.253.8
#全部都可以通行
——————————-
——————————-
order deny,allow
allow from 218.20.253.2
deny from 218.20
#代表拒绝218.20开头的IP,但允许218.20.253.2通过;而其它非218.20开头的IP也都允许通过。
——————————-
order allow,deny
allow from 218.20.253.2
deny from 218.20
#和上面的差不多,只是掉换的order语句中的allow、deny先后顺序,但最终结果表示全部都拒绝!

最近看的美剧

好久没有更新博客了,人比较懒。懒到不想思考啊。。。那现在就记录下最近看的一些片片吧。想到哪里写到那里,我的地界我撒野。

15号的时候,热烈期盼的《行尸走肉》第三季隆重回归了,吼吼!!自从去年看了这个剧,一直在等着。所以那天晚上直接就看了,可能导演觉得第二季太磨叽了,总是吵吵闹闹,这一季开头直接暴力血腥。整个团队战斗力嗖嗖的往上冲,开始进入监狱开疆破土了。

同样由于够暴力够血腥,晚上睡觉都还做噩梦了。。。同样也是够血腥够暴力,一扫上一季的磨磨唧唧,由该剧保持的AMC收视纪录被自己轻松的打破。果然喜欢的人多啊啊!!!

瑞克更有领导气质了,当然做领导的都有专权独裁的倾向,作为一个好的领导,更需要有坚守自己底线的品质。由于这一集瑞克的简单粗暴,果断直接。很多人都说瑞克自私、堕落了,直接拿来与肖恩相比把瑞克划入肖恩之流。对于这种观点与评价我是不敢苟同的,瑞克的粗暴,专权在我看来这是坚决为了执行自己与团队生存权的手段。瑞克现在可以很好的凝聚团队的战斗力与士气,俨然已是团队的主心骨,能够带领他们走的更远,活的更久,而肖恩则不能。同时,对比于肖恩,从自私的角度上来说,瑞克的自私从来都是以大家的角度来出发的,仅仅是为了大家的生存。这可以再瑞克与妻子洛莉的关系中看的出来,瑞克与妻子的关系可以说随着剧情的发展越走越远,瑞克已经对这段感情心灰意冷了。但瑞克并没有丝毫的有不搭理或者抛弃他们的念头,他们目前是相依为命的团队了。反观肖恩,第二季中拼命的去小镇上拿药,说到底还是出于自己个人的私心,为了自己的这个“小家庭”,而且不惜故意打伤同伴,拖住ss,自己逃出。最后还想直接杀掉瑞克,可以说他这样一类人在这样的世界中很能够生存,戴尔早就看出了肖恩的本质。其实一个最简单的假设就能得出结论,我们把肖恩与瑞克的立场对调一下,目前的这群人里怕是只剩下洛莉与卡尔了,因为只有他们是肖恩不想抛弃的,其他人,无所谓了。

弓弩男达里尔还是一如既往的酷啊,还是相当的喜欢啊。与他哥根本不是一类人,哦这一季他哥也要回归了。达里尔回不回脱离团队呢?坚持自我啊,达里尔!达里尔与奶妈的关系那个暧昧啊,不过他们一定不会在一起的。这暗暗地情愫是什么咧。。

格伦与女友的关系也越来越好了,既是并肩作战的战友又是相互依靠的唯一。格伦可要更加的机灵和小心啊。法赫尔现在掉了一个腿,哎更加的艰难了,也是为了活命的冒险。还好至少活下来了。还有bag继续生猛,可别再伤着了。而且接下来要面对着达里尔他哥这可是仇人见面分外眼红的啊,不免要战一场。贝丝要与他姐相互坚持啊。。。

行尸走肉除了血腥暴力,还有更多的看点是人勾心斗角,争权夺利,为了生存的本能而战斗。这些才是真正黑暗啊。

现在在追额除了这个,还有新剧《破釜沉舟》上个月偶然看到的。白宫政治漩涡影响在外的美剧核动力攻击潜艇,潜艇官兵为了正义与自由反抗啦!直接与政府对抗,占岛为王。剧中多方势力角逐,军队,政客,军火商,岛内土著,潜艇内部问题等等。而且目前大反派还不明了,不会就是总统吧。。。豆瓣评论“坑太多了,估计会烂尾”,现在也不管了,反正我觉得看的还不错,题材胆大新颖啊。。。

人物角色方面,舰长的“独立宣言”真的太霸气了,沉着冷静优秀的主帅啊!副舰长也是不错的,有血有肉对老婆那个好啊。女上尉是上将千金,不过可不是花架子啊,专业技能是杠杠的,目前还看不错明显的情感走向。水手长每次看到他都觉得这个家伙是卧底,从来没有看好过坚守,现在老实了些,最后估计还是会反。。反而监测站的女女对副舰长有情有义啊,监测站的女女也是个狠角色,拿着张海图就直接引领潜艇逃出生天了。那个海豹大兵核打击的秘密就在他那里,与酒吧女主人现在正打的火热。岛上土著已经开始闹腾了,现在也不会善罢甘休,最后应该会与潜艇一方结盟。

此外,之前还看过《国土安全》《绿箭》《绝命毒师》这些都看过几集,国土就不吐槽了,看了5、6集真的太墨迹了,就是不停的啊。。出现了线索。。啊线索又没了。。啊男主有可疑动向。。。女主直接把自己丢进去。。。然后继续纠结。。。弃之。

绿箭典型的英雄漫画啊,男主不愁吃不愁穿开发牛x东西,劫富济贫,杀恶救善。简直就是蝙蝠侠的电视剧版啊。。。有时间也看一下,男主角很帅。吸引女粉丝啊。。。

绝命毒师看过两集,08年就有第一季了,现在才知道。知识就是力量啊!!!我化学很少及格,那个老师真的好牛x,不过生活所迫啊啊啊,除了那些牛xx的艺术品制作,其中这业余的毒贩子与土匪真的很搞笑,特别是毒贩子用摩托车锁把craze-8锁在地下室里面时候,整个还是可以一看的,就是那位老师很辛苦了啊。白天教书晚上做艺术品,提心吊胆啊。。不过习惯了后来就应该更牛x了。

phpunit的安装,测试

什么是phpunit(来自百度百科

PHPUnit是一个轻量级的PHP测试框架。它是在PHP5下面对JUnit3系列版本的完整移植,是xUnit测试框架家族的一员(它们都基于模式先锋Kent Beck的设计)。

单元测试是几个现代敏捷开发方法的基础,使得PHPUnit成为许多大型PHP项目的关键工具。这个工具也可以被Xdebug扩展用来生成代码覆盖率报告 ,并且可以与phing集成来自动测试,最后它还可以和Selenium整合来完成大型的自动化集成测试。

安装部署有两种方式

方法一: 使用Pear

运行 pear channel-discover pear.phpunit.de;   pear install phpunit/PHPUnit

方法二 :手动安装   1 从http://pear.phpunit.de/get/下载软件包并解压   2 把解压后的目录加入php.ini中的include_path   3 将脚本pear-phpunit改名为phpunit   4 将phpunit脚本中的@php_bin@替换成php可执行脚本的路径   5 为phpunit脚本增加可执行权限并加入$PATH   6 将PHPUnit/Runner/Version.php中的@package_version@替换成3位   PHPUnit版本号

这里我们使用的Pear方式进行安装。以windows下环境为例。

在建立一个完整的php工作开发环境后,phpunit的的部署首先需要xdebug与pear的支持,才能安装phpunit。

1、安装xdebug

[Xdebug]

extension=php_xdebug.dll

xdebug.profiler_enable=on

xdebug.trace_output_dir=”E:/php/xdebug”

xdebug.profiler_output_dir=”E:/php/xdebug”

在安装xdebug后,重启apache,在phpinfo中即可看到了xdebug的信息。

接下来cmd进入php的安装目录,如E:/wamp/php/

输入pear,会提示“pear不是内部或外部的命令,也不是可运行的程序…”,这说明系统还没有安装pear,如果出现如下的命令方式

则说明pear安装成功了,可以直接去安装phpunit了。

在这里需要注意的是,xdebug的的加载方式必须以zend的方式进行加载;而extension是以php的加载方式进行实现的

所以在最后安装成功phpunit后,如果xdebug还是以php方式进行加载的话,系统会提示如下信息

zend_extension 加载是以zend引擎的方式进行模块加载的,详细信息如下

{

根据 PHP 版本,zend_extension 指令可以是以下之一:

zend_extension (non ZTS, non debug build)
zend_extension_ts ( ZTS, non debug build)
zend_extension_debug (non ZTS, debug build)
zend_extension_debug_ts ( ZTS, debug build)

ZTS:ZEND Thread Safety

可通过phpinfo()查看ZTS是否启用,从而决定用zend_extension还是zend_extension_ts。既查看phpinfo综合信息处的状态

extension意为基于php引擎的扩展;zend_extension意为基于zend引擎的扩展

注:php是基于zend引擎的

不同的扩展安装后,在php.ini里是用extension还是zend_extension,是取决于该扩展,有的扩展可能只能用zend_extension,如xdebug,也有的扩展可以用extension或zend_extension,如mmcache。

注:上面的结论不保证准确。
zend_extension加载php扩展时需用全路径,而extension加载时可以用相对extension_dir的路径。

}

这里我的 Thread Safety是启用状态,即线程安全版本,所以使用zend_extension_ts来加载。且xdebug必须使用全路径,这点很重要;不然添加到php.ini后,重启apache不会报错,但xdebug信息也是没有的。

2、安装pear

还是在php安装目录中,cmd输入如下命令

go-pear

连续输入两次yes,在直接按Enter。然后就自动从服务器上下载pear安装包,进行自动安装了。

再次输入pear命令,则会出现commands列表了。说pear安装成功了。

3、安装phpunit

继续cmd命令输入

pear channel-discover pear.phpunit.de

pear channel-discover pear.symfony-project.com

pear install phpunit/PHPUnit

这时可能会提示缺少依赖包内容,如下图

phpunit package

我们可以使用

pear install Image_GraphViz

pear install log

pear install YAML

进行依赖包得依次安装。安装完成后,继续输入

pear install phpunit/PHPUnit

耐心等待,可能有点慢,直到下载安装成功。

至此,在cmd中键入phpunit –version,提示phpunit版本信息则表示安装成功可以进入单元测试阶段了。

4、结合netbeans 使用phpunit进行单元测试

主要有两步,一、对某个类或者整个项目创建phpunit单元测试;二、单击源文件或项目进行单元测试。详细进行请进入使用 PHPUnit 和 Selenium 进行测试阅读。

其中对需要测试的类添加@assert 标注需要注意,点击这里详细了解,在netbeans中可以直接“创建phpunit单元测试”生成测试类;

<?php
class Calculator
{
/**
* @assert (0, 0) == 0
* @assert (0, 1) == 1
* @assert (1, 0) == 1
* @assert (1, 1) == 2
* @assert (1, 2) == 4
*/
public function add($a, $b)
{
return $a + $b;
}

// 添加用来测试“代码覆盖率”的
public function addnow($a, $b)
{
return $a + $b;
}
}
?>


同时也可以使用命令来完成

phpunit –skeleton-test Calculator

注意,Calculator是类名,不是文件名。

会在同目录下自动生成Calculator的测试类,CalculatorTest.php;

CalculatorTest.php类详细内容


<?php
require_once 'PHPUnit/Framework.php';

require_once 'D:\www\phpunit\Calculator.php';

/**
* Test class for Calculator.
* Generated by PHPUnit on 2012-09-25 at 10:17:34.
*/
class CalculatorTest extends PHPUnit_Framework_TestCase
{
/**
* @var Calculator
*/
protected $object;

/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
$this->object = new Calculator;
}

/**
* Tears down the fixture, for example, closes a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
}

/**
* Generated from @assert (0, 0) == 0.
*/
public function testAdd()
{
$this->assertEquals(
0,
$this->object->add(0, 0)
);
}

/**
* Generated from @assert (0, 1) == 1.
*/
public function testAdd2()
{
$this->assertEquals(
1,
$this->object->add(0, 1)
);
}

/**
* Generated from @assert (1, 0) == 1.
*/
public function testAdd3()
{
$this->assertEquals(
1,
$this->object->add(1, 0)
);
}

/**
* Generated from @assert (1, 1) == 2.
*/
public function testAdd4()
{
$this->assertEquals(
2,
$this->object->add(1, 1)
);
}

/**
* Generated from @assert (1, 2) == 4.
*/
public function testAdd5()
{
$this->assertEquals(
4,
$this->object->add(1, 2)
);
}

/**
* @todo Implement testAddnow().
*/
public function testAddnow()
{
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet.'
);
}
}
?>

这时就可以测试这个测试类了。

phpunit CalculatorTest.php unittest

结果信息如下

点号表示测试通过,没有错误;

F表示其中一个测试结果有误。错误信息直观展示了。

I表示测试没有实现执行

对比netbeans的执行更加抽象一点,如下为netbeans执行的测试结果。

这个只是一个简单的示例,更详细的服务于项目级别的对于每一个独立的模块都需要写新的独立测试类来进行测试。

phpunit安装成功后,会在c盘建立一个php5文件夹。其中可以找到PHPUnit的接测试例子

C:\php5\pear\docs\PHPUnit\PHPUnit\Samples

(说实话money例子挺复杂的)很容易绕晕了。

参考信息:

phpunit官方手册

PHP Warning: Xdebug MUST be loaded as a Zend extension in Unknown on line 0 解决办法 

PHP下安装phpunit 及xdebug

PHPUnit安装 部分配图来源此博客,鸣谢

使用 PHPUnit 和 Selenium 进行测试

PHPUnit是一个轻量级的PHP测试框架。它是在PHP5下面对JUnit3系列版本的完整移植,是xUnit测试框架家族的一员(它们都基于模式先锋Kent Beck的设计)。
单元测试是几个现代敏捷开发方法的基础,使得PHPUnit成为许多大型PHP项目的关键工具。这个工具也可以被Xdebug扩展用来生成代码覆盖率报告 ,并且可以与phing集成来自动测试,最后它还可以和Selenium整合来完成大型的自动化集成测试。

windows7 IE的保护模式对于js form.submit()自动执行提交的影响

近期在完成一个项目充值支付业务逻辑时,需要完成用户在网站上提交充值订单,网站后端处理充值订单信息,发布“网站正在跳转,进入充值平台”的提示信息,跳转至充值平台进行充值的这样一个流程。

因为直接采用的是discuzX进行二次开发,在xp、2003等各浏览器下业务逻辑测试都没有问题。就是在交付客户测试中,发现客户的IE9浏览器不能正常的充值支付;发现是停顿在了跳转消息发布的那一步中了,也就是说后端返回自建的form表单,并没有正确的提交。


// 对discuzx的提交进行了更改,没有采用ajax提交;而是直接订单form表单新页面提交,后端直接echo数据。

echo '<form id="payform" action="'.$requesturl.'" method="post"></form><script type="text/javascript" reload="1">$(\'payform\').submit();</script>';
dexit();

确实是个问题了,刚开始以为是订单form新页面提交后,新产生的此页面渲染模式有关,从而建立完整的文档结构


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">

...

但是效果还是没好转,只是在完成提示信息;自己form表单还是没有进行自提交。再次google查看

不过在查看的过程中发现了这样一个问题,就是在xp或2003的系统上是能够正常跳转使用的;但在win7 ie下则不可以了,但是其他浏览器则行,就算是IE内核,但非ie官方的浏览器也是正常的。

于是对比2003与win7下的IE8设置,发现了下图独特的地方。

多了一个“启动保护模式”选择框!
另一位同事尝试的取消这个勾选框后,重启IE 再次测试就能正常的跳转了。
至此问题明了了,不过这个选择框是windows7上系列浏览器的默认勾选状态;所以不可能要求windows7的用户去更改此项设置,且会招致用户的反感。
于是在想办法的过程中参考了其他网站的api支付跳转实现效果,发现用户充值订单表单提交后,在跳转信息的提示过程中,程序后端并没有直接跳转至支付平台,而是再次跳转至网站本地后端的一个页面,而这个跳转的过程是使用window.location.href来实现的,而目标页面则直接通过header location跳转到了目标支付平台,也就是充值表单数据最终是以get方式发布至支付平台。(安全性方面不知道是否降低了,待验证)
代码结构再次更改后,2003下测试正常;在windows7下测试也能执行正常的跳转了!为此,新添加一个php页面用来处理添加充值订单,header页面跳转。而原form接受页面只是进行数据的收集以及跳转信息的提示。
在业务逻辑构造的过程中,查看了discuzx2.5最新版本;支付充值这块地方的代码还是没有变化还是采用自建from表单提交的方式实现的。于是在discuz官网论坛上面去搜索“IE9 充值”果然有很多用户的帖子,
而官方团队对此的回复是“该问题我们已经发现,这问题与环境有关,且不好重现,之前装有安全软件的一些用户也出现空白现象,往后我们再排查下是否是产品本身的兼容性问题。 感谢您的反馈1”
不过已经有用户发现了这样的问题了,并且有了对应的解答
知识普及:
IE保护模式原理简述

IE保护模式(Protected Mode),以前也叫做IE低权利(Lowcenters)。IE保护模式可能是Windows Vista里最严厉的安全措施,比我们熟知的UAC(用户帐户控制)还要严厉。事实上,IE保护模式依赖于Windows Vista的以下三大特性:

(1)UAC(用户帐户控制)

相信大家已经很熟悉了,可以让管理员帐户自动获得一个标准用户的访问令牌,以减少WindowsVista系统的受攻击面。

(2)MIC(强制完整性控制)

这是WindowsVista安全架构中新增加的一种检测机制。大家知道,Windows2000/XP安全体系里,安全子系统把进程的访问令牌和 资源的访问控制列表进行匹配比较,以确认该进程是否具有访问该资源的权限。而在WindowsVista下,除了遵守传统的安全控制机制外,还必须检查进 程和资源对象的完整性级别,完整性级别低的进程,不能写入完整性级别高的资源对象。

(3)UIPI(用户界面特权隔离)

完整性级别低的进程,不能向完整性级别高的进程发送Window消息。

windows IE浏览器安全设置的注意事项:

在这里,我们必须了解以下三点注意事项:

1.只有IE浏览器才能享用保护模式带来的好处,第三方的浏览器、或者以IE为内核的一些外挂浏览器无法获得保护模式的功能。

2.只有确认安全的网站,才能添加到可信站点区域。错将问题站点添加到可信区域,将会导致系统安全的严重受损。

3.最好能够对旧版插件进行改进,以确保插件的兼容性,这样可以大大提高系统安全。

扩展阅读

Internet Explorer 保护模式有何功能?

IE8浏览器安全设置 更好的安全浏览

关闭“保护模式”后,IE9还有没有安全?