Discuz数据库引擎转换

来源:互联网 发布:欧洲杯 网络版权 编辑:程序博客网 时间:2024/05/16 08:38

MyISAM与InnoDB的对比
https://www.biaodianfu.com/mysql-myisam-innodb.html

MyISAM的读取性能要比InnoDB好一些,但这略微的优势远比不上InnoDB的行级锁和事务操作等特性。MySQL从5.6开始,将InnoDB作为默认的数据库引擎,因此除非需要使用MyISAM的特有功能,都应该使用InnoDB数据库引擎。Discuz论坛中的帖子主表(pre_forum_post)使用MyISAM特有的非第一列自增的联合主键,使得我们不能直接将改变转换为InnoDB。

数据表的改造

pre_forum_post表的结构为:

CREATE TABLE pre_forum_post (  pid int(10) unsigned NOT NULL,  #帖子id  fid mediumint(8) unsigned NOT NULL DEFAULT '0',  #论坛id  tid mediumint(8) unsigned NOT NULL DEFAULT '0',  #主题id  ...  position int(8) unsigned NOT NULL AUTO_INCREMENT, #楼层编号  PRIMARY KEY (tid,position),  UNIQUE KEY pid (pid),  ...) TYPE=MyISAM;

主键为(tid,position),自增列为position,这种结构在MyISAM中不同的tid对应的position会分别从1开始自增,正好对应不同的帖子的楼层数都是从1开始自增的。

将该表转换为InnoDB,我们需要将(tid,position)设为唯一索引,来保障同一个帖子的楼层不会重复,并将pid作为新的主键。

ALTER TABLE `pre_forum_post` MODIFY COLUMN `position`  int(8) UNSIGNED NOT NULL AFTER `replycredit`,ADD PRIMARY KEY (`pid`), DROP INDEX `pid`, ADD UNIQUE INDEX `tid_position` (`tid`, `position`) 

由于主键为(tid,position),自增列为position,这种结构在InnoDB中是不被支持的。因此我们在插入帖子表之前先要通过tid查询该帖子目前的最大楼层数,然后再加1作为position的值。

# 查询得到该帖子的最大楼层(11)SELECT MAX(position) AS max FROM pre_forum_post WHERE tid=1234 # 插入帖子数据以及楼层数(12)INSERT INTO position SET tid=1234,position=12, ...

上面这种方案有一个问题就是在高并发情况下,多个进程通过SELECT查询同一个帖子的最大楼层数时,很可能得到相同的值,进而发表的帖子的楼层数相同,会导致许多用户发帖失败。但由于存在唯一索引的限制,只会有一个用户发帖成功,并不会存在相同楼层的情况。

高并发时发帖失败的问题

因为SELECT查询的速度很快,并且SELECT操作加的是共享锁,所以在高并发时会有多个进程读取到相同的最大楼层数,导致发帖失败。

为了避免该问题的发送,可以采用悲观锁方案,在SELECT语句最后加上FOR UPDATE,并将SELECT和INSERT作为一个事务进行处理(由于Discuz的DB类不支持事务操作,需要使用mysqli进行数据库的操作)。

SELECT MAX(position) AS max FROM pre_forum_post WHERE tid=1234 FOR UPDATE

还有一种方案是将SELECT和INSERT操作合并为一条语句,由于mysql中每条语句都是一个原子操作,因此无需自己实现事务操作,更加方便。

INSERT INTO position SET tid=1234,position=(SELECT MAX(tmp.position)+1 AS max FROM pre_forum_post AS tmp WHERE tid=1234) ...

数据库引擎转换脚本

Discuz所有数据表以及结构
https://git.oschina.net/ComsenzDiscuz/DiscuzX/blob/master/upload/install/data/install.sql

a. 查询得到数据库所有数据库引擎为MyISAM的数据表

SHOW TABLE STATUS WHERE Engine='MyISAM'

这里一定要注意,Discuz中并不是所有的数据表都是MyISAM,有一些数据表是HEAP(MEMORY)。
因此不能使用 SHOW TABLES 命令将所有的数据表进行转换
Memory的数据是存储在内存中的,速度比InnDB快很多,如果进行转换,则反而会使论坛的性能下降。

b. 转换pre_common_member_grouppm和pre_common_member_grouppm数据表

ALTER TABLE `pre_common_member_grouppm` MODIFY COLUMN `gpmid`  smallint(6) UNSIGNED NOT NULL AFTER `uid`,ENGINE=InnoDB

pre_common_member_grouppm 虽然表结构中的gpmid是自增的,但是通过查阅代码发现在插入操作时,并没有使用该特性,因此无需对插入操作的代码进行改造。

ALTER TABLE `pre_forum_post` MODIFY COLUMN `position`  int(8) UNSIGNED NOT NULL AFTER `replycredit`, DROP PRIMARY KEY, ADD PRIMARY KEY (`pid`), DROP INDEX `pid`, ADD UNIQUE INDEX `tid_position` (`tid`, `position`) 

c. 转换其他数据表


ALTER TABLE XXX ENGINE = InnoDB

原创粉丝点击