您好,登錄后才能下訂單哦!
這幾天看源碼弄清了一件事:WEB服務器接收瀏覽器請求、將請求傳給PHP/Python進程(FCGI等)、與數據庫進行交互都是用socket(套接字)。
也就是說,這些行為都是進程間通信。一臺WEB服務器在硬件、操作系統不變的情況下,它的性能主要取決于socket通信的速度。如果所有進程都在一臺服務器上的話,這個速度就取決于通信的效率了。
例如與MySQL數據庫交互時,程序會調用驅動程序來訪問數據庫,這個驅動程序主要做這幾件事:
1.創建socket,連接到MySQL。
2.將程序調用的API翻譯成SQL語句,通過socket發送給MySQL;MySQL執行后,將結果發送回來;驅動程序將結果(字符串)傳給程序,如有需要,還可以自動翻譯成程序能識別的變量類型(如整型)。
3.斷開連接。
可見連接的速度、翻譯的速度和接受響應的及時性是最主要的3個方面。
弄明白這點后就不難發現,與數據庫的執行時間相比,這些只是很少的一部分;而且PHP和Python使用的都是C實現,而JDBC是用Java實現,所以根本不必擔心PHP和Python的性能。
不過在翻譯方面還存在算法和實現上的差異,客戶端還可以緩存一些語句,所以仍然會出現一些性能上的差異。
為證明我的想法,我特意去測試了一番。
首先列出測試平臺:
引用
CPU:Intel Core2 Duo T9400 @ 2.53GHz
內存:3GB
操作系統:Windows XP Pro SP2
MySQL:5.1.36
Java:1.6.0_17-b04
JDBC:MySQL Connector/J 5.1.10
PHP:5.2.11 (cli)
MySQLi:5.2.11.11
Python:2.6.4
MySQL-Python:1.2.3c1
所用的庫都是最新版的,也都采用了最為推薦的庫。
但數據庫并沒有使用最新的穩定版,因為我懶得重下了。5.4.3-beta測試版也試過,在連續插入時,性能比5.1快1~2個數量級,估計是服務器端緩存和設置的原因。
測試項目:
1.創建100萬個隨機數,并生成插入這些隨機數的SQL語句。
2.連接本地數據庫,如不成功,嘗試創建數據庫。
3.刪除并創建數據庫表,引擎類型為InnoDB,主鍵為自動遞增的整數,此外有個浮點型的字段(無索引)。
4.分成100組,每次插入1萬個隨機數。(因為每組的執行量都很大,因此啟用自動提交事務。)
5.用SELECT COUNT(*)統計小于0.1的隨機數個數。(約10萬個)
6.用SELECT *取出再統計大于0.9的隨機數個數。(約10萬個)
7.將所有0.4~0.5之間的隨機數加1。(約10萬個)
8.將所有0.5~0.6之間的行刪除。(約20萬個)
9.斷開數據庫連接。
10.再次連接數據庫。
測試代碼:
Java:
Java代碼
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.util.Random;
public final class Test {
public static void main(String[] args) {
final int SIZE1 = 10000;
final int SIZE2 = 100;
final String DB_ENGINE = "InnoDB"; // InnoDB Memory MyISAM
final double NANO_TIME_PER_SEC = 1000000000.0;
System.out.printf("測試數據量:%d\n", SIZE1 * SIZE2);
System.out.printf("測試引擎:%s\n", DB_ENGINE);
long t1 = System.nanoTime(), t2, t3 = 0, t4, t5, t6, t7, t8, t9, t10, t11;
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
Random r = new Random();
String[] sqls = new String[SIZE2];
for (int i = 0; i < SIZE2; ++i){
StringBuilder buffer = new StringBuilder("INSERT INTO test (value) VALUES (");
for (int j = 0; j < SIZE1; ++j){
buffer.append(r.nextDouble()).append("),(");
}
sqls[i] = buffer.substring(0, buffer.length() -2);
}
t2 = System.nanoTime();
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost/testdb?user=root&password=123456");
t3 = System.nanoTime();
stmt = conn.createStatement();
} catch (SQLException e) {
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost/?user=root&password=123456");
t3 = System.nanoTime();
stmt = conn.createStatement();
stmt.execute("CREATE DATABASE testdb");
} catch (SQLException ex) {
System.out.println("SQLException: " + ex.getMessage());
System.out.println("SQLState: " + ex.getSQLState());
System.out.println("VendorError: " + ex.getErrorCode());
}
}
try {
stmt.execute("DROP TABLE test");
} catch (SQLException e) {
}
try {
stmt.execute("CREATE TABLE test (`id` INT AUTO_INCREMENT PRIMARY KEY, `value` REAL) ENGINE = " + DB_ENGINE);
} catch (SQLException e) {
}
t4 = System.nanoTime();
try {
for (String sql: sqls){
stmt.execute(sql);
}
t5 = System.nanoTime();
rs = stmt.executeQuery("SELECT COUNT(*) FROM test WHERE value < 0.1");
if (rs.next())
System.out.printf("共有%d個小于0.1的隨機數\n", rs.getInt(1));
t6 = System.nanoTime();
rs = stmt.executeQuery("SELECT * FROM test WHERE value > 0.9");
if (rs.last())
System.out.printf("共有%d個大于0.9的隨機數\n", rs.getRow());
t7 = System.nanoTime();
stmt.executeUpdate("UPDATE test SET value = value + 0.1 WHERE value > 0.4 AND value < 0.5");
t8 = System.nanoTime();
stmt.execute("DELETE FROM test WHERE value > 0.5 AND value < 0.6");
t9 = System.nanoTime();
stmt.close();
conn.close();
t10 = System.nanoTime();
conn = DriverManager.getConnection("jdbc:mysql://localhost/?user=root&password=123456");
t11 = System.nanoTime();
conn.close();
System.out.printf("創建隨機數:%f\n", (t2 - t1) / NANO_TIME_PER_SEC);
System.out.printf("初次連接數據庫:%f\n", (t3 - t2) / NANO_TIME_PER_SEC);
System.out.printf("再次連接數據庫:%f\n", (t11 - t10) / NANO_TIME_PER_SEC);
System.out.printf("初始化數據庫和表:%f\n", (t4 - t3) / NANO_TIME_PER_SEC);
System.out.printf("插入:%f\n", (t5 - t4) / NANO_TIME_PER_SEC);
System.out.printf("選擇(COUNT):%f\n", (t6 - t5) / NANO_TIME_PER_SEC);
System.out.printf("選擇:%f\n", (t7 - t6) / NANO_TIME_PER_SEC);
System.out.printf("更新:%f\n", (t8 - t7) / NANO_TIME_PER_SEC);
System.out.printf("刪除:%f\n", (t9 - t8) / NANO_TIME_PER_SEC);
System.out.printf("關閉連接:%f\n", (t10 - t9) / NANO_TIME_PER_SEC);
System.out.printf("總時間:%f\n", (t10 - t1) / NANO_TIME_PER_SEC);
} catch (SQLException ex) {
System.out.println("SQLException: " + ex.getMessage());
System.out.println("SQLState: " + ex.getSQLState());
System.out.println("VendorError: " + ex.getErrorCode());
}
}
}
PHP:
Php代碼
<?php
define('SIZE1', 10000);
define('SIZE2', 100);
define('DB_ENGINE', 'InnoDB'); // InnoDB Memory MyISAM
printf("測試數據量:%d\n", SIZE1 * SIZE2);
printf("測試引擎:%s\n", DB_ENGINE);
$t1 = microtime(true);
for ($i = 0; $i < SIZE2; ++$i){
for ($j = 0; $j < SIZE1; ++$j){
$buffer[] = lcg_value();
}
$sqls[$i] = 'INSERT INTO test (value) VALUES ('.join('),(', $buffer).')';
unset($buffer);
}
$t2 = microtime(true);
$db = new mysqli('localhost', 'root', '123456', 'testdb');
$t3 = microtime(true);
if (mysqli_connect_errno()) {
$db = new mysqli('localhost', 'root', '123456');
$t3 = microtime(true);
$db->query('CREATE DATABASE testdb');
$db->select_db('testdb');
}
$db->query('DROP TABLE test');
$db->query('CREATE TABLE test (`id` INT AUTO_INCREMENT PRIMARY KEY, `value` REAL) ENGINE = '.DB_ENGINE);
$t4 = microtime(true);
foreach ($sqls as $key=>$sql) {
$db->query($sql);
}
$t5 = microtime(true);
$result = $db->query('SELECT COUNT(*) FROM test WHERE value < 0.1')->fetch_row();
printf("共有%d個小于0.1的隨機數\n", $result[0]);
$t6 = microtime(true);
$result = $db->query('SELECT * FROM test WHERE value > 0.9');
printf("共有%d個大于0.9的隨機數\n", $result->num_rows);
$t7 = microtime(true);
$db->query('UPDATE test SET value = value + 0.1 WHERE value > 0.4 AND value < 0.5');
$t8 = microtime(true);
$db->query('DELETE FROM test WHERE value > 0.5 AND value < 0.6');
$t9 = microtime(true);
$db->close();
$t10 = microtime(true);
$db = new mysqli('localhost', 'root', '123456', 'testdb');
$t11 = microtime(true);
$db->close();
printf("創建隨機數:%f\n", $t2 - $t1);
printf("初次連接數據庫:%f\n", $t3 - $t2);
printf("再次連接數據庫:%f\n", $t11 - $t10);
printf("初始化數據庫和表:%f\n", $t4 - $t3);
printf("插入:%f\n", $t5 - $t4);
printf("選擇(COUNT):%f\n", $t6 - $t5);
printf("選擇:%f\n", $t7 - $t6);
printf("更新:%f\n", $t8 - $t7);
printf("刪除:%f\n", $t9 - $t8);
printf("關閉連接:%f\n", $t10 - $t9);
printf("總時間:%f\n", $t10 - $t1);
?>
Python:
Python代碼
# -*- coding: gbk -*-
import MySQLdb
from random import random
from time import clock
SIZE1 = 10000
SIZE2 = 100
DB_ENGINE = 'InnoDB' # InnoDB Memory MyISAM
print '測試數據量:', SIZE1 * SIZE2
print '測試引擎:', DB_ENGINE
t1 = clock()
sqls = ['INSERT INTO test (value) VALUES (%s)' % '),('.join([`random()` for i in xrange(SIZE1)]) for j in xrange(SIZE2)]
t2 = clock()
try:
con = MySQLdb.connect(user='root', passwd='123456', db='testdb')
t3 = clock()
cu = con.cursor()
except:
con = MySQLdb.connect(user='root', passwd='123456')
t3 = clock()
cu = con.cursor()
cu.execute('CREATE DATABASE testdb')
con.select_db('testdb')
con.autocommit(True)
try:
cu.execute('DROP TABLE test')
except:
pass
cu.execute('''''CREATE TABLE test (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`value` REAL)
ENGINE = %s''' % DB_ENGINE)
t4 = clock()
for sql in sqls:
cu.execute(sql)
t5 = clock()
cu.execute('SELECT COUNT(*) FROM test WHERE value < 0.1')
print '共有%d個小于0.1的隨機數' % cu.fetchone()[0]
t6 = clock()
cu.execute('SELECT * FROM test WHERE value > 0.9')
print '共有%d個大于0.9的隨機數' % len(cu.fetchall())
t7 = clock()
cu.execute('UPDATE test SET value = value + 0.1 WHERE value > 0.4 AND value < 0.5')
t8 = clock()
cu.execute('DELETE FROM test WHERE value > 0.5 AND value < 0.6')
t9 = clock()
cu.close()
con.close()
t10 = clock()
con = MySQLdb.connect(user='root', passwd='123456', db='testdb')
t11 = clock()
con.close()
print '創建隨機數:', t2 - t1
print '初次連接數據庫:', t3 - t2
print '再次連接數據庫:', t11 - t10
print '初始化數據庫:', t4 - t3
print '插入:', t5 - t4
print '選擇(COUNT)', t6 - t5
print '選擇:', t7 - t6
print '更新:', t8 - t7
print '刪除:', t9 - t8
print '關閉連接:', t10 - t9
print '總時間:', t10 - t1
MySQL-Python還有個底層的模塊,一并測試下:
Python代碼
# -*- coding: gbk -*-
import _mysql
from MySQLdb.converters import conversions
from random import random
from time import clock
SIZE1 = 10000
SIZE2 = 100
DB_ENGINE = 'InnoDB' # InnoDB Memory MyISAM
print '測試數據量:', SIZE1 * SIZE2
print '測試引擎:', DB_ENGINE
t1 = clock()
sqls = ['INSERT INTO test (value) VALUES (%s)' % '),('.join([`random()` for i in xrange(SIZE1)]) for j in xrange(SIZE2)]
t2 = clock()
try:
con = _mysql.connect(user='root', passwd='123456', db='testdb', conv=conversions)
t3 = clock()
except:
con = _mysql.connect(user='root', passwd='123456', conv=conversions)
t3 = clock()
con.query('CREATE DATABASE testdb')
con.select_db('testdb')
con.autocommit(True)
try:
con.query('DROP TABLE test')
except:
pass
con.query('''''CREATE TABLE test (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`value` REAL)
ENGINE = %s''' % DB_ENGINE)
t4 = clock()
for sql in sqls:
con.query(sql)
t5 = clock()
con.query('SELECT COUNT(*) FROM test WHERE value < 0.1')
print '共有%d個小于0.1的隨機數' % con.store_result().fetch_row()[0]
t6 = clock()
con.query('SELECT * FROM test WHERE value > 0.9')
print '共有%d個大于0.9的隨機數' % con.store_result().num_rows()
t7 = clock()
con.query('UPDATE test SET value = value + 0.1 WHERE value > 0.4 AND value < 0.5')
t8 = clock()
con.query('DELETE FROM test WHERE value > 0.5 AND value < 0.6')
t9 = clock()
con.close()
t10 = clock()
con = _mysql.connect(user='root', passwd='123456', db='testdb', conv=conversions)
t11 = clock()
con.close()
print '創建隨機數:', t2 - t1
print '初次連接數據庫:', t3 - t2
print '再次連接數據庫:', t11 - t10
print '初始化數據庫:', t4 - t3
print '插入:', t5 - t4
print '選擇(COUNT)', t6 - t5
print '選擇:', t7 - t6
print '更新:', t8 - t7
print '刪除:', t9 - t8
print '關閉連接:', t10 - t9
print '總時間:', t10 - t1
每種測試3次(等硬盤燈無閃爍后才進行下一次測試),取最好的一次作為測試結果:
Java:
引用
測試數據量:1000000
測試引擎:InnoDB
共有99465個小于0.1的隨機數
共有99859個大于0.9的隨機數
創建隨機數:2.367840
初次連接數據庫:0.220420
再次連接數據庫:0.013174
初始化數據庫和表:0.075140
插入:12.139346
選擇(COUNT):1.130345
選擇:1.017769
更新:6.173245
刪除:9.380070
關閉連接:0.002131
總時間:32.506307
PHP:
引用
測試數據量:1000000
測試引擎:InnoDB
共有99898個小于0.1的隨機數
共有100152個大于0.9的隨機數
創建隨機數:1.506294
初次連接數據庫:0.003146
再次連接數據庫:0.001808
初始化數據庫和表:0.131754
插入:12.046944
選擇(COUNT):1.236742
選擇:1.238153
更新:6.115232
刪除:8.145547
關閉連接:0.000125
總時間:30.423937
Python(MySQLdb):
引用
測試數據量: 1000000
測試引擎: InnoDB
共有100040個小于0.1的隨機數
共有100351個大于0.9的隨機數
創建隨機數: 1.6822107279
初次連接數據庫: 0.0332120423126
再次連接數據庫: 0.00221704155137
初始化數據庫: 0.131054924578
插入: 11.7999030603
選擇(COUNT) 1.27067266929
選擇: 1.16714526567
更新: 6.29200638629
刪除: 8.13660563005
關閉連接: 0.000131022238861
總時間: 30.5129417286
Python(_mysql):
引用
測試數據量: 1000000
測試引擎: InnoDB
共有99745個小于0.1的隨機數
共有99869個大于0.9的隨機數
創建隨機數: 1.68099074044
初次連接數據庫: 0.0112056141213
再次連接數據庫: 0.00159293988482
初始化數據庫: 0.130169616529
插入: 12.1364623157
選擇(COUNT) 1.125517908
選擇: 0.968366649951
更新: 6.8042843434
刪除: 8.9760508668
關閉連接: 9.61015995031e-05
總時間: 31.8331441566
可以看到,在大批量數據測試中,Java是最慢的,而PHP是最快的。
不考慮IO性能的波動的話,Java主要慢在連接和關閉數據庫。JDBC
4.0在第一次連接數據庫時會動態加載驅動,非常耗時,因此使用Java要記住使用數據庫連接池,避免連接浪費大量時間。當然,這也造成了數據庫的負擔,勢必影響內存占用。而創建隨機數的算法實現各不相同,所以不具備可比性;令我驚訝的是SELECT的翻譯速度,將字符串轉換成浮點數居然慢于Python,要知道后者的浮點數可是對象。
PHP連接數據庫非常快,所以完全無需使用連接池,因為維護連接池會增加復雜性。
Python的表現和PHP差不多,但是第一次連接數據庫比較慢(仍比Java快1個數量級)。如果不用連接池,則使用FCGI等方式來運行比較合適。_mysql模塊的通信很快,但更新和刪除操作卻不太理想,也許是IO性能波動的原因。此外,我在連接數據庫時使用了轉換參數,實際上我用的語句都不需要翻譯,不使用的話會更快一點。
接著試試小數據,改成最常用的MyISAM引擎,插入100條(1組)。一般的應用不可能一次插入那么多,所以足夠滿足平時的應用了;而且由于數據量很小,也基本不受IO影響。
測試結果:
Java:
引用
測試數據量:100
測試引擎:MyISAM
共有9個小于0.1的隨機數
共有10個大于0.9的隨機數
創建隨機數:0.001596
初次連接數據庫:0.224135
再次連接數據庫:0.018656
初始化數據庫和表:0.055601
插入:0.001476
選擇(COUNT):0.000529
選擇:0.000433
更新:0.000304
刪除:0.000313
關閉連接:0.000927
總時間:0.285314
PHP:
引用
測試數據量:測試數據量:100
測試引擎:MyISAM
共有12個小于0.1的隨機數
共有9個大于0.9的隨機數
創建隨機數:0.000649
初次連接數據庫:0.008077
再次連接數據庫:0.001609
初始化數據庫和表:0.060421
插入:0.001860
選擇(COUNT):0.000580
選擇:0.000465
更新:0.000326
刪除:0.000373
關閉連接:0.000127
總時間:0.072878
Python(MySQLdb):
引用
測試數據量: 100
測試引擎: MyISAM
共有14個小于0.1的隨機數
共有9個大于0.9的隨機數
創建隨機數: 0.000198907961766
初次連接數據庫: 0.0334640296462
再次連接數據庫: 0.00150577796899
初始化數據庫: 0.0123194428342
插入: 0.00125211444471
選擇(COUNT) 0.000581079438867
選擇: 0.000484139744018
更新: 0.000250311142897
刪除: 0.000262323842835
關閉連接: 7.98984228442e-05
總時間: 0.0488922474784
Python(_mysql):
引用
測試數據量: 100
測試引擎: MyISAM
共有12個小于0.1的隨機數
共有10個大于0.9的隨機數
創建隨機數: 0.000214273043082
初次連接數據庫: 0.0118774872225
再次連接數據庫: 0.00123702872851
初始化數據庫: 0.0315031659052
插入: 0.00120322554962
選擇(COUNT) 0.000596165155069
選擇: 0.000507327048549
更新: 0.0002447238406
刪除: 0.00026148574749
關閉連接: 5.78285787719e-05
總時間: 0.0464656820909
從結果可以看出,雖然差距都很小,但Python仍然稍微占優。不過Java的SELECT操作稍微勝出,而這也是實際應用中最常使用的操作。
再從語言方面來看,Python無疑是寫得最歡暢的,生成隨機數只用了1行代碼;PHP的變量要寫個$讓我老是出錯,不過數據庫操作不需要處理異常,這點節省了很多代碼;Java的代碼量很大,而且不得不使用很多try...catch,我甚至懶得以安全的方式將close()放在finally塊里面,因為它也會拋出我根本懶得去管的異常,且會提示我計時變量可能沒有初始化。
總體上來看,Google放棄Python,只采用C++和Java是有點不明智。因為頁面響應時間主要在于數據庫通信和磁盤文件IO上,語言的影響基本忽略不計。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。