Mysql升级到5.1后库升级失败的问题

一台 mysql 5.0 服务器,升级到 5.1 后,发现原来有个 database 名字变成了 #mysql50#t-2008-zbb ,刚开始没在意想直接 RENAME DATABASE ,结果这个语法由于过渡危险已经取消了,改用ALTER DATABASE db_name UPGRADE DATA DIRECTORY NAME,结果执行错误:

mysql> ALTER DATABASE `#mysql50#db_name` UPGRADE DATA DIRECTORY NAME;
ERROR 1450 (HY000): Changing schema from '#mysql50#db_name' to 'db_name' is not allowed.

原来这里面还有个 BUG ,刚刚修正过来,发行版中肯定还没有呢。幸好,从中得到了提示,因为 View 的存在导致库无法升级的,删掉所有视图后 UPGRADE 成功:

mysql> ALTER DATABASE `#mysql50#db_name` UPGRADE DATA DIRECTORY NAME;
Query OK, 0 rows affected (0.08 sec)

这台服务器还作了双向同步,我还得手工重置同步状态,又是麻烦一连串儿的事情,幸亏这次操作的是测试服务器,下次升级正式服务器之前,记得先把所有 View 删掉,升级完成后再重新创建。

另外 RENAME DATABASE 实在是危险,我执行过程中出错终止了,结果一部分表在新库里、一部分表在旧库中,不小心把未转完的目标库删掉了(不然后面的正常 ALTER DATABASE 无法继续),结果就丢失了这些表的数据。

mysql的复制

这篇算是我的学习笔记,主要内容都来自mysql官方文档,然后参考一些网上的文章自己实践了个例子,所以内容上前后也有些重复。

mysql支持从一个主服务器到多个从服务器的复制,并且复制是异步的,也就是说从服务器不用一直保持与主服务器的连接,这使得一些远程、临时性的节点也能进行同步。同时,还可以指定参与同步的数据库或者数据表。

同步目前主要用于以下几个方面:

  • 架构扩容,一主写多从读的方式已经用得非常普遍了
  • 数据安全,同步也是一种备份,而且还可以是实时的,多好啊
  • 数据分析,大量的分析运算在从服务器上运行,不影响主系统的负载
  • 远程数据发布,或者说远程备份

同步的基本设置

主服务器的my.cnf设置

同步是通过binlog来实现的,虽然主服务器不能决定哪些操作在binlog中记录(默认记录全部),但从服务器可以根据配置来有选择的执行。每个主、从服务器都要有一个唯一ID,从服务器还要记录主服务器的主机名、log文件名以及位置等信息(在master.info中),并保存一份收到的binlog,这样多台从服务器就可以有不同的同步状态或进度,并且下次连线的时候再以“续传”的方式继续同步进程。binlog的开启需要修改主服务器的my.cnf

# 必须写在[mysqld]节里面
[mysqld]
# mysql-bin是log文件的前缀,也可以使用其它的名字,比如服务器名
# 如果不带路径,会把log文件写到`/var/lib/mysql`下
log-bin=mysql-bin
# serverid在一个同步体系中必须是唯一的,大于等于1且小于2^32-1的整数
server-id=1

如果使用InnoDb,为了保证稳定,还应设置如下两行:

innodb_flush_log_at_trx_commit=1
sync_binlog=1

并且确保没有设置skip-networking,禁用网络自然无法同步。但是,innodb_flush_log_at_trx_commit=1在某些服务器上会导致写数据速度急剧下降,可尝试调整为2。

用户

每个从服务器都要使用标准的mysql用户来连接主服务器,对应的权限名称为REPLICATION SLAVE(这是一个和库无关的权限,属于服务器管理权限的一种)。虽然使用独立用户并不是必需的,但由于用户名和密码都将以明文方式存储在记录replication相关信息的master.info文件中,还是采用独立用户更安全一些。语法如下:

mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%.domain.tld' IDENTIFIED BY 'passwd';

从服务器的my.cnf设置

server-id是必须设置的,binlog不用开。

获取主服务器的同步信息

从服务器必须知道要从主服务器binlog的哪个位置开始进行同步,相当于有一份数据、一份要执行的指令(binlog)以及一个当前执行哪一条指令,要获得这三个信息,必须临时暂停主服务器上的处理:

mysql> FLUSH TABLES WITH READ LOCK;

(这个连InnoDB的commit都能停住),暂停状态下数据、指令就都不会变了,查询出当前记录的binlog的位置:

mysql > SHOW MASTER STATUS;

得到的File列是当前的binlog记录文件名,Position就是日志记录,如果还没东西可记,File为空字符串而Position为4。后面设置从服务器的时候,CHANGE MASTER TO命令就要用到这些信息,比如:

mysql> CHANGE MASTER TO
    ->     MASTER_HOST='master_hostname',
    ->     MASTER_USER='repl_username',
    ->     MASTER_PASSWORD='repl_password',
    ->     MASTER_LOG_FILE='binlog_filename',
    ->     MASTER_LOG_POS=binlog_position;

复制主服务器的数据到从服务器

保持主服务器的暂停处理状态,先把数据都弄到从服务器上去。导出可以用mysqldump:

mysqldump -h db_host --add-drop-table --default-character-set=utf8 --user=root -pPASSWD --extended-insert=false db_name > db_name.sql

带上--all-databases参数可以导出所有库,不过不想参与同步的库是没必要导的。如果带上--master-data参数,还能够把从服务器设置的一些sql一并导出来。

当然,直接拷贝数据库文件也是可以的,速度也更快,但如果在mysql服务启动的状态下拷贝,可能会存在缓存、日志不同步的情况,所以要停掉服务以后再拷贝,并且不要拷贝和目标库无关的日志等文件。

为了进一步保证数据的一致性,可以在导出数据前给所有数据加锁:

mysql> FLUSH TABLES WITH READ LOCK;

导出完成后再解开:

mysql> UNLOCK TABLES;

同步的进阶说明

binlog的几种形式

一种是记录所操作的“命令”(mysql 5.1.4前版本只能用这个),简称为命令复制SBR;另外一种是记录所有受影响的行,简称为行复制RBR。从mysql 5.1.8开始支持包含上述两种方式的混合型日志。命令复制的优点是:

  • 产生的日志小
  • 不要求每个表都要有主键

缺点是:

  • 某些函数的返回值是无法复制的(比如LOAD_FILE(), UUID(), UUID_SHORT(), USER(), FOUND_ROWS(), SYSDATE(),但RAND(), NOW()能正常复制)
  • 写数据时用到的锁较多
  • 主从服务器的表必须一样

行复制的优点为:

  • 最安全,所有的操作都能记录和同步
  • 写数据的时候使用到的锁较少

缺点为:

  • 写大量数据时(比如全表UPDATE)日志里要写很多东西,日志锁定时间也相对更长,可能会带来一些并发问题
  • 所有的表必须要有显式主键(一般情况下都会有主键的,除非超大数据量或特殊设计)
  • 带有大文本的数据复制可能要慢一些,这个好理解,一个update操作,如果是行复制的话,肯定是更新了n行,复制传递的数据量就是n倍大小
  • 查看更新传递和执行的进度不方便
  • 无法用WHERE @server_id <> server-id这个小技巧来临时指定只更新某台服务器

在具体处理的时候,依然会有一些操作比如GRANT REVOKE之类是用命令方式同步的。总体来讲,经常要进行大量数据变更的情况下,命令复制似乎更合适一些。

常用配置参数说明

控制主服务器的:

  • auto_increment_increment, auto_increment_offset 自增主键的跳号间隔和初始值,有什么用呢?有人作双向复制的时候,设置A服务器主键自增从1开始每次跳10,B服务器则从2开始每次跳10,这样双向复制的过程中就不会产生自增主键重号的问题了。这两个是全局而非针对某表的设置。不过我更倾向于用UUID实现。
  • slave_exec_mode 从服务器容错方式,5.1.24后版本才有。

控制从服务器的:

  • master.info中配置的优先级比my.cnf中的高。
  • log-slave-updates 从服务器的变更也记入日志,作链式复制时必须。
  • replicate-do-db=db_name 只复制指定数据库,可以写多行来指定复制多个库。注意在命令复制模式下mysql只认用use db_name选定的,不会从跨表操作的sql中自动判断;而行复制则是依照实际变更的数据属于哪个表来判断。
  • replicate-rewrite-db=”from_name->to_name” 主、从数据库名称不一样的时候,用此命令进行转换。

控制日志的:

  • binlog_format 日志类型,ROW或STATEMENT,新版中还可选MIXED。
  • log-bin[=base_name] 开启二进制日志(复制必须),可以指定日志文件名。
  • sync_binlog 什么时候写日志到磁盘,这个机制比较复杂,一般设为1相对较安全。

同步的管理

有一些命令:

  • SHOW SLAVE STATUS 在从服务器上查看状态。
  • SHOW SLAVE HOSTS 在主服务器上,启用了report-host以后,显式已连接的从服务器。
  • STOP/START SLAVE [IO_THREAD/SQL_THREAD] 在从服务器上,停止复制(的日志下载/SQL执行操作)。

同步实现实例

复制还是在局域网内方便,尤其是双向复制,因为跨公网的话不光要连进去,还要从里面连出来。 综合考虑,M-M-Slaves的模式的一般适用性可能更强一些。下面以Master-Master简要举例:

首先分别修改M1/2的my.cnf:

[mysqld]
# Replication
server-id=1/2
log-bin=/var/log/mysql/svr1/2
log-error=/var/log/mysql.err
relay-log=/var/log/mysql/svr1/2-relay
binlog_format='STATEMENT'
sync_binlog=1
log-slave-updates
innodb_flush_log_at_trx_commit=2
binlog-do-db=db_to_replicate

master-host=ip_of_M1/2
master-port=port_of_M1/2
master-user=rep
master-password=passwd
master-connect-retry = 60
replicate-do-db=db_to_replicate

然后在M1/2上创建同步用户:

mysql> grant replication slave on *.* to 'rep'@'%' identified by 'passwd';

重启M1/2的mysql服务,并停止slave:

mysql> stop slave;

下面,把数据从M1上复制到M2上,先在M1上:

mysql> use db_to_replicate;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> flush tables with read lock;
Query OK, 0 rows affected (0.00 sec)

mysql> show master status;
+-------------+----------+--------------+------------------+
| File        | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+-------------+----------+--------------+------------------+
| svr5.000002 |       98 | db_to_replicate   |                  | 
+-------------+----------+--------------+------------------+
1 row in set (0.00 sec)

目的是锁上M1上所有表(为只读状态),并记下当前binlog的记录位置。然后不要退出mysql,另外开一个session,导出数据:

$ mysqldump -h 127.0.0.1 --add-drop-table --default-character-set=utf8 --user=root -p --extended-insert=false --skip-lock-tables db_to_replicate > db_to_replicate.sql

mysqldump时用参数指定不再次加锁,也可以采用直接拷贝数据库文件的方式(导入时更快,也得更小心一点)。导出完成后可以解锁,让M1正常运行了:

mysql> unlock tables;

现在到M2上去,用刚才导出的数据把库建起来(如果采用直接执行sql的方式,记得先停掉binlog),然后进行比较重要的一步,让M2从M1刚才导出数据的时候开始恢复同步:

mysql> stop slave;
Query OK, 0 rows affected (0.00 sec)

mysql> CHANGE MASTER TO MASTER_HOST='ip_of_M1', MASTER_PORT=port_of_M1, MASTER_USER='rep', MASTER_PASSWORD='passwd', MASTER_LOG_FILE='svr5.000002', MASTER_LOG_POS=98;
Query OK, 0 rows affected (0.01 sec)

mysql> start slave;
Query OK, 0 rows affected (0.01 sec)

最后两个参数就是刚才在M1上用show master status;得到的数值。

同理,在M1上也作一次CHANGE MASTER TO操作,指向M2,两边的双向主主复制基本上就算是完成。

另外注意以后切换MASTER的时候,直接修改my.cnf是不行的,因为都存到优先级更高的master.info文件中去了,还是用CHANGE MASTER TO来操作更靠谱。

通过ssh进行远程同步的连接方法

以M2远程登录M1的方式为例,M2 ssh到M1,以便M2作为slave能连接master M1,使用的是ssh隧道:

M2 $  ssh -p 8022 -L 127.0.0.1:3307:ip_of_M1:3306 -CfNg user@domain.tld -o ControlPath=/tmp/ssh-mysql-3307-M1-3306

双向同步时,M1还要连接到M2才行,依然是从M2上来打洞:

M2 $ ssh -p 8022 -R 0.0.0.0:3307:localhost:3306 user@domain.tld -fN -o ControlPath=/tmp/ssh-mysql-3307-local-3306

注意在建立这些ssh隧道的时候,一般不能使用ssh的连接共享

Update @ 2009-03-16

马上就遇到了innodb启用了innodb_file_per_table之后,直接拷贝.ibd文件过来不生效的问题,并且是在建立好M-M复制之后,如此这般(M1是旧服务器,M2上把文件拷贝过来导入的服务器)(补注:依然失败):

  • M1: 确认所有同步内容已经处理完毕。
  • M1: stop slave;
  • M2: flush tables with read lock;
  • M2: 备份mysql data目录下相应库的所有文件(就是拷贝过来的那些),并且删除掉(db.opt不要删)。
  • M2: unlock tables; 不然下面的操作无法进行。
  • M2: 使用其它手段重新创建所有表,数据不需要。
  • M2: show master status; 得到位置后面要用到。
  • M2: flush tables with read lock; 准备用拷贝过来的ibd覆盖刚才用sql重新创建的表,这样数据就有了。
  • M2: 复制/拷贝/恢复刚才备份的所有.frm, .ibd文件。
  • M2: unlock tables; M2恢复正常。
  • M1: CHANGE MASTER TO MASTER_HOST=’ip_of_M2′, MASTER_PORT=port_of_M2, MASTER_USER=’rep’, MASTER_PASSWORD=’passwd’, MASTER_LOG_FILE=’fwolf-office.000101′, MASTER_LOG_POS=57943; 目的是跳过刚才那些重新建表的处理。
  • M1: start slave; M1恢复正常。

至于原因嘛,因为拷贝过来的都是ibd文件,而innodb的schema信息都存在系统表INFORMATION_SCHEMA里,偏偏这个系统表又是只读的,所以只能重新建一遍库了。

还遇到了特定名称的库无法创建的情况,会提示.frm无法创建之类的,可以这样处理:创建失败后,从其它库中建一个名称相同的,然后把.frm, .ibd文件拷贝过来,然后执行drop table XXX,然后就能正常创建、删除了。

……慢,表都能看到了,可哪个里面都没有数据,还是不行,看来innodb迁移到其它服务器,尤其是配置不同、使用状态不同的情况下,还真不是一般的麻烦,我还是老老实实改用mysqldump得了,删库重建再导入,慢也就慢这一次而已。大体过程:

  • M1: stop slave;
  • M1: dump all data: mysqldump -h ip_of_M1 –add-drop-table –default-character-set=utf8 –user=root -p –extended-insert=true –net_buffer_length=600k–skip-lock-tables db_to_replicate > db_to_replicate.sql
  • M2: delete database & recreate it
  • M2: 执行刚才的sql,导入数据,最好先关闭binlog
  • M2: stop slave;
  • M1/2上分别change master to, start slave;

如果遇到这样的错误:

ERROR 1201 (HY000): Could not initialize master info structure; more error messages can be found in the MySQL error log

可以在CHANGE MASTER TO之前先RESET SLAVE;

参考

Firefox3书签中的Tags存哪儿了?

电脑岁数大了是不行,即使装上了号称速度有很大改观的Firefox3,我的老爷机也没快到哪儿去,这还是在一大票插件都由于不兼容被咔嚓了之后,唉。

不过Firefox3还是有些不错的新功能的,比如网址栏中的“星星”,和Gmail里面的哪个很像吧,不知道是谁家的专利,这个星星挺有意思,当你浏览到觉得不错的网站时,点一下星星就可以收藏起来了,默认是在书签的Unfiled Bookmarks文件夹中,以后可以慢慢整理;点两下星星会弹出窗口让你选保存位置,并可以输Tag。

Tag可以说是Web 2.0的标志性特征了,也是组织和查找信息的一种有效方式,所以我就很感兴趣,这个Tags被Firefox3存到哪个文件里去了?Tag的存储方式和Tag Cloud的算法我很感兴趣,也想看看FF的解决方式。

一般Firefox会把书签保存在bookmarks.html文件当中,不过Firefox3改进了,把书签放到了sqlite数据库中,文件名就叫做places.sqlite。这个文件在Firefox3运行时会以独占方式打开,关闭FF后才能用sqlite3打开。

起初没想到有什么难处,直接看schema,结果仔细的读了一遍也没发现哪些地方有tag的踪影:

  • moz_anno_attributes 注释的属性,没几行数据,不知道干啥的
  • moz_annos 目前是空的,应该是什么的注释
  • moz_bookmarks 书签项目
  • moz_bookmarks_roots 书签项目的根网站集合,新安装FF的还没数据呢
  • moz_favicons 网站图标favicon的位置
  • moz_historyvisits 哪个网站你浏览了多少次,偶的隐私啊。。。
  • moz_inputhistory 输入历史?暂无数据
  • moz_items_annos 书签的注释,也就是那个“Description:”
  • moz_keywords 关键字,和“Description:”在一个界面输入,没啥用处
  • moz_places 所有浏览的地址历史

看吧,没哪个表是和tag有什么关联的,可用grep一查,我标记的tag确实是在这个文件中,只好出绝招:

sqlite3 places.sqlite ".dump" > t.sql

然后在t.sql里面一查找,终于明白了:

INSERT INTO "moz_bookmarks" VALUES(346,2,NULL,4,2,'testtag',NULL,'',1208788891265751,1208788891362043);
INSERT INTO "moz_bookmarks" VALUES(347,1,638,346,0,NULL,NULL,NULL,1208788891361294,NULL);

tag就是存在了moz_bookmarks这个表中,和书签的记录混在一起。像上面这种情况,每个tag除了自身占一行记录之外,如果有几个书签被标记了这个tag,那么就还会多出几条记录tag-书签关联关系的记录。

窃以为这并不是存储tag的最佳方式,而且FF在tag的使用上也太简单了,目前看到的就是一个Smart Bookmarks里能按常用tag查网站,连Tag Cloud也没有。一是不知道以后的tag数据格式是否会改变,二是应该会有扩展来完善这块功能,期待吧。

ADOdb的数据字典功能及其它

在新项目中继续使用ADOdb,这个是没什么悬念的了,尝试过php5自带的pdo,感觉一般,对某些数据库的支持更一般。但是数据库结构如何维护这块放不下心,从以往的开发中看,系统开发维护期超过半年之后,如果在数据库schema管理上不下点功夫,在生产服务器和多个测试服务器以及sql结构之间,难免会产生schema不一致的情况,管理情况再差的,连最后一稿schema是哪个、以及schema注释都能丢失。所以,无意中瞥到ADOdb还有一个ADOdb Data Dictionary Library for PHP以后,就想看看这个能否用在多环境数据库schema同步方面,连同自己了解的一些其它情况,记录在这里。

ADOdb Data Dictionary Library for PHP

可以用简单的字符串或者数组的方式记录数据表的结构,然后调用相关功能来生成修改数据库的sql语句,并执行之。应该是在创建数据表方面还是非常方便的,虽然数据类型简单了一些,执行和调用也略显麻烦(抱歉我的确是这么认为的),但在简单的应用中,尤其是数据表修改不是很多的情况下,应该会很好用。

ADOdb XML Schema (AXMLS)

这个其实也是包含在ADOdb中的,其介绍也是直接列在了ADOdb Data Dictionary Library for PHP同一页的下方。在仔细把这段并不多的内容读完之后,我是半喜半忧,喜的是AXMLS这个东西可真好啊,相当于能够把整个数据库的schema xml化了,然后对实体数据库进行更新;忧的方面一是,在最新的ADOdb5.04当中,依然没有完全实现AXMLS中的全部功能,我建了一个简单的xml,其中带有一句:

<opt platform="mysql">TYPE=INNODB</opt>

然后在一个空的数据库上执行,输出sql,发现CREATE TABLE的sql中并没有带上ENGINE=InnoDB,所以就停止了尝试;忧的方面二是这么好的东西,它的官方网站上最新的消息却是2004年6月17号的,一个软件不可能在4年中一点都没有改动,况且那时候还没有php5呢吧。在互联网其它方面来搜索这个AXMLS,发现只是在adodb中有,并没有在其它项目中得到运用,可惜了了。

一些商业软件的实现

很多商业软件的数据库schema管理一般都在case工具中,也有生成sql甚至直接对数据库应用schema变更的功能,比如我以前使用的PowerDesigner,不仅是最好的数据库实体模型设计工具,而且设计完成后,直接连上数据库进行更新。也可以反过来,从实体数据库中分析出数据库模型。

东西是好东西,可用了一段时间不再想用下去了,原因是多方面的,一是Sybase的东西bug总是很多,有时候莫名其妙的错误提示很头疼,二是数据库大了以后,每次连线diff的时候都很慢,最要命的是,它在更改column时喜欢用临时表,然后删除原表建新表,再从临时表中把数据读回来,一旦这个过程由于数据库兼容问题或者其它原因被中断,下次再同步的时候,很有可能数据库结构更新了,而数据却丢失了,因为它有自动删除临时表的选项,并且一般大家还都需要打开这个选项;最后一个原因,这玩意儿既没有Linux版本,我也没有正版的授权,所以能下岗就下岗了。

自己用简单的脚本来实现

其实在实际的应用开发中,数据库相关要修改的地方会很多,不仅有表、索引、视图等数据库本身的元素,还可能会有一些数据的调整和处理,比如将表中的A、B字段去掉,他们的值相加存储在一个新的C字段中,如果说前者应该会有成型的工具能够实现的话,像后面所说的这种数据库,或者更准确一点说是数据调整在任何工具中都不会涉及到的。

反过来想,无论是对数据库如何进行操作,无外乎就是DDL数据库定义语言和DML数据库管理语言,也就是通过大家常说的SQL语句来操作,那么把这些简化为一个SQL语句集合,就可以将开发过程中所有的数据库变动转化为这一系列SQL语句的管理和执行,设计这样一个工具不就得了么?

我现在就是这么作的,在svn中除了有保存数据库最终状态的schema sql文件之外,还专门有一个php文件,用数组记录每一次需要对数据库调整的SQL,然后有一个执行记录表,记录哪些SQL执行过而哪些没有。这个执行记录表在每个数据库实例中都会创建,这样,我只需要简单的调整数据库连接参数,然后就可以通过执行php代码,依次逐个的执行每个SQL了。

有一个缺点,就是SQL语法太依赖具体的数据库类型了,没办法,好在更换数据库的情形并不多,平时尽量写标准的语法吧。另外真要转换数据库的时候,还可以另起炉灶嘛。

其它的开源软件都是怎么实现的呢?

我阅读过的开源软件源码并不多,但软件还是用了一些,在有些软件升级的时候,就会自动将数据库也升级了,他们都是怎么实现的呢?难道说只是在1.0升级到2.0的升级程序upgrade.php中,固定的、一次性执行数据库修改么?

关于同类数据备份和迁移

以前一个朋友用oracle数据库的时候,特别喜欢用tora(他管它叫蛤蟆,因为软件的about页上有只蛤蟆),但我却认为,在同类数据库,尤其是版本、配置相同的数据库之间进行数据转移,方便一点的方法是利用数据库的导出、导入功能,这样不会有DML覆盖范围以外的内容拉下,简单一点的,自己写个小程序,连上两个数据库,这边读来那边写,也不会慢到哪里去,想提前删个索引什么的也好控制。tora一类的工具,应该是在灌初始字典库的时候用最合适。另外,不是还有bcp么?

参考

WordPress升级后分类乱码的原因及解决

另外一个网站WordPress很久没有升级了,主要是嫌网站太慢,ssh上去操作麻烦,加上主人也很少更新。不过当WordPress Automatic upgrade出现之后这一切都简单多了我只要在页面上点点鼠标就能升级了,顺便还能把WP的文件和数据库打包下载到本地。同时,WP的新版2.5还支持插件的自动升级,以后无论是升级WP还是升级插件,基本上就不用ssh了。

WordPress Automatic upgrade的安装和WP的升级基本顺利,不过中间执行完upgrade.php升级数据库后,返回wpau有一个错误,没什么提示信息,直接retry后说升级不成功,清除结果再次运行就没问题了。

升级成功之后,blog标题成了??问号,我知道这是字符集的问题,看了看wp-include/wp-db.php中已经可以自动识别字符集(原来都是自己hack这个文件),就在wp-config.php中添加了两句配置:

define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');

乱码问题解决。以为这就没什么事,又分类的名称怎么都成了“??”了,到分类管理里面一看还是问号,编辑修改成中文保存后依然是问号,如果改成英文的就没事了,其它的内容比如文章什么的中文都正常,怎么回事呢?

左思右想,觉得还是和升级时候的那个错误有关,我是从比较旧版本的WP升级过来的,以前是通过hack wp-include/wp-db.php文件设置数据库用utf8编码连接,升级过程中这个文件被新版文件覆盖了,而wp-config.php中添加相应设置之前有一个空档,而升级数据库恰恰是在这个空档时进行的。WP 2.3添加了tag功能,后来查看分类的数据表发现分类和tag是保存在一起的,所以可以确认这些被修改了的数据表在创建时,程序并不是使用utf8编码连接的数据库服务器,而是默认的latin1_swedish_ci字符集。在这样字符集的数据表中,自然是无法保存中文的,所以原来的中文、新输入的中文就都变成了问号。

解决方法有些麻烦,因为没有用phpMyAdmin,都是直接编辑好sql命令在mysql中执行的,好在涉及到的表不多:

  • wp_term_relationships
  • wp_term_taxonomy
  • wp_terms
  • wpau_active_plugins_info
  • wpau_upgrade_log

修改表的字符集语法如下:

ALTER TABLE [table_name]  DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

这还没完,还得把每个表中的字符型字段的编码转换过来,这个就略微多一点了:

# 查看表中各列的详细情况,包括字符集:
show full columns from wp_term_relationships;

# 更改列的字符集或类型、长度
alter table  wp_term_taxonomy change taxonomy taxonomy varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
alter table  wp_term_taxonomy change description description longtext CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;

alter table  wp_terms change name name varchar(55) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
alter table  wp_terms change slug slug varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;

alter table  wpau_active_plugins_info change plugin_name plugin_name varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
alter table  wpau_active_plugins_info change plugin_status plugin_status varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;

alter table wpau_upgrade_log change task_name task_name varchar(150) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
alter table wpau_upgrade_log change task_status task_status varchar(150) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
alter table wpau_upgrade_log change task_description task_description varchar(150) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
alter table wpau_upgrade_log change task_log task_log text CHARACTER SET utf8 COLLATE utf8_general_ci NULL;

其实wpau的两个表不用改的,老外设计的程序一般不会写中文进去,改过来只是好看一些。

另外WP 2.5把slug弄没了很不舒服,虽然现在我只用id作permanent link,但还是原意有个整齐一点的slug更好看,所以用上了slugshow插件,希望WP啥时候把那个貌似智能其实很傻的功能改过来。

Update @ 2008-04-05

差点忘记了还有一个css裸奔节,顺便贴一下我blog首页穿衣服和不穿衣服的样子,首先是穿衣服的:

Free Image Hosting at www.ImageShack.us

然后是不穿衣服的:

Free Image Hosting at www.ImageShack.us

我的脱衣脚本还是很好用的嘛。

PDO和sqlite的一点体会

用php写了一个小工具,顺便体验了下PDO和sqlite,一个是php5自带的数据库层,一个是简易的文件型数据库,没什么章法,简单记录在这里。

  • 配合pdo使用,只用安装php5-sqlite即可,php5-sqlite3这个extension可能是单独的sqlite支持,就是类似mysql,有专门的sqlite_connect函数。
  • 系统中也可以安装单独的sqlite3(不带3的是sqlite2),采用类似mysql的shell方式管理库文件。
  • 通过PDO好像无法查询数据库的结构等信息,只能操作DML。
  • 不知道pdo使用sybase(dblib)的时候是否能在sql中使用limit,以及使用了是否有效,要到hardy 8.04 php5-sybase才支持pdo,到时候再试验。
  • Bug: rowCount()无法工作于pdo_sqlite数据库,其他一些函数也有小问题,官方文档的user comment中有说到。
  • PDO的exec方法可以一次执行多条sql,但不返回结果,而query则只能执行一条,返回结果信息。
  • 使用PDO的prepare和PDOStatement的execute不仅有助于加速同一sql不同参数的调用速度,还有助于防止sql注入攻击。
  • prepare的:name不能是表名,大概只能是where中的变值,prepare中的字符型:name不需要带上引号,这一点很方便。

总体感觉,PDO还没有AdoDb方便,大概是用熟了的感觉,但功能上的确要少一些。sqlite数据库表现不错,但由于数据类型、结构、功能相对简单,还是主要用在小型、简单一点的应用里更合适。

Update @ 2008-03-23

来一个使用PDO调sqlite库的例子:

try {
    $db = new PDO("sqlite:/path/to/db/file", null, null, array(PDO::ATTR_PERSISTENT => true));
} catch (PDOException $ex) {
    die("[Error] " . $ex->getMessage() . "\n");
}

// Useless but carefully is better, set encoding.
// Sqlite always use utf-8 internaly, UTF-8 is also it's default value.
$db->query('PRAGMA encoding = "UTF-8"');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// Begin query
$stmt = $db->query("PRAGMA table_info({$tbl})");
// PHP bug: rowCount() not work with pdo_sqlite
if (0 == count($stmt->fetchAll())) {
    // Table not exists or have no columns
    ......

另外在使用ADODB的时候,它默认好像并不直接支持sqlite3,而是sqlite2(ADODB版本为V5.04 13 Feb 2008 (c) 2000-2008 John Lim (jlim#natsoft.com)),所以只能通过pdo来实现(即ADODB中使用的数据库类型为pdo),可pdo在这个版本的ADODB中支持也不是很全面,drivers目录下甚至没有adodb-pdo_sqlite.inc.php文件(网上搜了一下,这个文件似乎在某些地方有),所以,只能这样用了:

//ADODB设定
$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;

try
{
    $conn = &ADONewConnection('pdo');
    @$conn->Connect('sqlite://path/to/db/file');
}
catch (Exception $e)
{
    adodb_backtrace($e->getTrace());
    exit();
}

// Check create table, adodb&sqlite3 not work very well, MetaTables() un-usable
//$rs = $conn->MetaTables();
$rs = $conn->Execute("SELECT name FROM sqlite_master WHERE type='table' AND name='my_table_name'");
if (1 > $rs->RecordCount())
{
    // Table does not exists
    ......

Connect前面的@如果去掉,会有Notice错误出现:

Notice: Undefined property:  ADODB_pdo_base::$_genIDSQL in /home/fwolf/dev/php_includes/adodb5/drivers/adodb-pdo.inc.php on line 106

Notice: Undefined property:  ADODB_pdo_base::$_dropSeqSQL in /home/fwolf/dev/php_includes/adodb5/drivers/adodb-pdo.inc.php on line 108

所以,只能说是ADODB对sqlite3的兼容性还不是很好。