1. 程式人生 > >讓天下沒有難用的資料庫 » 為什麼我的RDS慢了?

讓天下沒有難用的資料庫 » 為什麼我的RDS慢了?

為什麼我的RDS突然變慢了?相信這是大多數客戶在使用RDS中經常遇到的頭疼問題。下面我將通過實際的真實案例來分析一下使用者在使用RDS中慢的原因:

案例一:使用者從PGSQL遷移到RDS後,發現RDS變慢了.

問題描述:使用者的資料庫(pgsql)遷移到RDS(mysql)後,發現相同的一條sql語句,資料量百萬級左右,在原來postgreSQL中執行大概是0.015s,而在RDS下直接執行是6分20秒左右,執行非常的慢,已經嚴重的影響了使用者使用RDS使用的信心.

可能原因:為什麼在使用者的資料庫上執行只需要0.015s,而到RDS後變為了6分20s?根據經驗,很有可能是SQL的執行計劃改變了,而導致執行時間劇增.

問題排查:通過explain檢視sql的執行計劃,一步一步進行優化.

mysql> EXPLAIN SELECT a.oid,
    ->        a.tid,
    ->        a.created,
    ->        a.pay_time,
    ->        a.consign_time,
    ->        a.endtime,
    ->        c.nick,
    ->        c.user_name,
    -
> e.shop_name, -> d.num_iid, -> d.title, -> f.status_name, -> a.payment, -> a.num, -> a.refund_status, -> a.refund_fee, -> d.outer_id -> FROM
xxxx_test a -> LEFT JOIN xxxx_test1 c ON a.customerno = c.user_id -> LEFT JOIN xxxx_test2 d ON a.num_iid = d.num_iid -> LEFT JOIN xxxx_test3 e ON a.dp_id = e.shop_id -> LEFT JOIN xxxx_test4 f ON a.ccms_order_status = f.status_id -> WHERE EXISTS -> (SELECT 1 FROM xxxx_test5 b WHERE a.tid = b.tid); +----+--------------------+--------------------+--------+---------------+---------+---------+---------------------------+---------+-------------+ | id | select_type | TABLE | TYPE | possible_keys | KEY | key_len | REF | ROWS | Extra | +----+--------------------+--------------------+--------+---------------+---------+---------+---------------------------+---------+-------------+ | 1 | PRIMARY | a | ALL | NULL | NULL | NULL | NULL | 1055789 | USING WHERE | | 1 | PRIMARY | retail_tb_customer | eq_ref | PRIMARY | PRIMARY | 152 | xxxx_db.a.customerno | 1 | | | 1 | PRIMARY | d | eq_ref | PRIMARY | PRIMARY | 152 | xxxx_db.a.num_iid | 1 | | | 1 | PRIMARY | e | eq_ref | PRIMARY | PRIMARY | 152 | xxxx_db.a.dp_id | 1 | | | 1 | PRIMARY | f | eq_ref | PRIMARY | PRIMARY | 2 | xxxx_db.a.ccms_order_status | 1 | | | 2 | DEPENDENT SUBQUERY | b | ALL | NULL | NULL | NULL | NULL | 452 | USING WHERE | +----+--------------------+--------------------+--------+---------------+---------+---------+---------------------------+---------+-------------+

通過分析,我們可以從執行計劃上分析b表做了一個全表掃描(執行計劃的最後一行),檢視b表中tid並無索引,所以我們這裡可以進行優化,來減少查詢過程中關聯的行數,來達到優化:

ALTER TABLE xxxx_test5 ADD INDEX ind_twf_node_order_analysis_tid(tid);
mysql> EXPLAIN SELECT a.oid,
    ->        a.tid,
    ->        a.created,
    ->        a.pay_time,
    ->        a.consign_time,
    ->        a.endtime,
    ->        c.nick,
    ->        c.user_name,
    ->        e.shop_name,
    ->        d.num_iid,
    ->        d.title,
    ->        f.status_name,
    ->        a.payment,
    ->        a.num,
    ->        a.refund_status,
    ->        a.refund_fee,
    ->        d.outer_id
    ->   FROM xxxx_test a
    ->   LEFT JOIN xxxx_test1 c ON a.customerno = c.user_id
    ->   LEFT JOIN xxxx_test2 d ON a.num_iid = d.num_iid
    ->   LEFT JOIN xxxx_test3 e ON a.dp_id = e.shop_id
    ->   LEFT JOIN xxxx_test4 f ON a.ccms_order_status = f.status_id
    ->  WHERE EXISTS
    ->  (SELECT 1 FROM xxxx_test5 b WHERE a.tid = b.tid);
+----+--------------------+--------------------+--------+---------------------------------+---------------------------------+---------+---------------------------+---------+--------------------------+
| id | select_type        | TABLE              | TYPE   | possible_keys                   | KEY                             | key_len | REF                       | ROWS    | Extra                    |
+----+--------------------+--------------------+--------+---------------------------------+---------------------------------+---------+---------------------------+---------+--------------------------+
|  1 | PRIMARY            | a                  | ALL    | NULL                            | NULL                            | NULL    | NULL                      | 1055789 | USING WHERE              |
|  1 | PRIMARY            | retail_tb_customer | eq_ref | PRIMARY                         | PRIMARY                         | 152     | xxxx_db.a.customerno        |       1 |                          |
|  1 | PRIMARY            | d                  | eq_ref | PRIMARY                         | PRIMARY                         | 152     | xxxx_db.a.num_iid           |       1 |                          |
|  1 | PRIMARY            | e                  | eq_ref | PRIMARY                         | PRIMARY                         | 152     | xxxx_db.a.dp_id             |       1 |                          |
|  1 | PRIMARY            | f                  | eq_ref | PRIMARY                         | PRIMARY                         | 2       | xxxx_db.a.ccms_order_status |       1 |                          |
|  2 | DEPENDENT SUBQUERY | b                  | REF    | ind_twf_node_order_analysis_tid | ind_twf_node_order_analysis_tid | 153     | xxxx_db.a.tid               |       2 | USING WHERE; USING INDEX |
+----+--------------------+--------------------+--------+---------------------------------+---------------------------------+---------+---------------------------+---------+--------------------------+

我們可以看到執行計劃中的rows已經從452變為了2(執行計劃的最後一行),由於mysql的表關聯只有nest loop join這種演算法,所以我們可以估算一下這裡的優化:

原始執行一:1055789*1*1*1*1*452 掃描的行數

新執行計劃二:1055789*1*1*1*1*2 掃描的行數

執行時間:

mysql> SELECT a.oid,
    ->        a.tid,
    ->        a.created,
    ->        a.pay_time,
    ->        a.consign_time,
    ->        a.endtime,
    ->        c.nick,
    ->        c.user_name,
    ->        e.shop_name,
    ->        d.num_iid,
    ->        d.title,
    ->        f.status_name,
    ->        a.payment,
    ->        a.num,
    ->        a.refund_status,
    ->        a.refund_fee,
    ->        d.outer_id
    ->   FROM xxxx_test a
    ->   LEFT JOIN xxxx_test1 c ON a.customerno = c.user_id
    ->   LEFT JOIN xxxx_test2 d ON a.num_iid = d.num_iid
    ->   LEFT JOIN xxxx_test3 e ON a.dp_id = e.shop_id
    ->   LEFT JOIN xxxx_test4 f ON a.ccms_order_status = f.status_id
    ->  WHERE EXISTS
    ->  (SELECT 1 FROM xxxx_test5 b WHERE a.tid = b.tid);
....................
..........省去結果
8 ROWS IN SET (10.62 sec)

我們看到執行時間已經由原來的6分20秒下降到了10秒,我們繼續優化; 我們可以看到該sql的結果集只有區區的8行,但是掃描的行數卻是非常之大的(1055789*1*1*1*1*2),在優化sql的非常關鍵的一點就是優化sql 的執行路程,t=s/v;如果我們能夠優化S,那麼速度肯定會一下子提上來; 那麼我們在看看sql中最後的一句:

-> WHERE EXISTS
-> (SELECT 1 FROM xxxx_test5 b WHERE a.tid = b.tid);

sql查詢中是要查詢出每筆訂單的詳細資訊而不得不關聯其他一些表,但是最後的一個exist限定了我們最後結果的範圍,在看看xxxx_test5 這張表有多大:

mysql> SELECT COUNT(*) FROM xxxx_test5;
+----------+
| COUNT(*) |
+----------+
| 403 |
+----------+
1 ROW IN SET (0.00 sec)
mysql> SELECT COUNT(*) FROM xxxx_test5 b ,xxxx_test a WHERE a.tid = b.tid
-> ;
+----------+
| COUNT(*) |
+----------+
| 8 |
+----------+
1 ROW IN SET (0.42 sec)

兩張表關聯後只有8行記錄,如果我們將訂單表xxxx_test和限定表先做關聯,在和其他的一些訂單資訊表做連線,將會極大的減小關聯的行數;在進一步改寫sql:

mysql> EXPLAIN SELECT a.oid,
    ->        a.tid,
    ->        a.created,
    ->        a.pay_time,
    ->        a.consign_time,
    ->        a.endtime,
    ->        c.nick,
    ->        c.user_name,
    ->        e.shop_name,
    ->        d.num_iid,
    ->        d.title,
    ->        f.status_name,
    ->        a.payment,
    ->        a.num,
    ->        a.refund_status,
    ->        a.refund_fee,
    ->        d.outer_id
    ->   FROM xxxx_test5 g,xxxx_test a
    ->   LEFT JOIN xxxx_test1 c ON a.customerno = c.user_id
    ->   LEFT JOIN xxxx_test2 d ON a.num_iid = d.num_iid
    ->   LEFT JOIN xxxx_test3 e ON a.dp_id = e.shop_id
    ->   LEFT JOIN xxxx_test4 f ON a.ccms_order_status = f.status_id
    ->   WHERE g.tid=a.tid;
+----+-------------+--------------------+--------+---------------------------------+---------------------------------+---------+---------------------------+------+-------------+
| id | select_type | TABLE              | TYPE   | possible_keys                   | KEY                             | key_len | REF                       | ROWS | Extra       |
+----+-------------+--------------------+--------+---------------------------------+---------------------------------+---------+---------------------------+------+-------------+
|  1 | SIMPLE      | g                  | INDEX  | ind_twf_node_order_analysis_tid | ind_twf_node_order_analysis_tid | 153     | NULL                      |  452 | USING INDEX |
|  1 | SIMPLE      | a                  | REF    | idx_xxxx_test_tid               | idx_xxxx_test_tid               | 153     | xxxx_db.g.tid               |    1 | USING WHERE |
|  1 | SIMPLE      | retail_tb_customer | eq_ref | PRIMARY                         | PRIMARY                         | 152     | xxxx_db.a.customerno        |    1 |             |
|  1 | SIMPLE      | d                  | eq_ref | PRIMARY                         | PRIMARY                         | 152     | xxxx_db.a.num_iid           |    1 |             |
|  1 | SIMPLE      | e                  | eq_ref | PRIMARY                         | PRIMARY                         | 152     | xxxx_db.a.dp_id             |    1 |             |
|  1 | SIMPLE      | f                  | eq_ref | PRIMARY                         | PRIMARY                         | 2       | xxxx_db.a.ccms_order_status |    1 |             |
+----+-------------+--------------------+--------+---------------------------------+---------------------------------+---------+---------------------------+------+-------------+
6 ROWS IN SET (0.00 sec)

分析執行計劃,我們發現限定表xxxx_test5做了驅動表,驅動表的變化才是導致問題的最根本原因,掃描的行數:452*1*1*1*1; 這個時候sql的執行速度就飛一般的感覺了:

mysql> SELECT a.oid,
    ->        a.tid,
    ->        a.created,
    ->        a.pay_time,
    ->        a.consign_time,
    ->        a.endtime,
    ->        c.nick,
    ->        c.user_name,
    ->        e.shop_name,
    ->        d.num_iid,
    ->        d.title,
    ->        f.status_name,
    ->        a.payment,
    ->        a.num,
    ->        a.refund_status,
    ->        a.refund_fee,
    ->        d.outer_id
    ->   FROM xxxx_test5 g,xxxx_test a
    ->   LEFT JOIN xxxx_test1 c ON a.customerno = c.user_id
    ->   LEFT JOIN xxxx_test2 d ON a.num_iid = d.num_iid
    ->   LEFT JOIN xxxx_test3 e ON a.dp_id = e.shop_id
    ->   LEFT JOIN xxxx_test4 f ON a.ccms_order_status = f.status_id
    ->   WHERE g.tid=a.tid;
..................
........省去結果
8 ROWS IN SET (0.13 sec)

總結:由於環境的遷移,導致sql執行計劃的改變,這就是RDS變慢的最終原因了。

案例二:使用者使用RDS(mssql),經常出現連線超時報錯

問題描述:使用mssql rds時不時報錯如下A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 – Error Locating Server/Instance Specified),詢問是否是連線超過限制導致的?

可能原因:可能使用者的應用程式設計不是很好導致資料庫鎖爭用較多;或由於沒有建立適當的索引導致全表掃描導致,造成了資料庫等待;

問題排查:通過檢視資料庫的監控指標,發現在某個時間段有大量的全表掃描,同時資料庫的鎖爭超時,會話數明顯增加.

可以看到在15:00的時候有大量的全表掃描,出現了較多的鎖超時的情況,同時這個時候也有大量的會話在資料庫中,而這個時間恰好也是使用者報出錯誤的時間,在進一步排查,使用者的SQL,通過檢視mssql內部的一些檢視,來找到對應消耗資源top 5的sql,發現使用者使用頻繁的查詢一個檢視,在檢視中有多表連線,但在這些表連線中的欄位上沒有新增索引,導致了全表掃描,使用者檢視如下:

CREATE VIEW [dbo].[Vi_xxx]
AS
SELECT ..........
..........
FROM dbo.xxxx_test6 INNER JOIN
dbo.xxxx_test1 ON dbo.xxxx_test5.docID = dbo.xxxx_test1.docID INNER JOIN
dbo.xxxx_test2 ON dbo.xxxx_test5.typeID = dbo.xxxx_test2.typeID INNER JOIN
dbo.xxxx_test3 ON dbo.xxxx_test5.docID = dbo.xxxx_test3.docID INNER JOIN
dbo.xxxx_test4 ON dbo.xxxx_test5.categoryID = dbo.xxxx_test4.categoryID
WHERE (dbo.xxxx_test5.isDelete = 0)

通過檢視執行計劃,發現表上面很多的關聯欄位沒有建立索引,導致全表掃描 執行計劃如下(有很多的table scan):總結:使用者頻繁的查詢一個檢視,而該檢視中表的關聯欄位上沒有索引,導致了大量的全表掃描,累積了大量的會話數,造成資料庫效能的下降,應用與資料庫之間出現連線錯誤。

案例三:隱式轉換導致全表掃描

問題描述:使用者網站開啟緩慢,質疑RDS效能不好.

可能原因:使用者的資料存放在RDS中,網站訪問資料庫的時間較長,絕大部分都是使用者自己的應用程式設計不好,SQL寫的不優化,索引建立的不好而導致;

問題排查:通過檢視資料庫的慢日誌,發現大量的慢sql,執行時間超過了2S.

UPDATE USER SET xx=xx+N.N WHERE account=130000870343  LIMIT 10
SELECT * FROM  USER WHERE account=13056870  LIMIT 10

懷疑在user表上是否建立索引:
CREATE TABLE `user` (
`id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`account` char(11) NOT NULL COMMENT ‘???’,
…………………….
…………………….
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`account`),
…………………….
) ENGINE=InnoDB CHARSET=utf8 ;
檢視執行計劃,居然查詢使用了全表掃描: [email protected] 16:55:06>explain select * from user where account=13056870343;
+—-+————-+——–+——+—————+——+———+——+——+————-+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+————-+——–+——+—————+——+———+——+——+————-+
| 1 | SIMPLE | t_user | ALL | username | NULL | NULL | NULL | 799 | Using where |
+—-+————-+——–+——+—————+——+———+——+——+————-+
1 row in set (0.00 sec)

為什麼這裡會是全表掃描喃?account上不是已經建立索引來嗎?仔細一看,account定義為了字串,而傳入的條件為數字,我們知道數字的精度是比字串高的,所以這裡做了隱士轉換:to_number(account)=13056870343(to_number為將字串轉換為數字),這樣即使account上有索引,也沒法使用了,因此我們將傳入的數字改為字串:

[email protected] 16:55:13>explain SELECT * FROM USER WHERE account='13056870343';
+----+-------------+--------+-------+---------------+----------+---------+-------+------+-------+
| id | select_type | TABLE  | TYPE  | possible_keys | KEY      | key_len | REF   | ROWS | Extra |
+----+-------------+--------+-------+---------------+----------+---------+-------+------+-------+
|  1 | SIMPLE      | t_user |