劉勇 Email:lyssym@sina.com
簡介
鑒於內存數據庫訪問速率快的特點,本文分別從單線程、多線程(並發訪問)和多線程讀/寫混合訪問角度對eXtremeDB數據庫讀寫速率展開測試。需要指出的是,本文讀取操作包含將數據讀取后,並在控制台顯示出來。測試結果表明:eXtremeDB在單一讀/寫訪問時,速率大約在10w條/s,其速率是比較快的;同時相對單線程來說,多線程讀或者寫操作並發訪問eXtremeDB,也並未衰減其性能,因此在一定程度上可以滿足並發訪問需求;另一方面,多線程讀/寫混合訪問eXtremeDB時,單個線程寫入速率大約在10w條/s,單個線程讀取速率大約在4w條/s,此外,隨着讀/寫線程個數的增加,其讀寫速率在整體上趨於穩定。經過上述測試,該數據庫適合於嵌入式系統設計,對於有存儲需求的實時系統來說,可以采用內存與硬盤混合方式 ,但是該策略必然會衰減其性能。
測試環境
硬件環境:
Localhost:CPU:Intel Core I5;主頻:3.10G;內存:4G
軟件環境:
Localhost: extremedb_6.0_im_win32vs2012_x86_sql_eval.EXE;jdk 1.8
表結構:

1 DROP TABLE IF EXISTS `TAQ`; 2 CREATE TABLE `TAQ` ( 3 `SECCODE` varchar(6) NOT NULL, 4 `SECNAME` varchar(20) NOT NULL, 5 `TDATE` varchar(10) NOT NULL, 6 `TTIME` varchar(6) NOT NULL, 7 `LASTCLOSE` decimal(19,3) DEFAULT NULL, 8 `OP` decimal(19,3) DEFAULT NULL, 9 `CP` decimal(19,3) DEFAULT NULL, 10 `TQ` decimal(19,3) DEFAULT NULL, 11 `TM` decimal(19,3) DEFAULT NULL, 12 `CQ` decimal(18,0) DEFAULT NULL, 13 `CM` decimal(19,3) DEFAULT NULL, 14 `CT` decimal(19,3) DEFAULT NULL, 15 `BS` varchar(18) DEFAULT NULL, 16 `BSRATIO` decimal(19,3) DEFAULT NULL, 17 `SPD` decimal(19,3) DEFAULT NULL, 18 `RPD` decimal(19,3) DEFAULT NULL, 19 `UNIX` bigint(20) DEFAULT NULL, 20 `MARKET` varchar(4) DEFAULT NULL, 21 KEY `SECCODE` (`SECCODE`,`TDATE`,`TTIME`) 22 ) /*!50100 TABLESPACE ts_cloudstore STORAGE DISK */ ENGINE=ndbcluster DEFAULT CHARSET=utf8;
性能測試
本文先從單線程和多線程(並發訪問)和2個角度,以60K、100K和600K條為基礎數據總量,對eXtremeDB內存數據庫展開測試。其中單線程部分,以單條測試和批處理2個方面展開測試,並進行對比;多線程部分則主要以不同線程個數對eXtremeDB進行測試。需要指出的是,本文讀取操作包含將數據讀取后,並在控制台顯示出來,而寫入數據數據部分,則以數據寫入數據為結束節點。然后從多線程讀/寫混合訪問eXtremeDB角度,以100K和600K條數據為基礎數據庫,針對不同讀取線程和寫入線程個數展開測試。
單線程訪問
以60K、100K和600K條為基礎數據總量,在單條和批處理下對I/O進行測試,其中單條下測試結果如表-1所示。
批處理以批處理數據量為1000,2000和3000條角度展開測試,主要針對批處理寫入速率,由於本文讀取操作涉及從數據庫讀取數據並在控制台顯示出來,其處理瓶頸主要集中於數據顯示部分,因此本部分讀取速率暫不考慮,其測試結果如表-2所示。
小結
從表-1和表-2可知:1)從寫入速率角度來看,批處理相對單條處理而言,並沒有優勢,主要原因在於,eXtremeDB為內存數據庫,批處理在內存中還多了一份批量累積過程;2)從整體而言, 相對之前對MySQL(即使是在固態硬盤本地,詳細內容見以前測試報告),eXtremeDB的讀寫速率還是比較快的,測試速率大約為10w條/s。
多線程訪問
以60K、100K和600K條為基礎數據總量,在不同線程個數下,對eXtremeDB展開讀寫訪問,其中在不同線程個數下的寫入速率如表-3所示。需要指出的是,此處多線程寫入測試,以多線程處理平攤的方式,即寫入60K條數據,采用20個線程執行,則每個線程處理3K(60K/20)條數據,詳細內容見測試源代碼,在此不再贅述。
在不同線程下的讀取速率如表-4所示。需要指出的是,此處多線程讀取速率,每個線程則完整讀取給定的數據總量,即若數據總量為60K條,則每個線程讀取60K數據,獲取全表中指定的2個字段,並顯示出來。
小結
從表-3和表-4可知:1)隨着數據總量的增加,多線程訪問速率整體上變化不明顯,即線程讀寫速率相對比較穩定;2)與表-1相比較,針對多線程,若以平均速率乘以線程個數角度來說,則多線程與單線程在讀取數據並顯示出來上,兩者速率差不多,此外,多線程寫入和單線程寫入的速率也差不多(注意多線程寫入是平攤方式,兩者可以直接比較),因此,多線程訪問eXtremeDB,相對單線程並未衰減其性能。
總結
從上述2種場景測試結果來看,eXtremeDB的讀寫速率大約在10w條/s,相對一般的數據庫(非內存數據庫,即使在固態硬盤上)其速率也是比較快的。此外,針對多線程單一讀/寫並發訪問,與單線程相比,也並未衰減其讀寫性能,因此對於並發訪問來說,也能滿足一定的需求。
測試源程序:

1 import com.mcobject.extremedb.*; 2 import java.sql.Date; 3 import java.math.BigDecimal; 4 5 @Persistent // class will be stored in eXtremeDB database 6 class TestTable { 7 @Indexable(unique=true) 8 public String secCode; 9 10 public String secName; 11 public Date tDate; 12 public Date tTime; 13 14 public BigDecimal lastClose; 15 public BigDecimal OP; 16 public BigDecimal CP; 17 public BigDecimal TQ; 18 public BigDecimal TM; 19 public BigDecimal CQ; 20 public BigDecimal CM; 21 public BigDecimal CT; 22 public String BS; 23 24 public BigDecimal BSRATIO; 25 public BigDecimal SPD; 26 public BigDecimal RPD; 27 public long UNIX; 28 public String market; 29 }

1 import com.mcobject.extremedb.*; 2 import java.math.BigDecimal; 3 import java.math.RoundingMode; 4 import java.sql.*; 5 6 public class DataAccess { 7 public static int PAGE_SIZE = 128; 8 public static int DATABASE_SIZE = 512*1024*1024; 9 public static int MAXCONN = 100; 10 public static int NUM = 100*1000; 11 public static int BATCH = 1000; 12 private Database db; 13 private String sql; 14 private SqlLocalConnection con; 15 16 public void initDatabase() 17 { 18 int config = Database.MCO_CFG_SQL_SUPPORT; 19 Database.Parameters params = new Database.Parameters(); 20 params.memPageSize = PAGE_SIZE; 21 params.classes = new Class[] {TestTable.class}; 22 params.maxConnections = MAXCONN; 23 24 Database.Device device[] = new Database.Device[1]; 25 device[0] = new Database.PrivateMemoryDevice(Database.Device.Kind.Data, DATABASE_SIZE); 26 db = new Database(config); 27 db.open("DataAccess", params, device); 28 sql = "select secCode, secName from TestTable"; 29 con = db.connectSql(); 30 } 31 32 33 public void insertTransaction(TestTable table) 34 { 35 int code = 100000; 36 long start = getRunTime(); 37 for (int i = 0; i < NUM; i++) 38 { 39 con.startTransaction(Database.TransactionType.ReadWrite); 40 table.secCode = Integer.toString(code); 41 table.secName ="中國銀行"; 42 table.tDate = new Date(System.currentTimeMillis()); 43 table.tTime = new Date(System.currentTimeMillis()+5); 44 table.UNIX += 1; 45 con.insert(table); 46 con.commitTransaction(); 47 code++ ; 48 } 49 long end = getRunTime(); 50 System.out.println("寫入速率為: " + NUM*1000/(end-start)); 51 con.saveSnapshot("db.img"); 52 } 53 54 55 public long getRunTime() 56 { 57 return System.currentTimeMillis(); 58 } 59 60 61 public void getTransaction() 62 { 63 long start = getRunTime(); 64 con.startTransaction(Database.TransactionType.ReadWrite); 65 SqlResultSet result = con.executeQuery(sql); 66 for (String column : result.getColumnNames()) { 67 System.out.print(column + " "); 68 } 69 System.out.println(); 70 71 for (SqlTuple tuple : result) { 72 System.out.println(tuple.get("secCode") + " " + tuple.get("secName") + " "); 73 } 74 con.commitTransaction(); 75 76 long end = getRunTime(); 77 System.out.println("讀取速率為: " + NUM*1000/(end-start)); 78 } 79 80 81 public void closeDatabase() 82 { 83 con.disconnect(); 84 db.close(); 85 } 86 87 88 public void deleteData() 89 { 90 con.startTransaction(Database.TransactionType.ReadWrite); 91 con.removeAll(TestTable.class); 92 con.commitTransaction(); 93 System.out.println("delete all the data!"); 94 } 95 96 public static void main(String[] args) { 97 // TODO Auto-generated method stub 98 TestTable table = new TestTable(); 99 table.secCode = null; 100 table.secName = null; 101 table.tDate = null; 102 table.tTime = null; 103 104 table.lastClose = new BigDecimal(15.857).setScale(3, RoundingMode.HALF_UP); 105 table.OP = new BigDecimal(10.132).setScale(3, RoundingMode.HALF_UP); 106 table.CP = new BigDecimal(12.310).setScale(3, RoundingMode.HALF_UP); 107 table.TQ = new BigDecimal(14.185).setScale(3, RoundingMode.HALF_UP); 108 table.TM = new BigDecimal(19.107).setScale(3, RoundingMode.HALF_UP); 109 table.CQ = new BigDecimal(8.457).setScale(3, RoundingMode.HALF_UP); 110 table.CM = new BigDecimal(7.859).setScale(3, RoundingMode.HALF_UP); 111 table.CT = new BigDecimal(13.101).setScale(3, RoundingMode.HALF_UP); 112 table.BS = null; 113 114 table.BSRATIO = new BigDecimal(18.525).setScale(3, RoundingMode.HALF_UP); 115 table.SPD = new BigDecimal(6.108).setScale(3, RoundingMode.HALF_UP); 116 table.RPD = new BigDecimal(3.199).setScale(3, RoundingMode.HALF_UP); 117 table.UNIX = System.currentTimeMillis(); 118 table.market = "SSE"; 119 120 DataAccess da = new DataAccess(); 121 da.initDatabase(); 122 da.insertTransaction(table); 123 // da.getTransaction(); 124 // da.deleteData(); 125 da.closeDatabase(); 126 } 127 128 }
並發讀/寫混合測試
本文先構建100K和600K條的基礎數據量,以避免讀取線程初始讀取數據失敗情形。然后在該基礎數據量的基礎上,寫入數據總量為100K條的數據,其中寫入操作采用平攤的方式,即寫入的總數據量平攤至每個寫入線程中,而讀取操作則保持在每個線程讀取100K條的數據,詳細內容見測試源代碼。
以下從基礎數據量為100K和600K條2個方面對eXtremeDB展開測試。
100K
從不同線程個數角度對該數據庫展開測試,其中讀/寫線程各占50%,即若表格中線程個數為10個時,讀/寫線程各占5個,后續表格內容與之類同,不再贅述了。寫入操作以寫入總量100K條數據,並平攤給每個寫入線程,讀取操作中每個讀取線程執行 "select secCode, secName from TestTable limit 100000"操作。基礎數據量為100K條的測試結果見表-5。
600K
寫入操作以寫入總量100K條數據,並平攤給每個寫入線程,讀取操作中每個讀取線程執行 "select secCode, secName from TestTable limit 100000, 100000"操作。基礎數據量為600K條下的測試結果見表-6。
總結
從表-5和表-6可知:1)多線程讀寫混合訪問eXtremeDB的單個線程寫入速率大約在10w條/s, 單個線程讀取速率大約在4w條/s;2)隨着讀取線程和寫入線程的增加,eXtremeDB的讀寫速率變化不大,整體上比較穩定。
並發讀/寫測試源代碼:

1 import com.mcobject.extremedb.*; 2 import java.sql.Date; 3 import java.math.BigDecimal; 4 5 @Persistent // class will be stored in eXtremeDB database 6 class TestTable { 7 @Indexable(unique=true) 8 public String secCode; 9 10 public String secName; 11 public Date tDate; 12 public Date tTime; 13 14 public BigDecimal lastClose; 15 public BigDecimal OP; 16 public BigDecimal CP; 17 public BigDecimal TQ; 18 public BigDecimal TM; 19 public BigDecimal CQ; 20 public BigDecimal CM; 21 public BigDecimal CT; 22 public String BS; 23 24 public BigDecimal BSRATIO; 25 public BigDecimal SPD; 26 public BigDecimal RPD; 27 public long UNIX; 28 public String market; 29 }

1 import java.sql.Date; 2 import java.io.BufferedWriter; 3 import java.io.FileOutputStream; 4 import java.io.IOException; 5 import java.io.OutputStreamWriter; 6 import com.mcobject.extremedb.Database; 7 import com.mcobject.extremedb.SqlLocalConnection; 8 9 public class WriteThread extends Thread { 10 private int code; 11 private Database db; 12 private int id; 13 private SqlLocalConnection con; 14 private TestTable table; 15 16 public WriteThread(Database db, int code, TestTable table, int id) 17 { 18 this.db = db; 19 this.code = code; 20 this.table = table; 21 this.id = id; 22 con = db.connectSql(); 23 con.startTransaction(Database.TransactionType.ReadWrite); 24 } 25 26 27 public void run() 28 { 29 insertTransaction(); 30 closeConnection(); 31 } 32 33 34 public void insertTransaction() 35 { 36 long start = getRunTime(); 37 for (int i = 0; i < MultThreadAccess.NUM*2/MultThreadAccess.THREAD; i++) 38 { 39 con.startTransaction(Database.TransactionType.ReadWrite); 40 table.secCode = Integer.toString(code); 41 table.secName = "中國銀行"; 42 table.tDate = new Date(System.currentTimeMillis()); 43 table.tTime = new Date(System.currentTimeMillis()+5); 44 table.UNIX += 1; 45 con.insert(table); 46 con.commitTransaction(); 47 code++ ; 48 } 49 long end = getRunTime(); 50 51 try { 52 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(id%5+5 + ".txt", true))); 53 String str = Long.toString(MultThreadAccess.NUM*2/MultThreadAccess.THREAD*1000/(end-start)); 54 bw.write(str); 55 bw.write("\n"); 56 bw.flush(); 57 bw.close(); 58 } catch (IOException e) { 59 // TODO Auto-generated catch block 60 e.printStackTrace(); 61 } 62 } 63 64 65 public long getRunTime() 66 { 67 return System.currentTimeMillis(); 68 } 69 70 71 public void closeConnection() 72 { 73 con.disconnect(); 74 } 75 76 }

1 import java.io.BufferedWriter; 2 import java.io.FileOutputStream; 3 import java.io.IOException; 4 import java.io.OutputStreamWriter; 5 import com.mcobject.extremedb.Database; 6 import com.mcobject.extremedb.SqlLocalConnection; 7 import com.mcobject.extremedb.SqlResultSet; 8 import com.mcobject.extremedb.SqlTuple; 9 10 public class ReadThread extends Thread { 11 private Database db; 12 private String sql; 13 private SqlLocalConnection con; 14 private int id; 15 16 public ReadThread (Database db, int id) 17 { 18 this.db = db; 19 sql = "select secCode, secName from TestTable limit 100000"; 20 this.id = id; 21 con = db.connectSql(); 22 } 23 24 25 public void run() 26 { 27 getTransaction(); 28 closeConnection(); 29 } 30 31 32 public void getTransaction() 33 { 34 long start = getRunTime(); 35 SqlResultSet result = con.executeQuery(sql); 36 for (String column : result.getColumnNames()) { 37 System.out.print(column + " "); 38 } 39 System.out.println(); 40 41 for (SqlTuple tuple : result) 42 System.out.println(tuple.get("secCode") + " " + tuple.get("secName") + " "); 43 44 con.commitTransaction(); 45 long end = getRunTime(); 46 47 try { 48 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(id%5 + ".txt", true))); 49 String str = Long.toString(100000*1000/(end-start)); 50 bw.write(str); 51 bw.write("\n"); 52 bw.flush(); 53 bw.close(); 54 } catch (IOException e) { 55 // TODO Auto-generated catch block 56 e.printStackTrace(); 57 } 58 } 59 60 61 public long getRunTime() 62 { 63 return System.currentTimeMillis(); 64 } 65 66 67 public void closeConnection() 68 { 69 con.disconnect(); 70 } 71 72 }

1 import java.math.BigDecimal; 2 import java.math.RoundingMode; 3 import com.mcobject.extremedb.Database; 4 5 public class MultThreadAccess { 6 public static int PAGE_SIZE = 128; 7 public static int DATABASE_SIZE = 1024*1024*1024; 8 public static int MAXCONN = 100; 9 public static int NUM = 100*1000; 10 public static int THREAD = 2; 11 private Database db; 12 13 14 public void initDataBase() 15 { 16 int config = Database.MCO_CFG_SQL_SUPPORT; 17 Database.Parameters params = new Database.Parameters(); 18 params.memPageSize = PAGE_SIZE; 19 params.databaseSnapshotFilePath = "db.img"; 20 params.classes = new Class[] {TestTable.class}; 21 params.maxConnections = MAXCONN; 22 23 Database.Device device[] = new Database.Device[1]; 24 device[0] = new Database.PrivateMemoryDevice(Database.Device.Kind.Data, DATABASE_SIZE); 25 db = new Database(config); 26 db.open("DataAccess", params, device); 27 System.out.println("OK"); 28 } 29 30 31 public void closeDataBase() 32 { 33 db.close(); 34 } 35 36 37 public Database getDB() 38 { 39 return db; 40 } 41 42 43 public static void main(String[] args) { 44 // TODO Auto-generated method stub 45 TestTable []table = new TestTable[THREAD]; 46 for (int i = 0; i < THREAD; i++) { 47 table[i] = new TestTable(); 48 table[i].secCode = null; 49 table[i].secName = null; 50 table[i].tDate = null; 51 table[i].tTime = null; 52 53 table[i].lastClose = new BigDecimal(15.857).setScale(3, RoundingMode.HALF_UP); 54 table[i].OP = new BigDecimal(10.132).setScale(3, RoundingMode.HALF_UP); 55 table[i].CP = new BigDecimal(12.310).setScale(3, RoundingMode.HALF_UP); 56 table[i].TQ = new BigDecimal(14.185).setScale(3, RoundingMode.HALF_UP); 57 table[i].TM = new BigDecimal(19.107).setScale(3, RoundingMode.HALF_UP); 58 table[i].CQ = new BigDecimal(8.457).setScale(3, RoundingMode.HALF_UP); 59 table[i].CM = new BigDecimal(7.859).setScale(3, RoundingMode.HALF_UP); 60 table[i].CT = new BigDecimal(13.101).setScale(3, RoundingMode.HALF_UP); 61 table[i].BS = null; 62 63 table[i].BSRATIO = new BigDecimal(18.525).setScale(3, RoundingMode.HALF_UP); 64 table[i].SPD = new BigDecimal(6.108).setScale(3, RoundingMode.HALF_UP); 65 table[i].RPD = new BigDecimal(3.199).setScale(3, RoundingMode.HALF_UP); 66 table[i].UNIX = System.currentTimeMillis(); 67 table[i].market = "SSE"; 68 } 69 70 int code = 700000; 71 MultThreadAccess mt = new MultThreadAccess(); 72 mt.initDataBase(); 73 WriteThread[] wThread = new WriteThread[THREAD/2]; 74 ReadThread[] rThread = new ReadThread[THREAD/2]; 75 for (int i = 0; i < THREAD/2; i++) { 76 wThread[i] = new WriteThread(mt.getDB(), code+i*NUM/THREAD, table[i], i); 77 rThread[i] = new ReadThread(mt.getDB(), i); 78 wThread[i].start(); 79 rThread[i].start(); 80 } 81 82 try { 83 while(true); 84 } catch (Exception e) { 85 mt.closeDataBase(); 86 } 87 88 } 89 }
作者:志青雲集
出處:http://www.cnblogs.com/lyssym
如果,您認為閱讀這篇博客讓您有些收獲,不妨點擊一下右下角的【推薦】。
如果,您希望更容易地發現我的新博客,不妨點擊一下左下角的【關注我】。
如果,您對我的博客所講述的內容有興趣,請繼續關注我的后續博客,我是【志青雲集】。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。