公司数据库使用的是MySQL5.0.37-log,不支持分区,在数据量较大又没有使用分布式的情况下,我们通常的做法是在应用端实现表散列,用一个简单的hash算法将数据分散到多张表中。
以一个记录用户行为,判断用户在指定时间内行为是否有效的功能为例来说明,实现方式如下:
1.散列表的张数以及散列表的命名方式需要能够在外部的配置文件中实现可配置,在这里用一个简单的配置文件action.properties来实现
TABLE_HASH_SEED=100
TABLE_PREFIX=action_record_
2.建表时确定好索引
private void createTable() throws IOException {
Properties properties = new Properties();
InputStream is = ActionDao.class.getClassLoader().getResourceAsStream("action.properties");
properties.load(is);
TABLE_HASH_SEED = Integer.parseInt(properties.getProperty("TABLE_HASH_SEED"));
TABLE_PREFIX = properties.getProperty("TABLE_PREFIX");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < TABLE_HASH_SEED; i++) {
String tableName = TABLE_PREFIX + i;
sb.append("CREATE TABLE IF NOT EXISTS `" + tableName + "` (");
sb.append("`id` int(11) NOT NULL AUTO_INCREMENT,");
sb.append("`source` bigint(20) NOT NULL,");
sb.append("`target` bigint(20) NOT NULL,");
sb.append("`action_type` varchar(255) default NULL,");
sb.append("`time_` datetime DEFAULT NULL,");
sb.append("PRIMARY KEY (`id`),");
sb.append("KEY `action_index` (`source`,`target`)");
sb.append(") ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;");
jdbcTemplate.getJdbcOperations().execute(sb.toString());
sb.delete(0, sb.length());
}
}
3.hash算法用一个简单的取模来实现,将数据分散在指定的N张表内,获取表明的方法
/**
* 获取表名
* @param source 行为发起者
* @param target 行为接受者
* @return
*/
public String getTableName(long source, long target){
if(source != 0){
return TABLE_PREFIX + source % TABLE_HASH_SEED;
}else{
return TABLE_PREFIX + target % TABLE_HASH_SEED;
}
}
4.插入数据时,先确定需要插入的表名,然后再执行插入操作
/**
* 插入记录
* @param source 行为发起者
* @param target 行为接受者
* @param actionType 行为类型
*/
public void addRecord(long source, long target, ActionType actionType) {
String tableName = getTableName(source, target);
Date now = new Date();
String sql = "INSERT INTO " + tableName + " (source,target,action_type,time_) VALUES (?,?,?,?)";
jdbcTemplate.update(sql, source, target, actionType.toString(), now);
}
5判断行为是否合法的方法
/**
* 判断用户本次操作是否合法
* @param source 行为发起方
* @param target 行为接受方
* @param actionType 行为类型
* @param legalCount 指定时间间隔内合法的操作次数
* @param interval 时间间隔,时间段为:<br>start:本次操作的时间 - 时间间隔 end:当前时间
* @param isRecord 若合法,是否增加相应的记录
* @return
*/
public boolean isIllegal (long source, long target, ActionType actionType, long legalCount, long interval, boolean isRecord){
String tableName = actionDao.getTableName(source, target);
try {
lock.lock();
long count = 0;
String sql = "SELECT COUNT(*) FROM " + tableName + " WHERE 1=1";
if(source != 0){
sql += " AND source=" + source;
}
if(target != 0){
sql += " AND target=" + target;
}
sql += " AND action_type='" + actionType.toString();
if(interval == DAY){
sql += "' AND DATE(time_)=CURDATE()";
}else{
sql += "' AND time_>DATE_SUB(NOW(),INTERVAL " + interval + " SECOND)";
}
count = actionDao.getJdbcTemplate().queryForLong(sql);
if(count >= legalCount){
return false;
}else{
if(isRecord){
actionDao.addRecord(source, target, actionType);
}
return true;
}
} catch (DataAccessException e) {
logger.info("check user action error", e);
return false;
} finally{
lock.unlock();
}
}
当然,对于这样的功能,使用memcache这样的缓存来实现,速度自然不在一个数量级。在分表的情况下,稍微复杂一点的条件查询都会成为恶梦,夸多表的查询对于系统系能的影响也是无法接受的。有时间研究一下MySQL 5.1的分表,看看性能如何。