日期:2014-05-16  浏览次数:20464 次

数据库表设计中使用横向表切割来降低并发冲突

近日在设计《威博文件管理系统》的文件标签系统中的一点体会。

?

近来根据用户反馈和原定的开发计划,为《威博文件管理系统》设计一个“文件标签”模块,实现的功能类似于很多大型网站的标签功能,能够实现智能标签功能。

?

开始的数据库表结构,使用一个单一的“文件标签”表,文件标签表,结构如下:

?

主键:char(36)

标签名称:varchar(50)

标签排序:int

主观权重:bit

主观推荐:bit

时间戳:bigint

版本号:int

引用计数:int

点击计数:int

?

@Entity
@Table(name = "文件标签")
public class FileTagModel implements Serializable {

    /**
     * 使用java的UUID唯一号,36长度的字符串
     */
    @Id
    @Column(name = "主键")
    private String id;
    /**
     * 标签名称
     */
    @Column(name = "标签名称")
    private String tagName;
    /**
     * 标签排序
     */
    @Column(name = "标签排序")
    private int px;
    /**
     * 主观权重
     */
    @Column(name = "主观权重")
    private boolean tagWeight;
    /**
     * 主观推荐
     */
    @Column(name = "主观推荐")
    private boolean tagSuggest;
    /**
     * 实体创建时的时间戳
     */
    @Column(name = "时间戳")
    private long timestamp;
    /**
     * 实体对象的版本号
     */
    @Column(name = "版本号")
    @Version
    private int version;


    /**
     * 点击计数
     */
    @Column(name = "点击计数")
    private int clickCount;
    /**
     * 引用计数
     */
    @Column(name = "引用计数")
    private int quoteCount;


}

?

经过一段时间的测试后发现,“文件标签”表中,各个字段的的变化速度有很大的不同,其中引用计数、点击计数、变化很频繁,而其它字段的内容相对变化 就比较稳定,一旦生成,相对比较稳定,这样在并发程度高时,常常导致数据更新冲突,虽然可以通过@version注解可以保证数据的乐观锁定发生作用,数 据的一致性不被破坏。但是由于把变化频度差异很大的字段放在同一张表上,导致冲突发生的概率增大,降低了系统的效能。

?

于是想到了对该表的横向切割,以便获得更好的并发性能。

?

切割后,分为三个表,《文件标签》作为主表,《文件标签引用计数》《文件标签点击计数》作为从表,三个表共用主键,三表结构如下:

?

《文件标签》表结构

主键:char(36)

标签名称:varchar(50)

标签排序:int

主观权重:bit

主观推荐:bit

时间戳:bigint

版本号:int

?

《文件标签引用计数》表结构

主键:char(36)

引用计数:int

时间戳:bigint

版本号:int

?

《文件标签点击计数》表结构

主键:char(36)

点击计数:int

时间戳:bigint

版本号:int

?

?

《文件标签》表实体Bean

package cn.sh.webfile.model;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
import javax.persistence.Version;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;

/**
 * 开始于 2010-06-09
 * 文件标签符号表的模型
 * [2010-06-18修]新增加一个字段
 * [2010-06-21修]根据应用程序的使用反馈,把引用计数、点击计数字段,放入两个不同的表
 * 以实现更好的多工状态下的并发操作,减少状态冲突。分拆理由,表《文件标签》主表、《文件标签引用计数》、
 * 《文件标签点击计数》作为从表,主表变化慢,从表变化快。
 * 最后修改于 2010-06-21
 * @author 万继斌
 * @version 1.3
 */
@Entity
@Table(name = "文件标签")
public class FileTagModel implements Serializable {

    /**
     * 使用java的UUID唯一号,36长度的字符串
     */
    @Id
    @Column(name = "主键")
    private String id;
    /**
     * 标签名称
     */
    @Column(name = "标签名称")
    private String tagName;
    /**
     * 标签排序
     */
    @Column(name = "标签排序")
    private int px;
    /**
     * 主观权重
     */
    @Column(name = "主观权重")
    private boolean tagWeight;
    /**
     * 主观推荐
     */
    @Column(name = "主观推荐")
    private boolean tagSuggest;
    /**
     * 实体创建时的时间戳
     */
    @Column(name = "时间戳")
    private long timestamp;
    /**
     * 实体对象的版本号
     */
    @Column(name = "版本号")
    @Version
    private int version;


    /**
     * 点击计数
     */
    @OneToOne
    @PrimaryKeyJoinColumn
    private FileTagClickCountModel clickCount;
    /**
     * 引用计数
     */
    @OneToOne
    @PrimaryKeyJoinColumn
    private FileTagQuoteCountModel quoteCount;



    @Override
    public String toString() {
        return ReflectionToStringBuilder.toString(this);
    }

    /**
     * @return the id
     */
    public String getId() {
        return id.trim();
    }

    /**
     * @param id the id to set
     */
    public void setId(String id) {
        this.id = id.trim();
    }

    /**
     * @return the timestamp
     */
    public long getTimestamp()