由于公司準備用discuz建立企業級的論壇,所以需要進行百萬級數據的測試。
搜索了很久沒有看到相關的批量插入數據的資料,后來找到一個批量插入帖子的插件,但導入的txt文件超過2350條記錄就插入不了。最后只好硬著頭皮分析插入帖子的相關sql語句--直接插入到cdb_posts表前臺不會顯示的。
最后簡化為只有標題和內容的帖子需要在兩個表插入記錄,最后更新cdb_foums對應的一條數據即可。
由于是本人主要是開發java的對php不熟悉,所以測試代碼是用java寫的,但懂php的人根據我的思路用php也應該很容易搞定了。
最后分享下代碼:(用jdbc連接數據庫的,需要在java web項目下的lib目錄下導入mysq的jar包)
=====================================
DBManager.java
=====================================
package com.test.utc;
import java.sql.*;
public class DBManager {
//用戶名
private String user = "";
//密碼
private String password = "";
//主機
private String host = "";
//數據庫名字
private String database = "";
/*
private String url="jdbc:mysql://"+host+"/"+"useUnicode=true&characterEncoding=GB2312";
*/
private String url ="";
private Connection con = null;
Statement stmt;
/**
* 根據主機、數據庫名稱、數據庫用戶名、數據庫用戶密碼取得連接。
* @param host String
* @param database String
* @param user String
* @param password String
*/
public DBManager(String host, String database, String user, String password) {
this.host = host;
this.database = database;
this.user = user;
this.password = password;
//顯示中文
this.url = "jdbc:mysql://" + host + "/" + database +
"?useUnicode=true&characterEncoding=GB2312";
try {
Class.forName("org.gjt.mm.mysql.Driver");
}
catch (ClassNotFoundException e) {
System.err.println("class not found:" + e.getMessage());
}
try {
con = DriverManager.getConnection(this.url, this.user, this.password);
//連接類型為ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY
stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
}
catch (SQLException a) {
System.err.println("sql exception:" + a.getMessage());
}
}
/**
* 返回取得的連接
*/
public Connection getCon() {
return con;
}
/**
* 執行一條簡單的查詢語句
* 返回取得的結果集
*/
public ResultSet executeQuery(String sql) {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
}
catch (SQLException e) {
e.printStackTrace();
}
return rs;
}
/**
* 執行一條簡單的更新語句
* 執行成功則返回true
*/
public boolean executeUpdate(String sql) {
boolean v = false;
try {
v = stmt.executeUpdate(sql) > 0 ? true : false;
}
catch (SQLException e) {
e.printStackTrace();
}
finally {
return v;
}
}
}
======================
一次同時插入兩個表,for循環后更新數據。
為了方便同時加入了納米級別的時間差,并且把詳細信息輸出到指定文件中。
轉換16進制的單獨測試沒有問題,但轉換后作為sql語句插入總是提示過大或者是中文的轉換后不能識別,比較郁悶。不過這個不是很重要的問題。
插入的文件InsertIntoSQL .java如下:
======================
package com.test.utc;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.test.utc.DBManager;
public class InsertIntoSQL
{
public final static String path = "D:/ComsenzEXP/wwwroot/discuz/attachments/testLog.txt";
public static void main (String[] agrs)
{
//System.out.println(toHexString("測試"));
//System.out.println(toStringHex("0x74657374"));
insertData(300000,4,100000,"test","test");
}
/*
* @deprecated 插入數據
* @param startNumber
* @param endNumber
* @param fourmid
* @param postsNumber
* @param todayposts
*/
public static void insertData(long insertNumber,int fourmid,long todayposts,String title,String content)
{
String message = "插入:" + insertNumber + "條記錄";
System.out.println(message);
appendMethod(path,message);
long startTime = System.nanoTime();
message = "insertData startTime:" + startTime;
System.out.println(message);
appendMethod(path,message);
DBManager dbm = new DBManager("localhost:6033", "discuz", "discuz", "discuzexp");
String sql = "";
String pid = getLastRowByColumnValue(dbm,"cdb_posts","pid");
long startNumber = Long.parseLong(pid) + 1;
long endNumber = startNumber + insertNumber;
long i = 1;
//content = toHexString(content);
//0x6d4b8bd551855bb9
//0x6d4b8bd5
//String insertTitle = title;
for(i = startNumber; i < endNumber; i++)
{
//insertTitle = insertTitle + i;
//insertTitle = toHexString(insertTitle + i);
sql = "INSERT INTO cdb_posts VALUES ('" + i + "','" + fourmid + "','" + i + "','1',0x61646d696e,'1',0x74657374,'1221120302',0x74657374,0x3139322e3136382e302e3335,'0','0','0','0','-1','-1','0','0','0','0','0')";
dbm.executeUpdate(sql);
sql = "INSERT INTO cdb_threads VALUES ('" + i + "','" + fourmid + "','0','0','0','0',0x61646d696e,'1',0x74657374,'1221120302','1221120302',0x61646d696e,'1','0','0','0','0','0','0','0','0','0','0','0','0','0','0')";
dbm.executeUpdate(sql);
/*
sql = "INSERT INTO cdb_posts VALUES ('" + i + "','" + fourmid + "','" + i + "','1',0x61646d696e,'1','" + insertTitle + "','1221120302','" + content + "',0x3139322e3136382e302e3335,'0','0','0','0','-1','-1','0','0','0','0','0')";
dbm.executeUpdate(sql);
sql = "INSERT INTO cdb_threads VALUES ('" + i + "','" + fourmid + "','0','0','0','0',0x61646d696e,'1','" + insertTitle + "','1221120302','1221120302',0x61646d696e,'1','0','0','0','0','0','0','0','0','0','0','0','0','0','0')";
dbm.executeUpdate(sql);
*/
}
todayposts = endNumber - 499999;
sql = "UPDATE cdb_forums SET lastpost='0x74657374', threads=" + endNumber + ", posts=" + endNumber + ", todayposts=" + todayposts + " WHERE fid=" + fourmid;
dbm.executeUpdate(sql);
message = "insertData endTime:" + System.nanoTime();
System.out.println(message);
appendMethod(path,message);
long estimatedTime = System.nanoTime() - startTime;
message = "相差(用時):" + estimatedTime + "納秒," + estimatedTime/1000000000 + "秒\r\n\r\n";//1秒=10億納秒
System.out.println(message);
appendMethod(path,message);
}
/*
* @deprecated 獲取最后一行某列的值
* @param table
* @param column
*/
public static String getLastRowByColumnValue(DBManager dbm,String table,String column)
{
String sql = "";
sql = "select " + column + " from " + table + " order by " + column + " desc limit 1";
ResultSet rs = dbm.executeQuery(sql);
String str = "";
try {
while( rs.next())
{
str = rs.getString(column);
}
} catch (SQLException e) {
e.printStackTrace();
}
return str;
}
//轉化字符串為十六進制編碼
public static String toHexString(String s)
{
String str="";
for (int i=0;i<s.length();i++)
{
int ch = (int)s.charAt(i);
String s4 = Integer.toHexString(ch);
str = str + s4;
}
return "0x" + str;
}
/**
* B方法追加文件:使用FileWriter
* @param fileName
* @param content
*/
public static void appendMethod(String fileName, String content){
try {
//打開一個寫文件器,構造函數中的第二個參數true表示以追加形式寫文件
FileWriter writer = new FileWriter(fileName, true);
writer.write(content + "\r\n");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
=========================
測試結果
=========================
插入:1條記錄
insertData startTime:996017275659
insertData endTime:996570971450
相差:555546585納秒,0秒
插入:10條記錄
insertData startTime:1005210041220
insertData endTime:1005774437812
相差:566248503納秒,0秒
插入:100條記錄
insertData startTime:1014529111102
insertData endTime:1015376139146
相差:848823244納秒,0秒
插入:1000條記錄
insertData startTime:1024027419406
insertData endTime:1027460295093
相差:3434544614納秒,3秒
插入:10000條記錄
insertData startTime:1098779316644
insertData endTime:1116864493392
相差:18086540050納秒,18秒
插入:20000條記錄
insertData startTime:1172427124676
insertData endTime:1207721237196
相差:35295511580納秒,35秒
插入:30000條記錄
insertData startTime:1221623540422
insertData endTime:1273396534755
相差:51774426358納秒,51秒
插入:40000條記錄
insertData startTime:1304022174326
insertData endTime:1373074817609
相差:69054081175納秒,69秒
插入:50000條記錄
insertData startTime:1387615577957
insertData endTime:1474072398866
相差:86458228630納秒,86秒
插入:60000條記錄
insertData startTime:1498220329120
insertData endTime:1602732788105
相差:104513895481納秒,104秒
插入:70000條記錄
insertData startTime:1713667130878
insertData endTime:1834998856279
相差:121333178658納秒,121秒
插入:80000條記錄
insertData startTime:1859249070482
insertData endTime:1998321805424
相差:139074161939納秒,139秒
插入:90000條記錄
insertData startTime:2025945141631
insertData endTime:2182073256517
相差:156129545235納秒,156秒
插入:100000條記錄
insertData startTime:2220057424616
insertData endTime:2395134985871
相差:175078943553納秒,175秒
插入:120000條記錄
insertData startTime:2658822511240
insertData endTime:2866959720299
相差:208138247459納秒,208秒
插入:150000條記錄
insertData startTime:2886769641392
insertData endTime:3146130152016
相差:259361930078納秒,259秒
插入:180000條記錄
insertData startTime:3482758554788
insertData endTime:3795354906559
相差:312598627479納秒,312秒
插入:200000條記錄
insertData startTime:4023631035788
insertData endTime:4370300796888
相差(用時):346671225253納秒,346秒
插入:250000條記錄
insertData startTime:4460471629494
insertData endTime:4894180641115
相差(用時):433710430516納秒,433秒
插入:300000條記錄
insertData startTime:4971718038186
insertData endTime:5489207947378
相差(用時):517491270818納秒,517秒
========================
另外分享幾條刪除,更新的sql語句
DELETE FROM cdb_posts where pid > 1000000
DELETE FROM cdb_threads where tid > 1000000
已刪除行數: 1262140 (查詢花費 130.2156 秒)
UPDATE cdb_forums SET lastpost='0x74657374', threads=1000000, posts=1000000, todayposts=500001 WHERE fid=4;
select pid from cdb_posts order by pid desc limit 1;
============================
最后通過loadrunner模擬用戶點擊頁面普通頁面,和在100萬條數據的表下面查詢20條數據的頁面(通過后臺js調用設置就可以了)。
希望官方和那位高手能整出更加高效,輕松實現的方法來。
=================================================
后來發現每個版塊最多只能1000分頁--雖然上面顯示了25000頁,但點到1000頁時后面就沒有分頁了,直接輸入1001就直接跳轉到1000也那里了。也就是說一個版塊前臺用戶看到的最多文章主題只有20000條。無法做到真正的100萬數據量(數據庫里面確實是100萬主題數)。
最后發現這些越來是可以在后臺設置的。后臺-->全局-->界面與顯示-->主題列表頁-->每頁顯示主題數:
主題列表中每頁顯示主題數目
主題列表最大頁數:
主題列表中用戶可以翻閱到的最大頁數,建議設置為默認值 1000,或在不超過 2500 范圍內取值,0 為不限制
==================================================================================
后記:沒想到自己發的這篇文章受到這么多人的關注,順便對回復的問題作個概要回答:
儲存過程是我一開始考慮到的,但為了模擬真實插入數據的效率后來否定了。
Oracle SQL Loader 工具對這個應用沒有用,因為這個是要用來跟其他系統作對比的,使用的都是mysql,批量提交的方法也測試過了,受到文件大小的限制一次最多只能插入兩千條。
非常歡迎給更多的人分享,如果轉載文章中能注明作者或網址就更好了。