1. 程式人生 > 其它 >.net core 3.1 webapi解決跨域問題 GET DELETE POST PUT等

.net core 3.1 webapi解決跨域問題 GET DELETE POST PUT等

資料庫優化維度有四個:

硬體升級、系統配置、表結構設計、SQL語句及索引。

優化選擇:

  • 優化成本:硬體升級>系統配置>表結構設計>SQL語句及索引。
  • 優化效果:硬體升級<系統配置<表結構設計<SQL語句及索引。

1、系統配置優化

1.1 保證從記憶體中讀取資料

MySQL會在記憶體中儲存一定的資料,通過LRU演算法將不常訪問的資料儲存在硬碟檔案中。
儘可能的擴大記憶體中的資料量,將資料儲存在記憶體中,從記憶體中讀取資料,可以提升MySQL效能。
擴大innodb_buffer_pool_size,能夠全然從記憶體中讀取資料。最大限度降低磁碟操作。
確定innodb_buffer_pool_size 足夠大的方法:

mysql> show global status like 'innodb_buffer_pool_pages_%';
+----------------------------------+-------+
| Variable_name | Value |
+----------------------------------+-------+
| Innodb_buffer_pool_pages_data | 8190 |
| Innodb_buffer_pool_pages_dirty | 0 |
| Innodb_buffer_pool_pages_flushed | 12646 |
| Innodb_buffer_pool_pages_free | 0 | 0 表示已經被用光
| Innodb_buffer_pool_pages_misc | 1 |
| Innodb_buffer_pool_pages_total | 8191 |
+----------------------------------+-------+

innodb_buffer_pool_size預設為128M,理論上可以擴大到記憶體的3/4或4/5。
修改 my.cnf
innodb_buffer_pool_size = 750M
如果是專用的MySQL Server可以禁用SWAP

#檢視swap
cat /proc/swaps
Filename Type Size Used Priority
/dev/sda2 partition 1048572 0 -1
#關閉所有交換裝置和檔案.
swapoff -a

1.2 資料預熱

預設情況,僅僅有某條資料被讀取一次,才會快取在 innodb_buffer_pool。
所以,資料庫剛剛啟動,須要進行資料預熱,將磁碟上的全部資料快取到記憶體中。
資料預熱能夠提高讀取速度。

1、對於InnoDB資料庫,進行資料預熱的指令碼是:

SELECT DISTINCT
	CONCAT( 'SELECT ', ndxcollist, ' FROM ', db, '.', tb, ' ORDER BY ', ndxcollist, ';' ) SelectQueryToLoadCache 
FROM
	(
	SELECT ENGINE
		,
		table_schema db,
		table_name tb,
		index_name,
		GROUP_CONCAT( column_name ORDER BY seq_in_index ) ndxcollist 
	FROM
		(
		SELECT
			B.ENGINE,
			A.table_schema,
			A.table_name,
			A.index_name,
			A.column_name,
			A.seq_in_index 
		FROM
			information_schema.statistics A
			INNER JOIN ( SELECT ENGINE, table_schema, table_name FROM information_schema.TABLES WHERE ENGINE = 'InnoDB' ) B USING ( table_schema, table_name ) 
		WHERE
			B.table_schema NOT IN ( 'information_schema', 'mysql' ) 
		ORDER BY
			table_schema,
			table_name,
			index_name,
			seq_in_index 
		) A 
	GROUP BY
		table_schema,
		table_name,
		index_name 
	) AA 
ORDER BY
	db,
	tb;

將該指令碼儲存為:loadtomem.sql
2、執行命令:

mysql -uroot -proot -AN < /root/loadtomem.sql > /root/loadtomem.sql

3、在需要資料預熱時,比如重啟資料庫
執行命令:

mysql -uroot < /root/loadtomem.sql > /dev/null 2>&1

1.3 降低磁碟寫入次數

  • 增大redolog,減少落盤次數
    innodb_log_file_size 設定為 0.25 * innodb_buffer_pool_size

  • 通用查詢日誌、慢查詢日誌可以不開 ,bin-log開
    生產中不開通用查詢日誌,遇到效能問題開慢查詢日誌

  • 寫redolog策略 innodb_flush_log_at_trx_commit設定為0或2
    如果不涉及非常高的安全性 (金融系統),或者基礎架構足夠安全,或者事務都非常小,都能夠用 0
    或者 2 來減少磁碟操作。

1.4 提高磁碟讀寫效能

使用SSD或者記憶體磁碟

2、表結構設計優化

2.1 設計中間表

設計中間表,一般針對於統計分析功能,或者實時性不高的需求(OLTP、OLAP)

2.2 設計冗餘欄位

為減少關聯查詢,建立合理的冗餘欄位(建立冗餘欄位還需要注意資料一致性問題)

2.3 拆表

對於欄位太多的大表,考慮拆表(比如一個表有100多個欄位)
對於表中經常不被使用的欄位或者儲存資料比較多的欄位,考慮拆表

2.4 主鍵優化

每張表建議都要有一個主鍵(主鍵索引),而且主鍵型別最好是int型別,建議自增主鍵(不考慮分佈
式系統的情況下 雪花演算法)。

2.5 欄位的設計

資料庫中的表越小,在它上面執行的查詢也就會越快。
因此,在建立表的時候,為了獲得更好的效能,我們可以將表中欄位的寬度設得儘可能小。
儘量把欄位設定為NOTNULL,這樣在將來執行查詢的時候,資料庫不用去比較NULL值。
對於某些文字欄位,例如“省份”或者“性別”,我們可以將它們定義為ENUM型別。因為在MySQL中,
ENUM型別被當作數值型資料來處理,而數值型資料被處理起來的速度要比文字型別快得多。這樣,我
們又可以提高資料庫的效能。
能用數字的用數值型別
sex 1 0

3、SQL語句及索引優化

設計一個表:tbiguser

CREATE TABLE tbiguser (
	id INT PRIMARY KEY auto_increment,
	nickname VARCHAR ( 255 ),
	loginname VARCHAR ( 255 ),
	age INT,
	sex CHAR ( 1 ),
	STATUS INT,
address VARCHAR ( 255 ) 
);

向該表中寫入10000000條資料

CREATE PROCEDURE test_insert()
BEGIN DECLARE i INT DEFAULT 1;
WHILE i<=10000000
DO
insert into tbiguser
VALUES(null,concat('zy',i),concat('zhaoyun',i),23,'1',1,'beijing'); SET i=i+1;
END WHILE ;
commit;
END;

執行該儲存過程
可以插入10000000條資料

mysql> select count(*) from tbiguser;
+----------+
| count(*) |
+----------+
| 10000000 |
+----------+

3.1 EXPLAIN檢視索引使用情況

使用【慢查詢日誌】功能,去獲取所有查詢時間比較長的SQL語句 3秒-5秒
使用explain檢視有問題的SQL的執行計劃,重點檢視索引使用情況

mysql> explain select * from tbiguser where loginname='zhaoyun1' and
nickname='zy1';
+----+-------------+----------+------+---------------+--------------+---------+-
------+------+------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len |
ref | rows | Extra |
+----+-------------+----------+------+---------------+--------------+---------+-
------+------+------------------------------------+
| 1 | SIMPLE | tbiguser | ref | idx_nickname | idx_nickname | 768 |
const | 1 | Using index condition; Using where |
+----+-------------+----------+------+---------------+--------------+---------+-
------+------+------------------------------------+
1 row in set (0.00 sec)

type列,連線型別。一個好的SQL語句至少要達到range級別。杜絕出現all級別。 index
key列,使用到的索引名。如果沒有選擇索引,值是NULL。可以採取強制索引方式。
key_len列,索引長度。
rows列,掃描行數。該值是個預估值。
extra列,詳細說明。注意,常見的不太友好的值,如下:Using filesort,Using temporary 。
常見的索引:
where 欄位 、組合索引 (最左字首) 、 索引下推 (非選擇行不加鎖) 、覆蓋索引(不回表)
on 兩邊、排序 、分組統計

3.2 SQL語句中IN包含的值不應過多

MySQL對於IN做了相應的優化,即將IN中的常量全部儲存在一個數組裡面,而且這個陣列是排好序
的。但是如果數值較多,產生的消耗也是比較大的。

3.3 SELECT語句務必指明欄位名稱

SELECT * 增加很多不必要的消耗(CPU、IO、記憶體、網路頻寬);減少了使用覆蓋索引的可能性;當
表結構發生改變時,前端也需要更新。所以要求直接在select後面接上欄位名。

mysql> explain select * from tbiguser ;
+----+-------------+----------+------+---------------+------+---------+------+--
-------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref |
rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+--
-------+-------+
| 1 | SIMPLE | tbiguser | ALL | NULL | NULL | NULL | NULL |
9754360 | NULL |
+----+-------------+----------+------+---------------+------+---------+------+--
-------+-------+
1 row in set (0.00 sec)
mysql> explain select id,nickname from tbiguser ;
+----+-------------+----------+-------+---------------+--------------+---------
+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len |
ref | rows | Extra |
+----+-------------+----------+-------+---------------+--------------+---------
+------+---------+-------------+
| 1 | SIMPLE | tbiguser | index | NULL | idx_nickname | 768 |
NULL | 9754360 | Using index |
+----+-------------+----------+-------+---------------+--------------+---------
+------+---------+-------------+
1 row in set (0.00 sec)

3.4 當只需要一條資料的時候,使用limit 1

limit 是可以停止全表掃描的

mysql> select * from tbiguser limit 1;
+----+----------+-----------+------+------+--------+---------+
| id | nickname | loginname | age | sex | status | address |
+----+----------+-----------+------+------+--------+---------+
| 1 | zy1 | zhaoyun1 | 23 | 1 | 1 | beijing |
+----+----------+-----------+------+------+--------+---------+
1 row in set (0.00 sec)
mysql> explain select * from tbiguser limit 1;
+----+-------------+----------+------+---------------+------+---------+------+--
-------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref |
rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+--
-------+-------+
| 1 | SIMPLE | tbiguser | ALL | NULL | NULL | NULL | NULL |
9754360 | NULL |
+----+-------------+----------+------+---------------+------+---------+------+--
-------+-------+
1 row in set (0.00 sec)

3.5 排序欄位加索引

mysql> explain select * from tbiguser where loginname = 'zhaoyun9999999' order
by id ;
+----+-------------+----------+-------+---------------+---------+---------+-----
-+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+-------------+----------+-------+---------------+---------+---------+-----
-+---------+-------------+
| 1 | SIMPLE | tbiguser | index | NULL | PRIMARY | 4 | NULL
| 9754360 | Using where |
+----+-------------+----------+-------+---------------+---------+---------+-----
-+---------+-------------+
1 row in set (0.01 sec)
mysql> explain select * from tbiguser where loginname = 'zhaoyun9999999' order
by loginname ;
+----+-------------+----------+------+---------------+------+---------+------+--
-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref |
rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+--
-------+-------------+
| 1 | SIMPLE | tbiguser | ALL | NULL | NULL | NULL | NULL |
9754360 | Using where |
+----+-------------+----------+------+---------------+------+---------+------+--
-------+-------------+
1 row in set (0.00 sec)

3.6 如果限制條件中其他欄位沒有索引,儘量少用or

or兩邊的欄位中,如果有一個不是索引欄位,會造成該查詢不走索引的情況。

mysql> explain select * from tbiguser where nickname='zy1' or
loginname='zhaoyun3';
+----+-------------+----------+------+---------------+------+---------+------+--
-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref |
rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+--
-------+-------------+
| 1 | SIMPLE | tbiguser | ALL | idx_nickname | NULL | NULL | NULL |
9754360 | Using where |
+----+-------------+----------+------+---------------+------+---------+------+--
-------+-------------+
1 row in set (0.00 sec)

3.7 儘量用union all代替union

union和union all的差異主要是前者需要將結果集合並後再進行唯一性過濾操作,這就會涉及到排序,
增加大量的CPU運算,加大資源消耗及延遲。當然,union all的前提條件是兩個結果集沒有重複資料。

3.8 不使用ORDER BY RAND()

ORDER BY RAND() 不走索引

mysql> select * from tbiguser order by rand() limit 10;
+---------+-----------+----------------+------+------+--------+---------+
| id | nickname | loginname | age | sex | status | address |
+---------+-----------+----------------+------+------+--------+---------+
| 416412 | zy416412 | zhaoyun416412 | 23 | 1 | 1 | beijing |
| 4338012 | zy4338012 | zhaoyun4338012 | 23 | 1 | 1 | beijing |
| 4275017 | zy4275017 | zhaoyun4275017 | 23 | 1 | 1 | beijing |
| 8572779 | zy8572779 | zhaoyun8572779 | 23 | 1 | 1 | beijing |
| 2500546 | zy2500546 | zhaoyun2500546 | 23 | 1 | 1 | beijing |
| 5906410 | zy5906410 | zhaoyun5906410 | 23 | 1 | 1 | beijing |
| 3347200 | zy3347200 | zhaoyun3347200 | 23 | 1 | 1 | beijing |
| 4737955 | zy4737955 | zhaoyun4737955 | 23 | 1 | 1 | beijing |
| 9120355 | zy9120355 | zhaoyun9120355 | 23 | 1 | 1 | beijing |
| 2564477 | zy2564477 | zhaoyun2564477 | 23 | 1 | 1 | beijing |
+---------+-----------+----------------+------+------+--------+---------+
10 rows in set (10.86 sec)
mysql> select * from tbiguser t1 join (select rand()*(select max(id) from
tbiguser) nid ) t2 on t1.id>t2.nid limit 10;
+---------+-----------+----------------+------+------+--------+---------+-------
------------+
| id | nickname | loginname | age | sex | status | address | nid
|
+---------+-----------+----------------+------+------+--------+---------+-------
------------+
| 6580156 | zy6580156 | zhaoyun6580156 | 23 | 1 | 1 | beijing |
6580155.591089424 |
| 6580157 | zy6580157 | zhaoyun6580157 | 23 | 1 | 1 | beijing |
6580155.591089424 |
| 6580158 | zy6580158 | zhaoyun6580158 | 23 | 1 | 1 | beijing |
6580155.591089424 |
| 6580159 | zy6580159 | zhaoyun6580159 | 23 | 1 | 1 | beijing |
6580155.591089424 |
| 6580160 | zy6580160 | zhaoyun6580160 | 23 | 1 | 1 | beijing |
6580155.591089424 |
| 6580161 | zy6580161 | zhaoyun6580161 | 23 | 1 | 1 | beijing |
6580155.591089424 |
| 6580162 | zy6580162 | zhaoyun6580162 | 23 | 1 | 1 | beijing |
6580155.591089424 |
| 6580163 | zy6580163 | zhaoyun6580163 | 23 | 1 | 1 | beijing |
6580155.591089424 |
| 6580164 | zy6580164 | zhaoyun6580164 | 23 | 1 | 1 | beijing |
6580155.591089424 |
| 6580165 | zy6580165 | zhaoyun6580165 | 23 | 1 | 1 | beijing |
6580155.591089424 |
+---------+-----------+----------------+------+------+--------+---------+-------
------------+
10 rows in set (0.01 sec)

3.9 區分in和exists、not in和not exists

區分in和exists主要是造成了驅動順序的改變(這是效能變化的關鍵),如果是exists,那麼以外層表為
驅動表,先被訪問,如果是IN,那麼先執行子查詢。所以IN適合於外表大而內表小的情況;EXISTS適合
於外表小而內表大的情況。
關於not in和not exists,推薦使用not exists,不僅僅是效率問題,not in可能存在邏輯問題。如何高
效的寫出一個替代not exists的SQL語句?
原SQL語句:

select colname … from A表 where a.id not in (select b.id from B表)

高效的SQL語句:

select colname … from A表 Left join B表 on where a.id = b.id where b.id is null

3.10 使用合理的分頁方式以提高分頁的效率

分頁使用 limit m,n 儘量讓m 小
利用主鍵的定位,可以減小m的值

mysql> select * from tbiguser limit 9999998, 2;
+----------+------------+-----------------+------+------+--------+---------+
| id | nickname | loginname | age | sex | status | address |
+----------+------------+-----------------+------+------+--------+---------+
| 9999999 | zy9999999 | zhaoyun9999999 | 23 | 1 | 1 | beijing |
| 10000000 | zy10000000 | zhaoyun10000000 | 23 | 1 | 1 | beijing |
+----------+------------+-----------------+------+------+--------+---------+
2 rows in set (4.72 sec)
mysql> select * from tbiguser where id>9999998 limit 2;
+----------+------------+-----------------+------+------+--------+---------+
| id | nickname | loginname | age | sex | status | address |
+----------+------------+-----------------+------+------+--------+---------+
| 9999999 | zy9999999 | zhaoyun9999999 | 23 | 1 | 1 | beijing |
| 10000000 | zy10000000 | zhaoyun10000000 | 23 | 1 | 1 | beijing |
+----------+------------+-----------------+------+------+--------+---------+
2 rows in set (0.00 sec)

3.11 分段查詢

一些使用者選擇頁面中,可能一些使用者選擇的範圍過大,造成查詢緩慢。主要的原因是掃描行數過多。這
個時候可以通過程式,分段進行查詢,迴圈遍歷,將結果合併處理進行展示。

3.12 不建議使用%字首模糊查詢

例如LIKE“%name”或者LIKE“%name%”,這種查詢會導致索引失效而進行全表掃描。但是可以使用LIKE
“name%”。
那麼如何解決這個問題呢,答案:使用全文索引或ES全文檢索

3.13 避免在where子句中對欄位進行表示式操作

select user_id,user_project from user_base where age*2=36;

中對欄位就行了算術運算,這會造成引擎放棄使用索引,建議改成:

select user_id,user_project from user_base where age=36/2;

3.14 避免隱式型別轉換

where子句中出現column欄位的型別和傳入的引數型別不一致的時候發生的型別轉換,建議先確定
where中的引數型別。 where age='18'

3.15 對於聯合索引來說,要遵守最左字首法則

舉列來說索引含有欄位id、name、school,可以直接用id欄位,也可以id、name這樣的順序,但是
name;school都無法使用這個索引。所以在建立聯合索引的時候一定要注意索引欄位順序,常用的查詢
欄位放在最前面。

3.16 必要時可以使用force index來強制查詢走某個索引

有的時候MySQL優化器採取它認為合適的索引來檢索SQL語句,但是可能它所採用的索引並不是我們想
要的。這時就可以採用forceindex來強制優化器使用我們制定的索引。

3.17 注意範圍查詢語句

對於聯合索引來說,如果存在範圍查詢,比如between、>、<等條件時,會造成後面的索引欄位失效。

3.18 使用JOIN優化

LEFT JOIN A表為驅動表,INNER JOIN MySQL會自動找出那個資料少的表作用驅動表,RIGHT JOIN B
表為驅動表。
注意:
1)MySQL中沒有full join,可以用以下方式來解決:

select * from A left join B on B.name = A.namewhere B.name is null union all
select * from B;

2)儘量使用inner join,避免left join:
參與聯合查詢的表至少為2張表,一般都存在大小之分。如果連線方式是inner join,在沒有其他過濾條
件的情況下MySQL會自動選擇小表作為驅動表,但是left join在驅動表的選擇上遵循的是左邊驅動右邊
的原則,即left join左邊的表名為驅動表。
3)合理利用索引:
被驅動表的索引欄位作為on的限制欄位。
4)利用小表去驅動大表:

從原理圖能夠直觀的看出如果能夠減少驅動表的話,減少巢狀迴圈中的迴圈次數,以減少 IO總量及CPU
運算的次數。

4、MySQL開發規約

我們知道各大公司都有自己的MySQL開發規約,我們以阿里為例,阿里的MySQL開發規約如下:

4.1 建表規約

  1. 【強制】表達是與否概念的欄位,必須使用 is_xxx 的方式命名,資料型別是 unsigned tinyint
    (1 表示是,0 表示否)。
    說明:任何欄位如果為非負數,必須是 unsigned。
    注意:POJO 類中的任何布林型別的變數,都不要加 is 字首,所以,需要在設定
    從 is_xxx 到 Xxx 的對映關係。資料庫表示是與否的值,使用 tinyint 型別,堅持 is_xxx 的
    命名方式是為了明確其取值含義與取值範圍。
    正例:表達邏輯刪除的欄位名 is_deleted,1 表示刪除,0 表示未刪除。

  2. 【強制】表名、欄位名必須使用小寫字母或數字,禁止出現數字開頭,禁止兩個下劃線中間只
    出現數字。資料庫欄位名的修改代價很大,因為無法進行預釋出,所以欄位名稱需要慎重考慮。
    說明:MySQL 在 Windows 下不區分大小寫,但在 Linux 下預設是區分大小寫。因此,資料庫名、
    表名、欄位名,都不允許出現任何大寫字母,避免節外生枝。
    正例:aliyun_admin,rdc_config,level3_name
    反例:AliyunAdmin,rdcConfig,level_3_name

  3. 【強制】表名不使用複數名詞。
    說明:表名應該僅僅表示表裡面的實體內容,不應該表示實體數量,對應於 DO 類名也是單數
    形式,符合表達習慣。

  4. 【強制】禁用保留字,如 desc、range、match、delayed 等,請參考 MySQL 官方保留字。

  5. 【強制】主鍵索引名為 pk欄位名;唯一索引名為 uk欄位名;普通索引名則為 idx_欄位名。
    說明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的簡稱。

  6. 【強制】小數型別為 decimal,禁止使用 float 和 double。
    說明:float 和 double 在儲存的時候,存在精度損失的問題,很可能在值的比較時,得到不
    正確的結果。如果儲存的資料範圍超過 decimal 的範圍,建議將資料拆成整數和小數分開儲存。

  7. 【強制】如果儲存的字串長度幾乎相等,使用 char 定長字串型別。

  8. 【強制】varchar 是可變長字串,不預先分配儲存空間,長度不要超過 5000,如果儲存長
    度大於此值,定義欄位型別為 text,獨立出來一張表,用主鍵來對應,避免影響其它欄位索
    引效率。

  9. 【強制】表必備三欄位:id, gmt_create, gmt_modified。
    說明:其中 id 必為主鍵,型別為 bigint unsigned、單表時自增、步長為 1。gmt_create,
    gmt_modified 的型別均為 datetime 型別,前者現在時表示主動建立,後者過去分詞表示被
    動更新。

  10. 【推薦】表的命名最好是加上“業務名稱_表的作用”。
    正例:alipay_task / force_project / trade_config

  11. 【推薦】庫名與應用名稱儘量一致。

  12. 【推薦】如果修改欄位含義或對欄位表示的狀態追加時,需要及時更新欄位註釋。

  13. 【推薦】欄位允許適當冗餘,以提高查詢效能,但必須考慮資料一致。冗餘欄位應遵循:
    1)不是頻繁修改的欄位。
    2)不是 varchar 超長欄位,更不能是 text 欄位。
    正例:商品類目名稱使用頻率高,欄位長度短,名稱基本一成不變,可在相關聯的表中冗餘存
    儲類目名稱,避免關聯查詢。

  14. 【推薦】單錶行數超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。
    說明:如果預計三年後的資料量根本達不到這個級別,請不要在建立表時就分庫分表。

  15. 【參考】合適的字元儲存長度,不但節約資料庫表空間、節約索引儲存,更重要的是提升檢
    索速度。
    正例:如下表,其中無符號值可以避免誤存負數,且擴大了表示範圍。

物件 年齡區間 型別 位元組 表示範圍
150 歲之內 tinyint unsigned 1 無符號值:0 到 255
數百歲 smallint unsigned 2 無符號值:0 到 65535
恐龍化石 數千萬年 int unsigned 4 無符號值:0 到約 42.9 億
太陽 約 50 億年 bigint unsigned 8 無符號值:0 到約 10 的 19 次方

4.2 索引規約

  1. 【強制】業務上具有唯一特性的欄位,即使是多個欄位的組合,也必須建成唯一索引。
    說明:不要以為唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查詢速度是明顯的;另
    外,即 使在應用層做了非常完善的校驗控制,只要沒有唯一索引,根據墨菲定律,必然有髒資料產生。

  2. 【強制】三個表以上禁止 join。需要 join 的欄位,資料型別必須絕對一致;多表關聯查詢時,
    保證被關聯的欄位需要有索引。
    說明:即使雙表 join 也要注意表索引、SQL 效能。

  3. 【強制】在 varchar 欄位上建立索引時,必須指定索引長度,沒必要對全欄位建立索引,根據
    實際文字區分度決定索引長度即可。
    說明:索引的長度與區分度是一對矛盾體,一般對字串型別資料,長度為 20 的索引,區分
    度會高達 90%以上,可以使用 count(distinct left(列名, 索引長度))/count(*)的區分度來確定。

  4. 【強制】頁面搜尋嚴禁左模糊或者全模糊,如果需要請走搜尋引擎來解決。
    說明:索引檔案具有 B-Tree 的最左字首匹配特性,如果左邊的值未確定,那麼無法使用此索引。

  5. 【推薦】如果有 order by 的場景,請注意利用索引的有序性。order by 最後的欄位是組合
    索引的一部分,並且放在索引組合順序的最後,避免出現 file_sort 的情況,影響查詢效能。
    正例:where a=? and b=? order by c; 索引:a_b_c
    反例:索引中有範圍查詢,那麼索引有序性無法利用,如:WHERE a>10 ORDER BY b; 索引a_b 無法排
    序。

  6. 【推薦】利用覆蓋索引來進行查詢操作,避免回表。
    說明:如果一本書需要知道第 11 章是什麼標題,會翻開第 11 章對應的那一頁嗎?目錄瀏覽
    一下就好,這個目錄就是起到覆蓋索引的作用。
    正例:能夠建立索引的種類分為主鍵索引、唯一索引、普通索引三種,而覆蓋索引只是一種查
    詢的一種效果,用 explain 的結果,extra 列會出現:using index。

  7. 【推薦】利用延遲關聯或者子查詢優化超多分頁場景。
    說明:MySQL 並不是跳過 offset 行,而是取 offset+N 行,然後返回放棄前 offset 行,返回
    N 行,那當 offset 特別大的時候,效率就非常的低下,要麼控制返回的總頁數,要麼對超過
    特定閾值的頁數進行 SQL 改寫。
    正例:先快速定位需要獲取的 id 段,然後再關聯:
    SELECT a.* FROM 表 1 a, (select id from 表 1 where 條件 LIMIT 100000,20 ) b where a.id=b.id

  8. 【推薦】SQL 效能優化的目標:至少要達到 range 級別,要求是 ref 級別,如果可以是 consts最
    好。
    說明:
    1)consts 單表中最多隻有一個匹配行(主鍵或者唯一索引),在優化階段即可讀取到資料。
    2)ref 指的是使用普通的索引(normal index)。
    3)range 對索引進行範圍檢索。
    反例:explain 表的結果,type=index,索引物理檔案全掃描,速度非常慢,這個 index 級
    別比較 range 還低,與全表掃描是小巫見大巫。

  9. 【推薦】建組合索引的時候,區分度最高的在最左邊
    正例:如果 where a=? and b=? ,如果 a 列的幾乎接近於唯一值,那麼只需要單建 idx_a索引即可。
    說明:存在非等號和等號混合時,在建索引時,請把等號條件的列前置。如:where c>? and
    d=? 那麼即使 c 的區分度更高,也必須把 d 放在索引的最前列,即索引 idx_d_c。

  10. 【推薦】防止因欄位型別不同造成的隱式轉換,導致索引失效。

  11. 【參考】建立索引時避免有如下極端誤解
    1)寧濫勿缺。認為一個查詢就需要建一個索引。
    2)寧缺勿濫。認為索引會消耗空間、嚴重拖慢更新和新增速度。
    3)抵制惟一索引。認為業務的惟一性一律需要在應用層通過“先查後插”方式解決。

4.3 SQL 語句

  1. 【強制】不要使用 count(列名)或 count(常量)來替代 count(),count()是 SQL92 定義的標準統計行
    數的語法, 跟資料庫無關,跟 NULL 和非 NULL 無關。
    說明:count(*)會統計值為 NULL 的行,而 count(列名)不會統計此列為 NULL 值的行。

  2. 【強制】count(distinct col) 計算該列除 NULL 之外的不重複行數,注意 count(distinct col1, col2)
    如果其中一 列全為 NULL,那麼即使另一列有不同的值,也返回為 0。

  3. 【強制】當某一列的值全是 NULL 時,count(col)的返回結果為 0,但 sum(col)的返回結果為
    NULL,因此使用 sum()時需注意 NPE (Null Pointer Exception)問題。
    正例:可以使用如下方式來避免 sum 的 NPE 問題:SELECT IF(ISNULL(SUM(g)),0,SUM(g))FROM table;

  4. 【強制】使用 ISNULL()來判斷是否為 NULL 值。
    說明:NULL 與任何值的直接比較都為 NULL。
    1) NULL<>NULL 的返回結果是 NULL,而不是 false。
    2) NULL=NULL 的返回結果是 NULL,而不是 true。
    3) NULL<>1 的返回結果是 NULL,而不是 true。

  5. 【強制】在程式碼中寫分頁查詢邏輯時,若 count 為 0 應直接返回,避免執行後面的分頁語句。

  6. 【強制】不得使用外來鍵與級聯,一切外來鍵概念必須在應用層解決。
    說明:以學生和成績的關係為例,學生表中的 student_id是主鍵,那麼成績表中的 student_id
    則為外來鍵。如果更新學生表中的 student_id,同時觸發成績表中的 student_id 更新,即為
    級聯更新。外來鍵與級聯更新適用於單機低併發,不適合分散式、高併發叢集;級聯更新是強阻
    塞,存在資料庫更新風暴的風險;外來鍵影響資料庫的插入速度。

  7. 【強制】禁止使用儲存過程,儲存過程難以除錯和擴充套件,更沒有移植性。

  8. 【強制】資料訂正(特別是刪除、修改記錄操作)時,要先 select,避免出現誤刪除,確認無誤才
    能執行更新 語句。

  9. 【推薦】in 操作能避免則避免,若實在避免不了,需要仔細評估 in 後邊的集合元素數量,控制在
    1000 個之內。

  10. 【參考】如果有國際化需要,所有的字元儲存與表示,均以 utf-8 編碼,注意字元統計函式的區
    別。
    說明:
    SELECT LENGTH("輕鬆工作"); 返回為 12SELECT CHARACTER_LENGTH("輕鬆工作"); 返回為 4
    如果需要儲存表情,那麼選擇 utf8mb4 來進行儲存,注意它與 utf-8 編碼的區別。

  11. 【參考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系統和事務日誌資源少,但 TRUNCATE
    無事務且不 觸發 trigger,有可能造成事故,故不建議在開發程式碼中使用此語句。
    說明:TRUNCATE TABLE 在功能上與不帶 WHERE 子句的 DELETE 語句相同

4.4 ORM對映

  1. 【強制】在表查詢中,一律不要使用 * 作為查詢的欄位列表,需要哪些欄位必須明確寫明。
    說明:1)增加查詢分析器解析成本。2)增減欄位容易與 resultMap 配置不一致。3)無用字
    段增加網路消耗,尤其是 text 型別的欄位。

  2. 【強制】POJO 類的布林屬性不能加 is,而資料庫欄位必須加 is_,要求在 resultMap 中進行
    欄位與屬性之間的對映。
    說明:參見定義 POJO 類以及資料庫欄位定義規定,在中增加對映,是必須的。
    在 MyBatis Generator 生成的程式碼中,需要進行對應的修改。

  3. 【強制】不要用 resultClass 當返回引數,即使所有類屬性名與資料庫欄位一一對應,也需
    要定義;反過來,每一個表也必然有一個 POJO 類與之對應。
    說明:配置對映關係,使欄位與 DO 類解耦,方便維護。

  4. 【強制】sql.xml 配置引數使用:#{},#param# 不要使用${} 此種方式容易出現 SQL 注入。

  5. 【強制】iBATIS 自帶的 queryForList(String statementName,int start,int size)不推薦使用。
    說明:其實現方式是在資料庫取到statementName對應的SQL語句的所有記錄,再通過subList
    取 start,size 的子集合。

正例:Map<String, Object> map = new HashMap<>();
map.put("start", start);
map.put("size", size);
  1. 【強制】不允許直接拿 HashMap 與 Hashtable 作為查詢結果集的輸出。
    說明:resultClass=”Hashtable”,會置入欄位名和屬性值,但是值的型別不可控。

  2. 【強制】更新資料表記錄時,必須同時更新記錄對應的 gmt_modified 欄位值為當前時間。

  3. 【推薦】不要寫一個大而全的資料更新介面。傳入為 POJO 類,不管是不是自己的目標更新字
    段,都進行 update table set c1=value1,c2=value2,c3=value3; 這是不對的。執行 SQL
    時,不要更新無改動的欄位,一是易出錯;二是效率低;三是增加 binlog 儲存。

  4. 【參考】@Transactional 事務不要濫用。事務會影響資料庫的 QPS,另外使用事務的地方需
    要考慮各方面的回滾方案,包括快取回滾、搜尋引擎回滾、訊息補償、統計修正等。

  5. 【參考】中的 compareValue 是與屬性值對比的常量,一般是數字,表示相等時帶
    上此條件;表示不為空且不為 null 時執行;表示不為 null 值時執行。

5、複雜SQL優化實戰

優化案例

前面用過的tbiguser表有10000000條記錄
建立tuser1表和tuser2表,並初始化若干的資料。

CREATE TABLE tuser1 ( id INT PRIMARY KEY auto_increment, NAME VARCHAR ( 255 ), address VARCHAR ( 255 ) );
CREATE TABLE tuser2 ( id INT PRIMARY KEY auto_increment, NAME VARCHAR ( 255 ), address VARCHAR ( 255 ) );
mysql> select * from tuser1 ;
+----+----------+-----------+
| id | name | address |
+----+----------+-----------+
| 1 | zhangfei | tianjin |
| 2 | zhaoyun | tianjin |
| 3 | diaochan | guangzhou |
| 4 | diaochan | xianggang |
| 5 | diaochan | hebei |
| 6 | diaochan | dongbei |
| 7 | diaochan | dongbei |
| 8 | diaochan | dongbei |
| 9 | diaochan | dongbei |
| 10 | diaochan | dongbei |
| 11 | 1 | 1 |
| 12 | 1 | 1 |
| 13 | 1 | 1 |
| 14 | 1 | 1 |
| 15 | 1 | 1 |
| 16 | 1 | 1 |
| 17 | 1 | 1 |
| 18 | 1 | 1 |
| 19 | 1 | 1 |
| 20 | 1 | 1 |
+----+----------+-----------+
20 rows in set (0.00 sec)
mysql> select * from tuser2;
+----+----------+-----------+
| id | name | address |
+----+----------+-----------+
| 1 | zhangfei | shanghai |
| 2 | zhaoyun | shanghai |
| 3 | diaochan | guangzhou |
| 4 | diaochan | xianggang |
| 5 | diaochan | hebei |
| 6 | diaochan | dongbei |
| 7 | diaochan | dongbei |
| 8 | diaochan | dongbei |
| 9 | diaochan | dongbei |
| 10 | diaochan | dongbei |
| 11 | 1 | 1 |
| 12 | 1 | 1 |
| 13 | 1 | 1 |
| 14 | 1 | 1 |
| 15 | 1 | 1 |
| 16 | 1 | 1 |
| 17 | 1 | 1 |
| 18 | 1 | 1 |
| 19 | 1 | 1 |
| 20 | 1 | 1 |
+----+----------+-----------+
20 rows in set (0.00 sec)

可以看到tuser1和tuser2表有重複的資料。
需求:tbiguser表按照地區分組統計求和,要求是在tuser1表和tuser2表中出現過的地區
按照需求寫出SQL:

mysql> select count(id) num , address from tbiguser where address in (select
distinct address from tuser1) group by address union select count(id) num ,
address from tbiguser where address in (select distinct address from tuser2)
group by address ;
+-----+----------+
| num | address |
+-----+----------+
| 105 | tianjin |
| 100 | shanghai |
+-----+----------+
2 rows in set (14.43 sec)

通過explain可以看到:

mysql> explain select count(id) num , address from tbiguser where address in
(select distinct address from tuser1) group by address union select
count(id) num , address from tbiguser where address in (select distinct
address from tuser2) group by address ;
+----+--------------+-------------+------+---------------+------+---------+-----
-+---------+----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+--------------+-------------+------+---------------+------+---------+-----
-+---------+----------------------------------------------------+
| 1 | PRIMARY | <subquery2> | ALL | NULL | NULL | NULL | NULL
| NULL | Using temporary; Using filesort |
| 1 | PRIMARY | tbiguser | ALL | NULL | NULL | NULL | NULL
| 9754360 | Using where; Using join buffer (Block Nested Loop) |
| 2 | MATERIALIZED | tuser1 | ALL | NULL | NULL | NULL | NULL
| 20 | NULL |
| 3 | UNION | <subquery4> | ALL | NULL | NULL | NULL | NULL
| NULL | Using temporary; Using filesort |
| 3 | UNION | tbiguser | ALL | NULL | NULL | NULL | NULL
| 9754360 | Using where; Using join buffer (Block Nested Loop) |
| 4 | MATERIALIZED | tuser2 | ALL | NULL | NULL | NULL | NULL
| 20 | NULL |
| NULL | UNION RESULT | <union1,3> | ALL | NULL | NULL | NULL |
NULL | NULL | Using temporary |
+----+--------------+-------------+------+---------------+------+---------+-----
-+---------+----------------------------------------------------+
7 rows in set (0.00 sec)

type:為ALL 說明沒有索引,全表掃描
Using temporary:說明使用了臨時表
Using filesort :說明使用了檔案排序
Using where:沒有索引下推,在Server層進行了全表掃描和過濾
Using join buffer(Block Nested Loop):關聯沒有索引,有關聯優化
第一次優化:
給address加索引

--給address加索引
alter table tbiguser add index idx_addr(address);
alter table tuser1 add index idx_addr(address);
alter table tuser2 add index idx_addr(address);
--再次執行SQL
select count(id) num , address from tbiguser where address in (select
distinct address from tuser1) group by address union select count(id) num ,
address from tbiguser where address in (select distinct address from tuser2)
group by address ;
+-----+----------+
| num | address |
+-----+----------+
| 105 | tianjin |
| 100 | shanghai |
+-----+----------+
2 rows in set (13.61 sec)
--檢視執行計劃
mysql> explain select count(id) num , address from tbiguser where address in
(select distinct address from tuser1) group by address union select
count(id) num , address from tbiguser where address in (select distinct
address from tuser2) group by address ;
+----+--------------+-------------+--------+---------------+------------+-------
--+-----------------------+---------+--------------------------+
| id | select_type | table | type | possible_keys | key |
key_len | ref | rows | Extra |
+----+--------------+-------------+--------+---------------+------------+-------
--+-----------------------+---------+--------------------------+
| 1 | PRIMARY | tbiguser | index | idx_addr | idx_addr | 768
| NULL | 9754360 | Using where; Using index |
| 1 | PRIMARY | <subquery2> | eq_ref | <auto_key> | <auto_key> | 768
| demo.tbiguser.address | 1 | NULL |
| 2 | MATERIALIZED | tuser1 | index | idx_addr | idx_addr | 768
| NULL | 20 | Using index |
| 3 | UNION | tbiguser | index | idx_addr | idx_addr | 768
| NULL | 9754360 | Using where; Using index |
| 3 | UNION | <subquery4> | eq_ref | <auto_key> | <auto_key> | 768
| demo.tbiguser.address | 1 | NULL |
| 4 | MATERIALIZED | tuser2 | index | idx_addr | idx_addr | 768
| NULL | 20 | Using index |
| NULL | UNION RESULT | <union1,3> | ALL | NULL | NULL | NULL
| NULL | NULL | Using temporary |
+----+--------------+-------------+--------+---------------+------------+-------
--+-----------------------+---------+--------------------------+

type:index ,說明用到了索引 : 覆蓋索引
Using temporary :有臨時表
Using where :沒有索引下推,在Server層進行了全表掃描和過濾
第二次優化:

--修改sql
select count(id) num , address from tbiguser where address in (select distinct
address from tuser1) or address in (select distinct address from tuser2) group
by address order by address;
+-----+----------+
| num | address |
+-----+----------+
| 100 | shanghai |
| 105 | tianjin |
+-----+----------+
2 rows in set (3.54 sec)
--執行執行計劃
explain select count(id) num , address from tbiguser where address in (select
distinct address from tuser1) or address in (select distinct address from
tuser2) group by address order by address;
+----+-------------+----------+-------+---------------+----------+---------+----
--+---------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+-------------+----------+-------+---------------+----------+---------+----
--+---------+--------------------------+
| 1 | PRIMARY | tbiguser | index | idx_addr | idx_addr | 768 |
NULL | 9754360 | Using where; Using index |
| 3 | SUBQUERY | tuser2 | index | idx_addr | idx_addr | 768 |
NULL | 20 | Using index |
| 2 | SUBQUERY | tuser1 | index | idx_addr | idx_addr | 768 |
NULL | 20 | Using index |
+----+-------------+----------+-------+---------------+----------+---------+----
--+---------+--------------------------+
3 rows in set (0.00 sec)

type:index
沒有了臨時表
第三次優化:
從前面的執行計劃可以看出,索引只是使用了覆蓋索引,rows=9754360, 說明還是幾乎掃描了全表的

利用address索引,先過濾資料

mysql> select distinct b.* from tuser1 a,tbiguser b where a.address=b.address;
+-----+----------+------------+------+------+--------+---------+
| id | nickname | loginname | age | sex | status | address |
+-----+----------+------------+------+------+--------+---------+
| 101 | zy101 | zhaoyun101 | 23 | 1 | 1 | tianjin |
| 102 | zy102 | zhaoyun102 | 23 | 1 | 1 | tianjin |
| 103 | zy103 | zhaoyun103 | 23 | 1 | 1 | tianjin |
| 104 | zy104 | zhaoyun104 | 23 | 1 | 1 | tianjin |
| 105 | zy105 | zhaoyun105 | 23 | 1 | 1 | tianjin |
| 106 | zy106 | zhaoyun106 | 23 | 1 | 1 | tianjin |
| 107 | zy107 | zhaoyun107 | 23 | 1 | 1 | tianjin |
| 108 | zy108 | zhaoyun108 | 23 | 1 | 1 | tianjin |
| 109 | zy109 | zhaoyun109 | 23 | 1 | 1 | tianjin |
| 110 | zy110 | zhaoyun110 | 23 | 1 | 1 | tianjin |
| 111 | zy111 | zhaoyun111 | 23 | 1 | 1 | tianjin |
| 112 | zy112 | zhaoyun112 | 23 | 1 | 1 | tianjin |
| 113 | zy113 | zhaoyun113 | 23 | 1 | 1 | tianjin |
| 114 | zy114 | zhaoyun114 | 23 | 1 | 1 | tianjin |
| 115 | zy115 | zhaoyun115 | 23 | 1 | 1 | tianjin |
| 116 | zy116 | zhaoyun116 | 23 | 1 | 1 | tianjin |
| 117 | zy117 | zhaoyun117 | 23 | 1 | 1 | tianjin |
| 118 | zy118 | zhaoyun118 | 23 | 1 | 1 | tianjin |
| 119 | zy119 | zhaoyun119 | 23 | 1 | 1 | tianjin |
| 120 | zy120 | zhaoyun120 | 23 | 1 | 1 | tianjin |
| 121 | zy121 | zhaoyun121 | 23 | 1 | 1 | tianjin |
| 122 | zy122 | zhaoyun122 | 23 | 1 | 1 | tianjin |
| 123 | zy123 | zhaoyun123 | 23 | 1 | 1 | tianjin |
| 124 | zy124 | zhaoyun124 | 23 | 1 | 1 | tianjin |
| 125 | zy125 | zhaoyun125 | 23 | 1 | 1 | tianjin |
| 126 | zy126 | zhaoyun126 | 23 | 1 | 1 | tianjin |
| 127 | zy127 | zhaoyun127 | 23 | 1 | 1 | tianjin |
| 128 | zy128 | zhaoyun128 | 23 | 1 | 1 | tianjin |
| 129 | zy129 | zhaoyun129 | 23 | 1 | 1 | tianjin |
| 130 | zy130 | zhaoyun130 | 23 | 1 | 1 | tianjin |
| 131 | zy131 | zhaoyun131 | 23 | 1 | 1 | tianjin |
| 132 | zy132 | zhaoyun132 | 23 | 1 | 1 | tianjin |
| 133 | zy133 | zhaoyun133 | 23 | 1 | 1 | tianjin |
| 134 | zy134 | zhaoyun134 | 23 | 1 | 1 | tianjin |
| 135 | zy135 | zhaoyun135 | 23 | 1 | 1 | tianjin |
| 136 | zy136 | zhaoyun136 | 23 | 1 | 1 | tianjin |
| 137 | zy137 | zhaoyun137 | 23 | 1 | 1 | tianjin |
| 138 | zy138 | zhaoyun138 | 23 | 1 | 1 | tianjin |
| 139 | zy139 | zhaoyun139 | 23 | 1 | 1 | tianjin |
| 140 | zy140 | zhaoyun140 | 23 | 1 | 1 | tianjin |
| 141 | zy141 | zhaoyun141 | 23 | 1 | 1 | tianjin |
| 142 | zy142 | zhaoyun142 | 23 | 1 | 1 | tianjin |
| 143 | zy143 | zhaoyun143 | 23 | 1 | 1 | tianjin |
| 144 | zy144 | zhaoyun144 | 23 | 1 | 1 | tianjin |
| 145 | zy145 | zhaoyun145 | 23 | 1 | 1 | tianjin |
| 146 | zy146 | zhaoyun146 | 23 | 1 | 1 | tianjin |
| 147 | zy147 | zhaoyun147 | 23 | 1 | 1 | tianjin |
| 148 | zy148 | zhaoyun148 | 23 | 1 | 1 | tianjin |
| 149 | zy149 | zhaoyun149 | 23 | 1 | 1 | tianjin |
| 150 | zy150 | zhaoyun150 | 23 | 1 | 1 | tianjin |
| 151 | zy151 | zhaoyun151 | 23 | 1 | 1 | tianjin |
| 152 | zy152 | zhaoyun152 | 23 | 1 | 1 | tianjin |
| 153 | zy153 | zhaoyun153 | 23 | 1 | 1 | tianjin |
| 154 | zy154 | zhaoyun154 | 23 | 1 | 1 | tianjin |
| 155 | zy155 | zhaoyun155 | 23 | 1 | 1 | tianjin |
| 156 | zy156 | zhaoyun156 | 23 | 1 | 1 | tianjin |
| 157 | zy157 | zhaoyun157 | 23 | 1 | 1 | tianjin |
| 158 | zy158 | zhaoyun158 | 23 | 1 | 1 | tianjin |
| 159 | zy159 | zhaoyun159 | 23 | 1 | 1 | tianjin |
| 160 | zy160 | zhaoyun160 | 23 | 1 | 1 | tianjin |
| 161 | zy161 | zhaoyun161 | 23 | 1 | 1 | tianjin |
| 162 | zy162 | zhaoyun162 | 23 | 1 | 1 | tianjin |
| 163 | zy163 | zhaoyun163 | 23 | 1 | 1 | tianjin |
| 164 | zy164 | zhaoyun164 | 23 | 1 | 1 | tianjin |
| 165 | zy165 | zhaoyun165 | 23 | 1 | 1 | tianjin |
| 166 | zy166 | zhaoyun166 | 23 | 1 | 1 | tianjin |
| 167 | zy167 | zhaoyun167 | 23 | 1 | 1 | tianjin |
| 168 | zy168 | zhaoyun168 | 23 | 1 | 1 | tianjin |
| 169 | zy169 | zhaoyun169 | 23 | 1 | 1 | tianjin |
| 170 | zy170 | zhaoyun170 | 23 | 1 | 1 | tianjin |
| 171 | zy171 | zhaoyun171 | 23 | 1 | 1 | tianjin |
| 172 | zy172 | zhaoyun172 | 23 | 1 | 1 | tianjin |
| 173 | zy173 | zhaoyun173 | 23 | 1 | 1 | tianjin |
| 174 | zy174 | zhaoyun174 | 23 | 1 | 1 | tianjin |
| 175 | zy175 | zhaoyun175 | 23 | 1 | 1 | tianjin |
| 176 | zy176 | zhaoyun176 | 23 | 1 | 1 | tianjin |
| 177 | zy177 | zhaoyun177 | 23 | 1 | 1 | tianjin |
| 178 | zy178 | zhaoyun178 | 23 | 1 | 1 | tianjin |
| 179 | zy179 | zhaoyun179 | 23 | 1 | 1 | tianjin |
| 180 | zy180 | zhaoyun180 | 23 | 1 | 1 | tianjin |
| 181 | zy181 | zhaoyun181 | 23 | 1 | 1 | tianjin |
| 182 | zy182 | zhaoyun182 | 23 | 1 | 1 | tianjin |
| 183 | zy183 | zhaoyun183 | 23 | 1 | 1 | tianjin |
| 184 | zy184 | zhaoyun184 | 23 | 1 | 1 | tianjin |
| 185 | zy185 | zhaoyun185 | 23 | 1 | 1 | tianjin |
| 186 | zy186 | zhaoyun186 | 23 | 1 | 1 | tianjin |
| 187 | zy187 | zhaoyun187 | 23 | 1 | 1 | tianjin |
| 188 | zy188 | zhaoyun188 | 23 | 1 | 1 | tianjin |
| 189 | zy189 | zhaoyun189 | 23 | 1 | 1 | tianjin |
| 190 | zy190 | zhaoyun190 | 23 | 1 | 1 | tianjin |
| 191 | zy191 | zhaoyun191 | 23 | 1 | 1 | tianjin |
| 192 | zy192 | zhaoyun192 | 23 | 1 | 1 | tianjin |
| 193 | zy193 | zhaoyun193 | 23 | 1 | 1 | tianjin |
| 194 | zy194 | zhaoyun194 | 23 | 1 | 1 | tianjin |
| 195 | zy195 | zhaoyun195 | 23 | 1 | 1 | tianjin |
| 196 | zy196 | zhaoyun196 | 23 | 1 | 1 | tianjin |
| 197 | zy197 | zhaoyun197 | 23 | 1 | 1 | tianjin |
| 198 | zy198 | zhaoyun198 | 23 | 1 | 1 | tianjin |
| 199 | zy199 | zhaoyun199 | 23 | 1 | 1 | tianjin |
| 200 | zy200 | zhaoyun200 | 23 | 1 | 1 | tianjin |
| 201 | zy201 | zhaoyun201 | 23 | 1 | 1 | tianjin |
| 202 | zy202 | zhaoyun202 | 23 | 1 | 1 | tianjin |
| 203 | zy203 | zhaoyun203 | 23 | 1 | 1 | tianjin |
| 204 | zy204 | zhaoyun204 | 23 | 1 | 1 | tianjin |
| 205 | zy205 | zhaoyun205 | 23 | 1 | 1 | tianjin |
+-----+----------+------------+------+------+--------+---------+
105 rows in set (0.00 sec)
--檢視執行計劃
mysql> explain select distinct b.* from tuser1 a,tbiguser b where
a.address=b.address;
+----+-------------+-------+-------+---------------+----------+---------+-------
---------+---------+-------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+-------------+-------+-------+---------------+----------+---------+-------
---------+---------+-------------------------------------------+
| 1 | SIMPLE | a | index | idx_addr | idx_addr | 768 | NULL
| 20 | Using where; Using index; Using temporary |
| 1 | SIMPLE | b | ref | idx_addr | idx_addr | 768 |
demo.a.address | 2438590 | NULL |
+----+-------------+-------+-------+---------------+----------+---------+-------
---------+---------+-------------------------------------------+
2 rows in set (0.00 sec)

type:ref
rows:2438590
說明使用了address索引做關聯
同理:

mysql> select distinct b.* from tuser2 a,tbiguser b where a.address=b.address;
+-----+----------+------------+------+------+--------+----------+
| id | nickname | loginname | age | sex | status | address |
+-----+----------+------------+------+------+--------+----------+
| 1 | zy1 | zhaoyun1 | 23 | 1 | 1 | shanghai |
| 2 | zy2 | zhaoyun2 | 23 | 1 | 1 | shanghai |
| 3 | zy3 | zhaoyun3 | 23 | 1 | 1 | shanghai |
| 4 | zy4 | zhaoyun4 | 23 | 1 | 1 | shanghai |
| 5 | zy5 | zhaoyun5 | 23 | 1 | 1 | shanghai |
| 6 | zy6 | zhaoyun6 | 23 | 1 | 1 | shanghai |
| 7 | zy7 | zhaoyun7 | 23 | 1 | 1 | shanghai |
| 8 | zy8 | zhaoyun8 | 23 | 1 | 1 | shanghai |
| 9 | zy9 | zhaoyun9 | 23 | 1 | 1 | shanghai |
| 10 | zy10 | zhaoyun10 | 23 | 1 | 1 | shanghai |
| 11 | zy11 | zhaoyun11 | 23 | 1 | 1 | shanghai |
| 12 | zy12 | zhaoyun12 | 23 | 1 | 1 | shanghai |
| 13 | zy13 | zhaoyun13 | 23 | 1 | 1 | shanghai |
| 14 | zy14 | zhaoyun14 | 23 | 1 | 1 | shanghai |
| 15 | zy15 | zhaoyun15 | 23 | 1 | 1 | shanghai |
| 16 | zy16 | zhaoyun16 | 23 | 1 | 1 | shanghai |
| 17 | zy17 | zhaoyun17 | 23 | 1 | 1 | shanghai |
| 18 | zy18 | zhaoyun18 | 23 | 1 | 1 | shanghai |
| 19 | zy19 | zhaoyun19 | 23 | 1 | 1 | shanghai |
| 20 | zy20 | zhaoyun20 | 23 | 1 | 1 | shanghai |
| 21 | zy21 | zhaoyun21 | 23 | 1 | 1 | shanghai |
| 22 | zy22 | zhaoyun22 | 23 | 1 | 1 | shanghai |
| 23 | zy23 | zhaoyun23 | 23 | 1 | 1 | shanghai |
| 24 | zy24 | zhaoyun24 | 23 | 1 | 1 | shanghai |
| 25 | zy25 | zhaoyun25 | 23 | 1 | 1 | shanghai |
| 26 | zy26 | zhaoyun26 | 23 | 1 | 1 | shanghai |
| 27 | zy27 | zhaoyun27 | 23 | 1 | 1 | shanghai |
| 28 | zy28 | zhaoyun28 | 23 | 1 | 1 | shanghai |
| 29 | zy29 | zhaoyun29 | 23 | 1 | 1 | shanghai |
| 30 | zy30 | zhaoyun30 | 23 | 1 | 1 | shanghai |
| 31 | zy31 | zhaoyun31 | 23 | 1 | 1 | shanghai |
| 32 | zy32 | zhaoyun32 | 23 | 1 | 1 | shanghai |
| 33 | zy33 | zhaoyun33 | 23 | 1 | 1 | shanghai |
| 34 | zy34 | zhaoyun34 | 23 | 1 | 1 | shanghai |
| 35 | zy35 | zhaoyun35 | 23 | 1 | 1 | shanghai |
| 36 | zy36 | zhaoyun36 | 23 | 1 | 1 | shanghai |
| 37 | zy37 | zhaoyun37 | 23 | 1 | 1 | shanghai |
| 38 | zy38 | zhaoyun38 | 23 | 1 | 1 | shanghai |
| 39 | zy39 | zhaoyun39 | 23 | 1 | 1 | shanghai |
| 40 | zy40 | zhaoyun40 | 23 | 1 | 1 | shanghai |
| 41 | zy41 | zhaoyun41 | 23 | 1 | 1 | shanghai |
| 42 | zy42 | zhaoyun42 | 23 | 1 | 1 | shanghai |
| 43 | zy43 | zhaoyun43 | 23 | 1 | 1 | shanghai |
| 44 | zy44 | zhaoyun44 | 23 | 1 | 1 | shanghai |
| 45 | zy45 | zhaoyun45 | 23 | 1 | 1 | shanghai |
| 46 | zy46 | zhaoyun46 | 23 | 1 | 1 | shanghai |
| 47 | zy47 | zhaoyun47 | 23 | 1 | 1 | shanghai |
| 48 | zy48 | zhaoyun48 | 23 | 1 | 1 | shanghai |
| 49 | zy49 | zhaoyun49 | 23 | 1 | 1 | shanghai |
| 50 | zy50 | zhaoyun50 | 23 | 1 | 1 | shanghai |
| 51 | zy51 | zhaoyun51 | 23 | 1 | 1 | shanghai |
| 52 | zy52 | zhaoyun52 | 23 | 1 | 1 | shanghai |
| 53 | zy53 | zhaoyun53 | 23 | 1 | 1 | shanghai |
| 54 | zy54 | zhaoyun54 | 23 | 1 | 1 | shanghai |
| 55 | zy55 | zhaoyun55 | 23 | 1 | 1 | shanghai |
| 56 | zy56 | zhaoyun56 | 23 | 1 | 1 | shanghai |
| 57 | zy57 | zhaoyun57 | 23 | 1 | 1 | shanghai |
| 58 | zy58 | zhaoyun58 | 23 | 1 | 1 | shanghai |
| 59 | zy59 | zhaoyun59 | 23 | 1 | 1 | shanghai |
| 60 | zy60 | zhaoyun60 | 23 | 1 | 1 | shanghai |
| 61 | zy61 | zhaoyun61 | 23 | 1 | 1 | shanghai |
| 62 | zy62 | zhaoyun62 | 23 | 1 | 1 | shanghai |
| 63 | zy63 | zhaoyun63 | 23 | 1 | 1 | shanghai |
| 64 | zy64 | zhaoyun64 | 23 | 1 | 1 | shanghai |
| 65 | zy65 | zhaoyun65 | 23 | 1 | 1 | shanghai |
| 66 | zy66 | zhaoyun66 | 23 | 1 | 1 | shanghai |
| 67 | zy67 | zhaoyun67 | 23 | 1 | 1 | shanghai |
| 68 | zy68 | zhaoyun68 | 23 | 1 | 1 | shanghai |
| 69 | zy69 | zhaoyun69 | 23 | 1 | 1 | shanghai |
| 70 | zy70 | zhaoyun70 | 23 | 1 | 1 | shanghai |
| 71 | zy71 | zhaoyun71 | 23 | 1 | 1 | shanghai |
| 72 | zy72 | zhaoyun72 | 23 | 1 | 1 | shanghai |
| 73 | zy73 | zhaoyun73 | 23 | 1 | 1 | shanghai |
| 74 | zy74 | zhaoyun74 | 23 | 1 | 1 | shanghai |
| 75 | zy75 | zhaoyun75 | 23 | 1 | 1 | shanghai |
| 76 | zy76 | zhaoyun76 | 23 | 1 | 1 | shanghai |
| 77 | zy77 | zhaoyun77 | 23 | 1 | 1 | shanghai |
| 78 | zy78 | zhaoyun78 | 23 | 1 | 1 | shanghai |
| 79 | zy79 | zhaoyun79 | 23 | 1 | 1 | shanghai |
| 80 | zy80 | zhaoyun80 | 23 | 1 | 1 | shanghai |
| 81 | zy81 | zhaoyun81 | 23 | 1 | 1 | shanghai |
| 82 | zy82 | zhaoyun82 | 23 | 1 | 1 | shanghai |
| 83 | zy83 | zhaoyun83 | 23 | 1 | 1 | shanghai |
| 84 | zy84 | zhaoyun84 | 23 | 1 | 1 | shanghai |
| 85 | zy85 | zhaoyun85 | 23 | 1 | 1 | shanghai |
| 86 | zy86 | zhaoyun86 | 23 | 1 | 1 | shanghai |
| 87 | zy87 | zhaoyun87 | 23 | 1 | 1 | shanghai |
| 88 | zy88 | zhaoyun88 | 23 | 1 | 1 | shanghai |
| 89 | zy89 | zhaoyun89 | 23 | 1 | 1 | shanghai |
| 90 | zy90 | zhaoyun90 | 23 | 1 | 1 | shanghai |
| 91 | zy91 | zhaoyun91 | 23 | 1 | 1 | shanghai |
| 92 | zy92 | zhaoyun92 | 23 | 1 | 1 | shanghai |
| 93 | zy93 | zhaoyun93 | 23 | 1 | 1 | shanghai |
| 94 | zy94 | zhaoyun94 | 23 | 1 | 1 | shanghai |
| 95 | zy95 | zhaoyun95 | 23 | 1 | 1 | shanghai |
| 96 | zy96 | zhaoyun96 | 23 | 1 | 1 | shanghai |
| 97 | zy97 | zhaoyun97 | 23 | 1 | 1 | shanghai |
| 98 | zy98 | zhaoyun98 | 23 | 1 | 1 | shanghai |
| 99 | zy99 | zhaoyun99 | 23 | 1 | 1 | shanghai |
| 100 | zy100 | zhaoyun100 | 23 | 1 | 1 | shanghai |
+-----+----------+------------+------+------+--------+----------+
100 rows in set (0.00 sec)
--檢視執行計劃
mysql> explain select distinct b.* from tuser2 a,tbiguser b where
a.address=b.address;
+----+-------------+-------+-------+---------------+----------+---------+-------
---------+---------+-------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+-------------+-------+-------+---------------+----------+---------+-------
---------+---------+-------------------------------------------+
| 1 | SIMPLE | a | index | idx_addr | idx_addr | 768 | NULL
| 20 | Using where; Using index; Using temporary |
| 1 | SIMPLE | b | ref | idx_addr | idx_addr | 768 |
demo.a.address | 2438590 | NULL |
+----+-------------+-------+-------+---------------+----------+---------+-------
---------+---------+-------------------------------------------+
2 rows in set (0.00 sec)

type:ref
rows:2438590
說明使用了address索引做關聯
合併結果集後再分組求和

select count(x.id),x.address
from
(select distinct b.* from tuser1 a,tbiguser b where a.address=b.address union
all select distinct b.* from tuser2 a,tbiguser b where a.address=b.address) x
group by x.address;
+-------------+----------+
| count(x.id) | address |
+-------------+----------+
| 100 | shanghai |
| 105 | tianjin |
+-------------+----------+
2 rows in set (0.00 sec)
--檢視執行計劃
mysql> explain select count(x.id),x.address
-> from
-> (select distinct b.* from tuser1 a,tbiguser b where a.address=b.address
union all select distinct b.* from tuser2 a,tbiguser b where
a.address=b.address) x group by x.address;
+----+--------------+------------+-------+---------------+----------+---------+-
---------------+----------+-------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len |
ref | rows | Extra |
+----+--------------+------------+-------+---------------+----------+---------+-
---------------+----------+-------------------------------------------+
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL |
NULL | 97543600 | Using temporary; Using filesort |
| 2 | DERIVED | a | index | idx_addr | idx_addr | 768 |
NULL | 20 | Using where; Using index; Using temporary |
| 2 | DERIVED | b | ref | idx_addr | idx_addr | 768 |
demo.a.address | 2438590 | Distinct |
| 3 | UNION | a | index | idx_addr | idx_addr | 768 |
NULL | 20 | Using where; Using index; Using temporary |
| 3 | UNION | b | ref | idx_addr | idx_addr | 768 |
demo.a.address | 2438590 | Distinct |
| NULL | UNION RESULT | <union2,3> | ALL | NULL | NULL | NULL
| NULL | NULL | Using temporary |
+----+--------------+------------+-------+---------------+----------+---------+-
---------------+----------+-------------------------------------------+
6 rows in set (0.00 sec)

DERIVED:派生表
最終優化
將派生表寫成檢視

--建立檢視
create view v_tuser as select distinct b.* from tuser1 a,tbiguser b where
a.address=b.address union all select distinct b.* from tuser2 a,tbiguser b where
a.address=b.address;
--執行SQL
select count(id) cont ,address from v_tuser group by address order by address;
+------+----------+
| cont | address |
+------+----------+
| 100 | shanghai |
| 105 | tianjin |
+------+----------+
2 rows in set (0.00 sec)

優化結果:從最初的將近14秒優化到不到1秒。
優化總結:

  • 開啟慢查詢日誌,定位執行慢的SQL語句

  • 利用explain執行計劃,檢視SQL執行情況

  • 關注索引使用情況:type

  • 關注Rows:行掃描

  • 關注Extra:沒有資訊最好

  • 加索引後,檢視索引使用情況,index只是覆蓋索引,並不算很好的使用索引

  • 如果有關聯儘量將索引用到eq_ref或ref級別

  • 複雜SQL可以做成檢視,檢視在MySQL內部有優化,而且開發也比較友好

  • 對於複雜的SQL要逐一分析,找到比較費時的SQL語句片段進行優化

別廢話,拿你程式碼給我看。