A. Mysql Innodb存儲引擎 select count 太慢,怎麼優化
從 MySQL 5.7 開始,開發人員改變了 InnoDB 構建二級索引的方式,採用自下而上的方法,而不是早期版本中自上而下的方法了。在這篇文章中,我們將通過一個示例來說明如何構建 InnoDB 索引。最後,我將解釋如何通過為 innodb_fill_factor 設置更合適的值。
索引構建過程
在有數據的表上構建索引,InnoDB 中有以下幾個階段:1.讀取階段(從聚簇索引讀取並構建二級索引條目)2.合並排序階段3.插入階段(將排序記錄插入二級索引)在 5.6 版本之前,MySQL 通過一次插入一條記錄來構建二級索引。這是一種「自上而下」的方法。搜索插入位置從樹的根部(頂部)開始並達到葉頁(底部)。該記錄插入游標指向的葉頁上。在查找插入位置和進行業面拆分和合並方面開銷很大。從MySQL 5.7開始,添加索引期間的插入階段使用「排序索引構建」,也稱為「批量索引載入」。在這種方法中,索引是「自下而上」構建的。即葉頁(底部)首先構建,然後非葉級別直到根(頂部)。
示例
在這些情況下使用排序的索引構建:
ALTER TABLE t1 ADD INDEX(or CREATE INDEX)
ALTER TABLE t1 ADD FULLTEXT INDEX
ALTER TABLE t1 ADD COLUMN, ALGORITHM = INPLACE
OPIMIZE t1
對於最後兩個用例,ALTER 會創建一個中間表。中間表索引(主要和次要)使用「排序索引構建」構建。
在 0 級別創建頁,還要為此頁創建一個游標
使用 0 級別處的游標插入頁面,直到填滿
頁面填滿後,創建一個兄弟頁(不要插入到兄弟頁)
為當前的整頁創建節點指針(子頁中的最小鍵,子頁碼),並將節點指針插入上一級(父頁)
在較高級別,檢查游標是否已定位。如果沒有,請為該級別創建父頁和游標
在父頁插入節點指針
如果父頁已填滿,請重復步驟 3, 4, 5, 6
現在插入兄弟頁並使游標指向兄弟頁
在所有插入的末尾,每個級別的游標指向最右邊的頁。提交所有游標(意味著提交修改頁面的迷你事務,釋放所有鎖存器)
為簡單起見,上述演算法跳過了有關壓縮頁和 BLOB(外部存儲的 BLOB)處理的細節。
CREATE TABLE t1 (a INT PRIMARY KEY, b INT, c BLOB);
INSERT INTO t1 VALUES (1, 11, 'hello111');
INSERT INTO t1 VALUES (2, 22, 'hello222');
INSERT INTO t1 VALUES (3, 33, 'hello333');
INSERT INTO t1 VALUES (4, 44, 'hello444');
INSERT INTO t1 VALUES (5, 55, 'hello555');
INSERT INTO t1 VALUES (6, 66, 'hello666');
INSERT INTO t1 VALUES (7, 77, 'hello777');
INSERT INTO t1 VALUES (8, 88, 'hello888');
INSERT INTO t1 VALUES (9, 99, 'hello999');
INSERT INTO t1 VALUES (10, 1010, 'hello101010');
ALTER TABLE t1 ADD INDEX k1(b);
(11,1), (22,2), (33,3), (44,4), (55,5), (66,6), (77,7), (88,8), (99,9), (1010, 10)
讓我們從記錄 (11,1) 開始。
在 0 級別(葉級別)創建頁
創建一個到頁的游標
所有插入都將轉到此頁面,直到它填滿了
箭頭顯示游標當前指向的位置。它目前位於第 5 頁,下一個插入將轉到此頁面。
創建一個兄弟頁,頁碼 6
不要插入兄弟頁
在游標處提交頁面,即迷你事務提交,釋放鎖存器等
作為提交的一部分,創建節點指針並將其插入到 【當前級別 + 1】 的父頁面中(即在 1 級別)
節點指針的格式 (子頁面中的最小鍵,子頁碼) 。第 5 頁的最小鍵是 (11,1) 。在父級別插入記錄 ((11,1),5)。
1 級別的父頁尚不存在,MySQL 創建頁碼 7 和指向頁碼 7 的游標。
將 ((11,1),5) 插入第 7 頁
現在,返回到 0 級並創建從第 5 頁到第 6 頁的鏈接,反之亦然
0 級別的游標現在指向兄弟頁,頁碼為 6
將 (44,4) 插入第 6 頁
下一個插入 - (55,5) 和 (66,6) - 很簡單,它們轉到第 6 頁。
插入記錄 (77,7) 類似於 (44,4),除了父頁面 (頁面編號 7) 已經存在並且它有兩個以上記錄的空間。首先將節點指針 ((44,4),8) 插入第 7 頁,然後將 (77,7) 記錄到同級 8 頁中。
插入記錄 (88,8) 和 (99,9) 很簡單,因為第 8 頁有兩個空閑插槽。
我們現在有了一個完整的 B+-tree 索引,它是自下至上構建的!
值 80 意味著 MySQL 使用了 80% 的頁空間填充,預留 20% 於未來的更新。如果 innodb_fill_factor=100 則沒有剩餘空間供未來插入二級索引。如果在添加索引後,期望表上有更多的 DML,則可能導致業面拆分並再次合並。在這種情況下,建議使用 80-90 之間的值。此變數還會影響使用 OPTIMIZE TABLE 和 ALTER TABLE DROP COLUMN, ALGOITHM=INPLACE 重新創建的索引。也不應該設置太低的值,例如低於 50。因為索引會佔用浪費更多的磁碟空間,值較低時,索引中的頁數較多,索引統計信息的采樣可能不是最佳的。優化器可以選擇具有次優統計信息的錯誤查詢計劃。
沒有頁面拆分(不包括壓縮表)和合並
沒有重復搜索插入位置
插入不會被重做記錄(頁分配除外),因此重做日誌子系統的壓力較小
ALTER 正在進行時,插入性能降低 Bug#82940,但在後續版本中計劃修復。
演算法
通過自下而上的方式構建索引
為簡單起見,假設子頁和非子頁中允許的 最大記錄數為 3
InnoDB 將主鍵欄位追加到二級索引。二級索引 k1 的記錄格式為(b, a)。在排序階段完成後,記錄為:
初始插入階段
還有兩個空閑插槽,因此插入記錄 (22,2) 和 (33,3) 非常簡單
對於下一條記錄 (44,4),頁碼 5 已滿(前面提到的假設最大記錄數為 3)。這就是步驟。
頁填充時的索引構建
下一個插入 (1010,10) 。將節點指針 ((77,7),8) 插入 1級別的父頁(頁碼 7)。
MySQL 在 0 級創建同級頁碼 9。將記錄 (1010,10) 插入第 9 頁並將游標更改為此頁面。
以此類推。在上面的示例中,資料庫在 0 級別提交到第 9 頁,在 1 級別提交到第 7 頁。
索引填充因子
全局變數 innodb_fill_factor 用於設置插入 B-tree 頁中的空間量。默認值為 100,表示使用整個業面(不包括頁眉)。聚簇索引具有 innodb_fill_factor=100 的免除項。 在這種情況下,聚簇索引也空間的 1 /16 保持空閑。即 6.25% 的空間用於未來的 DML。
排序索引構建的優點
缺點
B. oracle插入blob欄位可以批量嗎
第一步:創建一個資料庫可以訪問的目錄(注意:這個目錄是資料庫伺服器上的目錄,不是客戶機上的)
-- Create directory
create or replace directory 圖片目錄
as 'E:\照片';
第二步:將圖片文件放入剛建好的目錄下面,不要在新建文件夾,就放在這個根目錄
第三步:根據自己的具體需求,編寫存儲過程,在做之前,我也在網上找了很多,但基本都只是大概說一下,沒有找到比較完整的,這里就把自己的項目源碼貼出來,供大家學習交流。
CREATE OR REPLACE PROCEDURE PRO_插入圖片(V_表名 IN VARCHAR2) IS
P_FILENAME VARCHAR2(50); --照片名,動態拼接得到
P_證件號碼 VARCHAR2(50);
P_姓名 VARCHAR2(50);--這個照片名是通過姓名+證件號拼接得到的,因為基礎測試數據沒有提供真實的證件號碼,就選擇用手機號來代替
P_查詢SQL VARCHAR2(500);
P_更新SQL VARCHAR2(5000);
P_LOB BLOB;
P_FILE BFILE;
TYPE P_REF_CURSOR IS REF CURSOR; --定義動態游標變數類型
P_CURSOR P_REF_CURSOR; --定義動態游標變數,因為一次要插入全表的照片,所以選擇用游標來處理
TYPE P_ROW_RECORD IS RECORD(
證件號碼 VARCHAR2(50),
姓名 VARCHAR2(50));
C_ROW P_ROW_RECORD;
V_ERR VARCHAR2(300);
BEGIN
P_更新SQL := 'update ' || V_表名 || ' set 證件號碼=手機號碼 WHERE 證件號碼 IS NULL';
--用手機號來代替證件號碼為空的數據
EXECUTE IMMEDIATE P_更新SQL;
COMMIT;
P_查詢SQL := 'SELECT 證件號碼,姓名 FROM ' || V_表名 ||
' WHERE 證件號碼 IS NOT NULL and 照片 IS NULL order by 證件號碼';
OPEN P_CURSOR FOR P_查詢SQL;
LOOP
begin
FETCH P_CURSOR
INTO C_ROW;
EXIT WHEN P_CURSOR%NOTFOUND;
--獲取證件號碼和姓名,先排除空格等臟數據,然後拼接成文件名;
P_證件號碼 := C_ROW.證件號碼;
P_姓名 := C_ROW.姓名;
SELECT REPLACE(P_證件號碼, ' ', '') INTO P_證件號碼 FROM DUAL;
SELECT substr(P_證件號碼, 1, 11) INTO P_證件號碼 FROM DUAL;
SELECT REPLACE(P_姓名, ' ', '') INTO P_姓名 FROM DUAL;
P_FILENAME := P_證件號碼 || P_姓名 || '.jpg';
SELECT REPLACE(P_FILENAME, ' ', '') INTO P_FILENAME FROM DUAL;
--以下便是插入圖片的核心代碼
INSERT INTO TA_照片總表_TEMP
(證件號碼, 姓名, 照片)
VALUES
(P_證件號碼, P_姓名, EMPTY_BLOB()) RETURN 照片 INTO P_LOB;
--獲取指定目錄下的文件
P_FILE := BFILENAME('圖片目錄', P_FILENAME);
--以只讀的方式打開文件
DBMS_LOB.FILEOPEN(P_FILE, DBMS_LOB.FILE_READONLY);
--傳遞對象
DBMS_LOB.LOADFROMFILE(P_LOB, P_FILE, DBMS_LOB.GETLENGTH(P_FILE));
--關閉原始文件
DBMS_LOB.FILECLOSE(P_FILE);
COMMIT;
--通過更新語句來向目標表插入圖片
P_更新SQL := 'UPDATE ' || V_表名 ||
' A SET a.照片=(SELECT 照片 FROM TA_照片總表_TEMP b
WHERE A.證件號碼 = B.證件號碼 and a.姓名=b.姓名 AND ROWNUM=1)
WHERE EXISTS (SELECT 1 FROM TA_照片總表_TEMP B WHERE A.證件號碼 = B.證件號碼 and a.姓名=b.姓名)';
EXECUTE IMMEDIATE P_更新SQL;
COMMIT;
EXCEPTION
--處理異常情況,這個可以在出現異常時跳過異常繼續跑。正常數據依然可以插入,並且記錄異常信息,方便異常處理。這個是因為第一次寫的過程一報錯就斷掉了,本來可以插入的圖片也無法繼續,然後就做了這個優化。
WHEN OTHERS THEN
rollback;
V_ERR := SUBSTR(SQLERRM, 1, 150) || '照片名:' || P_FILENAME;
--定義一張異常信息記錄表,是一個非常好的習慣
INSERT INTO TA_程序運行異常記錄
VALUES
(SQ_異常序列.NEXTVAL, 'PRO_插入圖片', V_ERR, SYSDATE);
COMMIT;
end;
END LOOP;
CLOSE P_CURSOR;
COMMIT;
DELETE TA_照片總表_TEMP;
COMMIT;
END PRO_插入圖片;
然後測試、運行,基本都沒問題,不過圖片的大小,很影響實際插入的時間,這個時間的優化目前還沒有好的對策。
C. mysql數據類型中blob和binary的區別
MySQL 數據類型細分下來,大概有以下幾類:
數值,典型代表為 tinyint,int,bigint
浮點/定點,典型代表為 float,double,decimal 以及相關的同義詞
字元串,典型代表為 char,varchar
時間日期,典型代表為 date,datetime,time,timestamp
二進制,典型代表為 binary,varbinary
位類型
枚舉類型
集合類型
大對象,比如 text,blob
json 文檔類型
一、數值類型(不是數據類型,別看錯了)如果用來存放整數,根據范圍的不同,選擇不同的類型。
注意:timestamp 代表的時間戳是一個 int32 存儲的整數,取值范圍為 '1970-01-01 00:00:01.000000' 到 '2038-01-19 03:14:07.999999';datetime 取值范圍為 '1000-01-01 00:00:00.000000' 到 '9999-12-31 23:59:59.999999'。
1. 如果時間有可能超過時間戳范圍,優先選擇 datetime。2. 如果需要單獨獲取年份值,比如按照年來分區,按照年來檢索等,最好在表中添加一個 year 類型來參與。3. 如果需要單獨獲取日期或者時間,最好是單獨存放,而不是簡單的用 datetime 或者 timestamp。後面檢索時,再加函數過濾,以免後期增加 SQL 編寫帶來額外消耗。
建立表 t5,對這些可能需要的欄位全部分離開,這樣以後寫 SQL 語句的時候就很容易了。
當然了,這種情形佔用額外的磁碟空間。如果想在易用性與空間佔用量大這兩點來折中,可以用 MySQL 的虛擬列來實時計算。比如假設 c5 欄位不存在,想要得到 c5 的結果。mysql-(ytt/3305)->alter table t5 drop c5, add c5 year generated always as (year(c1)) virtual;Query OK, 1 row affected (2.46 sec)Records: 1 Duplicates: 0 Warnings: 0
五、二進制類型
binary(10)/varbinary(10) 代表的不是字元個數,而是位元組數。
行結束符不一樣。char 的行結束符是 ,binary 的行結束符是 0x00。
由於是二進制存儲,所以字元編碼以及排序規則這類就直接無效了。
六、位類型
1. 對於 bit(8) 如果單純存放 1 位,左邊以 0 填充 00000001。2. 查詢時可以直接十進制來過濾數據。3. 如果此欄位加上索引,MySQL 不會自己做類型轉換,只能用二進制來過濾。
創建表 c1, 欄位性別定義一個比特位。mysql-(ytt/3305)->create table c1(gender bit(1));Query OK, 0 rows affected (0.02 sec)
mysql-(ytt/3305)->select cast(gender as unsigned) 'f1' from c1;+------+| f1 |+------+| 0 || 1 |+------+2 rows in set (0.00 sec)
過濾數據也一樣,二進制或者直接十進制都行。mysql-(ytt/3305)->select conv(gender,16,10) as gender -> from c1 where gender = b'1';+--------+| gender |+--------+| 1|+--------+1 row in set (0.00 sec)mysql-(ytt/3305)->select conv(gender,16,10) as gender -> from c1 where gender = '1';+--------+| gender |+--------+| 1|+--------+1 row in set (0.00 sec)
mysql-(ytt/3305)->create table c2(gender char(0));Query OK, 0 rows affected (0.03 sec)
mysql-(ytt/3305)->select count(*) from c1;+----------+| count(*) |+----------+| 33554432 |+----------+1 row in set (1.37 sec)
mysql-(ytt/3305)->insert into c2 select if(gender = 0,'',null) from c1;Query OK, 33554432 rows affected (2 min 18.80 sec)Records: 33554432 Duplicates: 0 Warnings: 0
兩張表的磁碟佔用差不多。root@ytt-pc:/var/lib/mysql/3305/ytt# ls -sihl總用量 1.9G4085684 933M -rw-r----- 1 mysql mysql 932M 12月 11 10:16 c1.ibd4082686 917M -rw-r----- 1 mysql mysql 916M 12月 11 10:22 c2.ibd
檢索方式稍微有些不同,不過效率也差不多。所以說,字元類型不愧為萬能類型。
七、枚舉類型
1. 最大佔用 2 Byte。2. 最大支持 65535 個不同元素。3. MySQL 後台存儲以下標的方式,也就是 tinyint 或者 smallint 的方式,下標從 1 開始。4. 排序時按照下標排序,而不是按照裡面元素的數據類型。所以這點要格外注意。
創建表 t7。mysql-(ytt/3305)->create table t7(c1 enum('mysql','oracle','dble','postgresql','mongodb','redis','db2','sql server'));Query OK, 0 rows affected (0.03 sec)
1. 最大佔用 8 Byte,int64。2. 內部以二進制位的方式存儲,對應的下標如果以十進制來看,就分別為 1,2,4,8,...,pow(2,63)。3. 最大支持 64 個不同的元素,重復元素的插入,取出來直接去重。4. 元素之間可以組合插入,比如下標為 1 和 2 的可以一起插入,直接插入 3 即可。
mysql-(ytt/3305)->create table c7(c1 set('mysql','oracle','dble','postgresql','mongodb','redis','db2','sql server'));Query OK, 0 rows affected (0.02 sec)
mysql-(ytt/3305)->INSERT INTO c7WITH RECURSIVE ytt_number (cnt) AS ( SELECT 1 AS cnt UNION ALL SELECT cnt + 1 FROM ytt_number WHERE cnt < pow(2, 7) )SELECT *FROM ytt_number;Query OK, 128 rows affected (0.01 sec)Records: 128 Duplicates: 0 Warnings: 0
示例 10
mysql-(ytt/3305)->select ytt_sample_data_type(1111,222) 'result';+--------------------------+| result |+--------------------------+| The result is: '246642'. |+--------------------------+1 row in set (0.00 sec)

綜上所述,日期這塊類型的選擇遵循以下原則:
4. 如果有保存毫秒類似的需求,最好是用時間類型自己的特性,不要直接用字元類型來代替。MySQL 內部的類型轉換對資源額外的消耗也是需要考慮的。
示例 5
binary 和 varbinary 對應了 char 和 varchar 的二進制存儲,相關的特性都一樣。不同的有以下幾點:
示例 6
來看這個 binary 存取的簡單示例,還是之前的變數 @a。
切記!這里要提前計算好 @a 佔用的位元組數,以防存儲溢出。
bit 為 MySQL 里存儲比特位的類型,最大支持 64 比特位, 直接以二進制方式存儲,一般用來存儲狀態類的信息。比如,性別,真假等。具有以下特性:
示例 7
其實這樣的場景,也可以定義為 char(0),這也是類似於 bit 非常優化的一種用法。
那現在我給表 c1 簡單的造點測試數據。
把 c1 的數據全部插入 c2。
枚舉類型,也即 enum。適合提前規劃好了所有已經知道的值,且未來最好不要加新值的情形。枚舉類型有以下特性:
示例 8
八、集合類型
集合類型 SET 和枚舉類似,也是得提前知道有多少個元素。SET 有以下特點:
示例 9
定義表 c7 欄位 c1 為 set 類型,包含了 8 個值,也就是下表最大為 pow(2,7)。
插入 1 到 128 的所有組合。
九、數據類型在存儲函數中的用法
函數里除了顯式聲明的變數外,默認 session 變數的數據類型很弱,隨著給定值的不同隨意轉換。
定義一個函數,返回兩個給定參數的乘積。定義里有兩個變數,一個是 v_tmp 顯式定義為 int64,另外一個 @vresult 隨著給定值的類型隨意變換類型。
簡單調用下。
總結
本篇把 MySQL 基本的數據類型做了簡單的介紹,並且用了一些容易理解的示例來梳理這些類型。我們在實際場景中,建議選擇適合最合適的類型,不建議所有數據類型簡單的最大化原則。比如能用 varchar(100),不用 varchar(1000)。
D. 瀏覽器端生成的blob數據怎麼上傳給伺服器端的TP處理
1. 首先嘛,你得在瀏覽器里輸入要網址:
2. 瀏覽器查找域名的IP地址
導航的第一步是通過訪問的域名找出其IP地址。DNS查找過程如下:
瀏覽器緩存 – 瀏覽器會緩存DNS記錄一段時間。 有趣的是,操作系統沒有告訴瀏覽器儲存DNS記錄的時間,這樣不同瀏覽器會儲存個自固定的一個時間(2分鍾到30分鍾不等)。
系統緩存 – 如果在瀏覽器緩存里沒有找到需要的記錄,瀏覽器會做一個系統調用(windows里是gethostbyname)。這樣便可獲得系統緩存中的記錄。
路由器緩存 – 接著,前面的查詢請求發向路由器,它一般會有自己的DNS緩存。
ISP DNS 緩存 – 接下來要check的就是ISP緩存DNS的伺服器。在這一般都能找到相應的緩存記錄。
遞歸搜索 – 你的ISP的DNS伺服器從跟域名伺服器開始進行遞歸搜索,從.com頂級域名伺服器到Facebook的域名伺服器。一般DNS伺服器的緩存中會有.com域名伺服器中的域名,所以到頂級伺服器的匹配過程不是那麼必要了。
DNS遞歸查找如下圖所示:
DNS有一點令人擔憂,這就是像wikipedia.org 或者 facebook.com這樣的整個域名看上去只是對應一個單獨的IP地址。還好,有幾種方法可以消除這個瓶頸:
循環 DNS 是DNS查找時返回多個IP時的解決方案。舉例來說,Facebook.com實際上就對應了四個IP地址。
負載平衡器 是以一個特定IP地址進行偵聽並將網路請求轉發到集群伺服器上的硬體設備。 一些大型的站點一般都會使用這種昂貴的高性能負載平衡器。
地理 DNS 根據用戶所處的地理位置,通過把域名映射到多個不同的IP地址提高可擴展性。這樣不同的伺服器不能夠更新同步狀態,但映射靜態內容的話非常好。
Anycast 是一個IP地址映射多個物理主機的路由技術。 美中不足,Anycast與TCP協議適應的不是很好,所以很少應用在那些方案中。
大多數DNS伺服器使用Anycast來獲得高效低延遲的DNS查找。
3. 瀏覽器給web伺服器發送一個HTTP請求
因為像Facebook主頁這樣的動態頁面,打開後在瀏覽器緩存中很快甚至馬上就會過期,毫無疑問他們不能從中讀取。
所以,瀏覽器將把一下請求發送到Facebook所在的伺服器:
GET HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...]
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...]
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: facebook.com
Cookie: datr=1265876274-[...]; locale=en_US; lsd=WW[...]; c_user=2101[...]
GET 這個請求定義了要讀取的URL: 「」。 瀏覽器自身定義 (User-Agent 頭), 和它希望接受什麼類型的相應 (Accept and Accept-Encoding 頭). Connection頭要求伺服器為了後邊的請求不要關閉TCP連接。
請求中也包含瀏覽器存儲的該域名的cookies。可能你已經知道,在不同頁面請求當中,cookies是與跟蹤一個網站狀態相匹配的鍵值。這樣cookies會存儲登錄用戶名,伺服器分配的密碼和一些用戶設置等。Cookies會以文本文檔形式存儲在客戶機里,每次請求時發送給伺服器。
用來看原始HTTP請求及其相應的工具很多。作者比較喜歡使用fiddler,當然也有像FireBug這樣其他的工具。這些軟體在網站優化時會幫上很大忙。
除了獲取請求,還有一種是發送請求,它常在提交表單用到。發送請求通過URL傳遞其參數(e.g.: )。發送請求在請求正文頭之後發送其參數。
像「」中的斜杠是至關重要的。這種情況下,瀏覽器能安全的添加斜杠。而像「http: //example.com/folderOrFile」這樣的地址,因為瀏覽器不清楚folderOrFile到底是文件夾還是文件,所以不能自動添加 斜杠。這時,瀏覽器就不加斜杠直接訪問地址,伺服器會響應一個重定向,結果造成一次不必要的握手。
4. facebook服務的永久重定向響應
圖中所示為Facebook伺服器發回給瀏覽器的響應:
HTTP/1.1 301 Moved Permanently
Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Expires: Sat, 01 Jan 2000 00:00:00 GMT
Location:
P3P: CP="DSP LAW"
Pragma: no-cache
Set-Cookie: made_write_conn=deleted; expires=Thu, 12-Feb-2009 05:09:50 GMT;
path=/; domain=.facebook.com; httponly
Content-Type: text/html; charset=utf-8
X-Cnection: close
Date: Fri, 12 Feb 2011 05:09:51 GMT
Content-Length: 0
伺服器給瀏覽器響應一個301永久重定向響應,這樣瀏覽器就會訪問「」 而非「」。
為什麼伺服器一定要重定向而不是直接發會用戶想看的網頁內容呢?這個問題有好多有意思的答案。
其中一個原因跟搜索引擎排名有 關。你看,如果一個頁面有兩個地址,就像 和,搜索引擎會認為它們是兩個網站,結果造成每一個的搜索鏈接都減少從而降低排名。而搜索引擎知道301永久重定向是 什麼意思,這樣就會把訪問帶www的和不帶www的地址歸到同一個網站排名下。
還有一個是用不同的地址會造成緩存友好性變差。當一個頁面有好幾個名字時,它可能會在緩存里出現好幾次。
5. 瀏覽器跟蹤重定向地址
現在,瀏覽器知道了「」才是要訪問的正確地址,所以它會發送另一個獲取請求:
GET HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...]
Accept-Language: en-US
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...]
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Cookie: lsd=XW[...]; c_user=21[...]; x-referer=[...]
Host:
頭信息以之前請求中的意義相同。
6. 伺服器「處理」請求
伺服器接收到獲取請求,然後處理並返回一個響應。
這表面上看起來是一個順向的任務,但其實這中間發生了很多有意思的東西- 就像作者博客這樣簡單的網站,何況像facebook那樣訪問量大的網站呢!
Web 伺服器軟體
web伺服器軟體(像IIS和阿帕奇)接收到HTTP請求,然後確定執行什麼請求處理來處理它。請求處理就是一個能夠讀懂請求並且能生成HTML來進行響應的程序(像ASP.NET,PHP,RUBY...)。
舉 個最簡單的例子,需求處理可以以映射網站地址結構的文件層次存儲。像這個地 址會映射/httpdocs/folder1/page1.aspx這個文件。web伺服器軟體可以設置成為地址人工的對應請求處理,這樣 page1.aspx的發布地址就可以是。
請求處理
請求處理閱讀請求及它的參數和cookies。它會讀取也可能更新一些數據,並講數據存儲在伺服器上。然後,需求處理會生成一個HTML響應。
所 有動態網站都面臨一個有意思的難點 -如何存儲數據。小網站一半都會有一個SQL資料庫來存儲數據,存儲大量數據和/或訪問量大的網站不得不找一些辦法把資料庫分配到多台機器上。解決方案 有:sharding (基於主鍵值講數據表分散到多個資料庫中),復制,利用弱語義一致性的簡化資料庫。
委 托工作給批處理是一個廉價保持數據更新的技術。舉例來講,Fackbook得及時更新新聞feed,但數據支持下的「你可能認識的人」功能只需要每晚更新 (作者猜測是這樣的,改功能如何完善不得而知)。批處理作業更新會導致一些不太重要的數據陳舊,但能使數據更新耕作更快更簡潔。
7. 伺服器發回一個HTML響應
圖中為伺服器生成並返回的響應:
HTTP/1.1 200 OK
Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Expires: Sat, 01 Jan 2000 00:00:00 GMT
P3P: CP="DSP LAW"
Pragma: no-cache
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
X-Cnection: close
Transfer-Encoding: chunked
Date: Fri, 12 Feb 2011 09:05:55 GMT
2b3Tn@[...]
整個響應大小為35kB,其中大部分在整理後以blob類型傳輸。
內容編碼頭告訴瀏覽器整個響應體用gzip演算法進行壓縮。解壓blob塊後,你可以看到如下期望的HTML:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"">
<html xmlns="" xml:lang="en"
lang="en" id="facebook" class=" no_js">
...
關於壓縮,頭信息說明了是否緩存這個頁面,如果緩存的話如何去做,有什麼cookies要去設置(前面這個響應里沒有這點)和隱私信息等等。
請注意報頭中把Content-type設置為「text/html」。報頭讓瀏覽器將該響應內容以HTML形式呈現,而不是以文件形式下載它。瀏覽器會根據報頭信息決定如何解釋該響應,不過同時也會考慮像URL擴展內容等其他因素。
8. 瀏覽器開始顯示HTML
在瀏覽器沒有完整接受全部HTML文檔時,它就已經開始顯示這個頁面了:
9. 瀏覽器發送獲取嵌入在HTML中的對象
在瀏覽器顯示HTML時,它會注意到需要獲取其他地址內容的標簽。這時,瀏覽器會發送一個獲取請求來重新獲得這些文件。
下面是幾個我們訪問facebook.com時需要重獲取的幾個URL:
圖片
…
CSS 式樣表
…
JavaScript 文件
…
這些地址都要經歷一個和HTML讀取類似的過程。所以瀏覽器會在DNS中查找這些域名,發送請求,重定向等等...
但 不像動態頁面那樣,靜態文件會允許瀏覽器對其進行緩存。有的文件可能會不需要與伺服器通訊,而從緩存中直接讀取。伺服器的響應中包含了靜態文件保存的期限 信息,所以瀏覽器知道要把它們緩存多長時間。還有,每個響應都可能包含像版本號一樣工作的ETag頭(被請求變數的實體值),如果瀏覽器觀察到文件的版本 ETag信息已經存在,就馬上停止這個文件的傳輸。
試著猜猜看「fbcdn.net」在地址中代表什麼?聰明的答案是"Facebook內容分發網路"。Facebook利用內容分發網路(CDN)分發像圖片,CSS表和JavaScript文件這些靜態文件。所以,這些文件會在全球很多CDN的數據中心中留下備份。
靜態內容往往代表站點的帶寬大小,也能通過CDN輕松的復制。通常網站會使用第三方的CDN。例如,Facebook的靜態文件由最大的CDN提供商Akamai來託管。
舉例來講,當你試著ping static.ak.fbcdn.net的時候,可能會從某個akamai.net伺服器上獲得響應。有意思的是,當你同樣再ping一次的時候,響應的伺服器可能就不一樣,這說明幕後的負載平衡開始起作用了。
10. 瀏覽器發送非同步(AJAX)請求
在Web 2.0偉大精神的指引下,頁面顯示完成後客戶端仍與伺服器端保持著聯系。
以 Facebook聊天功能為例,它會持續與伺服器保持聯系來及時更新你那些亮亮灰灰的好友狀態。為了更新這些頭像亮著的好友狀態,在瀏覽器中執行的 JavaScript代碼會給伺服器發送非同步請求。這個非同步請求發送給特定的地址,它是一個按照程式構造的獲取或發送請求。還是在Facebook這個例 子中,客戶端發送給ajax/chat/buddy_list.php一個發布請求來獲取你好友里哪個 在線的狀態信息。
提起這個模式,就必須要講講"AJAX"-- 「非同步JavaScript 和 XML」,雖然伺服器為什麼用XML格式來進行響應也沒有個一清二白的原因。再舉個例子吧,對於非同步請求,Facebook會返回一些JavaScript的代碼片段。
除了其他,fiddler這個工具能夠讓你看到瀏覽器發送的非同步請求。事實上,你不僅可以被動的做為這些請求的看客,還能主動出擊修改和重新發送它們。AJAX請求這么容易被蒙,可著實讓那些計分的在線游戲開發者們郁悶的了。(當然,可別那樣騙人家~)
Facebook聊天功能提供了關於AJAX一個有意思的問題案例:把數據從伺服器端推送到客戶端。因為HTTP是一個請求-響應協議,所以聊天伺服器不能把新消息發給客戶。取而代之的是客戶端不得不隔幾秒就輪詢下伺服器端看自己有沒有新消息。
這些情況發生時長輪詢是個減輕伺服器負載挺有趣的技術。如果當被輪詢時伺服器沒有新消息,它就不理這個客戶端。而當尚未超時的情況下收到了該客戶的新消息,伺服器就會找到未完成的請求,把新消息做為響應返回給客戶端。
E. 含有blob欄位的表查詢特別慢,怎麼優化SQL
sql語句是:
insert
into
db2.b
select
blob
from
db1.a
如果你的db2.b表不止一個欄位,那麼請把欄位列在後面,並且其它欄位要運行為空或者自動編號,例如:
insert
into
db2.b(blob)
select
blob
from
db1.a
F. mysql插入單筆數據耗時為10s如何優化
2個辦法,
1、blob拆到另外一個表中,針對7W多數據,不是每條數據都有blob情況。
2、另外一方法,將blob徹底不用資料庫存儲,拆到硬碟目錄下的文件方式存儲