MySQL part3

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度學習實戰(不定時更新)



window的mysql 安裝過程

window下的MySQL客戶端 或 代碼程序 連接window的MySQL服務器,
修改window下的mysql配置:
1.mysql -uroot -padmin
2.use mysql
3.update user set host = '%' where user = 'root';
4.flush privileges


一條sql語句完成MySQL去重留一

DELETE 
    mygame
FROM 
    mygame, 
(
SELECT
   min(id) id,
   game,
   userid,
   create_datatime
FROM
   mygame
GROUP BY
   game,
   userid,
HAVING
   count(*) > 1
) t2
WHERE
    mygame.game = t2.game
and mygame.userid = t2.userid
and mygame.id > t2.id

第一步: 查詢出重複記錄形成一個集合(臨時表t2),集合裏是每種重複記錄的最小ID
SELECT
   min(id) id,
   game,
   userid,
   create_datatime
FROM
   mygame
GROUP BY
   game,
   userid,
HAVING
   count(*) > 1
) t2


第二步:關聯 判斷重複基準的字段,根據條件,刪除原表中id大於t2中id的記錄
DELETE 
    mygame
FROM 
    mygame, t2
WHERE
    mygame.game = t2.game
and mygame.userid = t2.userid
and mygame.id > t2.id
在使用mysql時,有時需要查詢出某個字段不重複的記錄,雖然mysql提供 有distinct這個關鍵字來過濾掉多餘的重複記錄只保留一條,
但往往只用它來返回不重複記錄的條數,而不是用它來返回不重記錄的所有值。

DELETE consum_record
FROM
 consum_record, 
 (
  SELECT
   min(id) id,
   user_id,
   monetary,
   consume_time
  FROM
   consum_record
  GROUP BY
   user_id,
   monetary,
   consume_time
  HAVING
   count(*) > 1
 ) t2
WHERE
 consum_record.user_id = t2.user_id 
 and consum_record.monetary = t2.monetary
 and consum_record.consume_time = t2.consume_time
AND consum_record.id > t2.id;

上面這條sql語句,仔細看一下,揣摩出思路也不難,大概也分爲3步來理解:
(SELECT min(id) id, user_id, monetary, consume_time FROM consum_record GROUP BY user_id, monetary, consume_time HAVING count(*) > 1 ) t2
 查詢出重複記錄形成一個集合(臨時表t2),集合裏是每種重複記錄的最小ID

consum_record.user_id = t2.user_id and consum_record.monetary = t2.monetary and consum_record.consume_time = t2.consume_time 
關聯 判斷重複基準的字段,根據條件,刪除原表中id大於t2中id的記錄

四:SQL(結構化查詢語言)

SQL是結構化查詢語言,是一種用來操作RDBMS的數據庫語言,當前關係型數據庫都支持使用SQL語言進行操作,也就是說可以通過 SQL 操作 oracle,sql server,mysql,sqlite 等等所有的關係型的數據庫

 

五:mysql服務器端

  • 服務器用於接收客戶端的請求、執行sql語句、管理數據庫
  • 服務器端一般以服務方式管理,名稱爲mysql

1.安裝服務器端:在終端中輸入如下命令,回車後,然後按照提示輸入

sudo apt-get install mysql-server

2.啓動服務

sudo service mysql start

3.查看進程中是否存在mysql服務

ps ajx|grep mysql

4.停止服務

sudo service mysql stop

5.重啓服務

sudo service mysql restart

 

六:命令行客戶端

1.安裝客戶端

sudo apt-get install mysql-client

2.登陸客戶端

mysql -u 用戶名 -p 密碼

3.退出客戶端

ctrl+d  quit  exit

 

七:數據完整性

  • 一個數據庫就是一個完整的業務單元,可以包含多張表,數據被存儲在表中
  • 在表中爲了更加準確的存儲數據,保證數據的正確有效,可以在創建表的時候,爲表添加一些強制性的驗證,包括數據字段的類型、約束

1.數據類型

  • 常用數據類型如下:
    • 整數:int,bit
    • 小數:decimal
    • 字符串:varchar,char
    • 日期時間: date, time, datetime
    • 枚舉類型(enum)
  • 特別說明的類型如下:
    • decimal表示浮點數,如decimal(5,2)表示共存5位數,小數佔2位
    • char表示固定長度的字符串,如char(3),如果填充'ab'時會補一個空格爲'ab '
    • varchar表示可變長度的字符串,如varchar(3),填充'ab'時就會存儲'ab'
    • 字符串text表示存儲大文本,當字符大於4000時推薦使用
    • 對於圖片、音頻、視頻等文件,不存儲在數據庫中,而是上傳到某個服務器上,然後在表中存儲這個文件的保存路徑

2.約束

  • 主鍵primary key:物理上存儲的順序
  • 非空not null:此字段不允許填寫空值
  • 惟一unique:此字段的值不允許重複
  • 默認default:當不填寫此值時會使用默認值,如果填寫時以填寫爲準
  • 外鍵foreign key:對關係字段進行約束,當爲關係字段填寫值時,會到關聯的表中查詢此值是否存在,如果存在則填寫成功,如果不存在則填寫失敗並拋出異常
  • 說明:雖然外鍵約束可以保證數據的有效性,但是在進行數據的crud(增加、修改、刪除、查詢)時,都會降低數據庫的性能,所以不推薦使用,那麼數據的有效性怎麼保證呢?答:可以在邏輯層進行控制

八:數據庫操作

1.查看所有數據庫

show databases;

2.使用數據庫

use 數據庫名;

3.查看當前使用的數據庫

select database();

4.創建數據庫

create database 數據庫名 charset=utf8;

5.刪除數據庫

drop database 數據庫名;

九:數據表操作

1.查看當前數據庫中所有表

show tables;

2.查看錶結構

desc 表名;

3.創建表

實例:

create table students(

id int unsigned primary key auto_increment not null,

name varchar(20) default '',

age tinyint unsigned default 0,

height decimal(5,2),

gender enum('男','女','人妖','保密'),

cls_id int unsigned default 0

)

4.刪除表

drop table 表名;

5.查看錶的創建語句

show create table 表名;

6.修改表-添加字段

alter table 表名 add 列名 類型;

7.修改表-修改字段:重命名版

alter table 表名 change 原名 新名 類型及約束;

8.修改表-修改字段:不重命名版

alter table 表名 modify 列名 類型及約束;

9.修改表-刪除字段

alter table 表名 drop 列名;

10.數據備份:

mysqldump uroot p 數據庫名 > python.sql;

 

十:數據的增刪改查(curd)

1.增加

insert into 表名 values(...)

實例:insert into students values(0,’郭靖‘,1,'蒙古','2016-1-2');

2.刪除

delete from 表名 where 條件

update students set isdelete=1 where id=1; # 邏輯刪除

3.修改

update 表名 set 列1=值1,列2=值2... where 條件

4.查找

select * from 表名;

select 列1,列2,... from 表名;

 

十一:數據表設計

1.三範式

◆ 第一範式(1NF):強調的是列的原子性,即列不能夠再分成其他幾列。

◆第二範式(2NF):首先是 1NF,另外包含兩部分內容,一是表必須有一個主鍵;二是沒有包含在主鍵中的列必須完全依賴於主鍵,而不能只依賴於主鍵的一部分.

◆ 第三範式(3NF):首先是 2NF,另外非主鍵列必須直接依賴於主鍵,不能存在傳遞依賴。即不能存在:非主鍵列 A 依賴於非主鍵列 B,非主鍵列 B 依賴於主鍵的情況。

 

十二:Mysql查詢

1.查詢所有字段

select * from 表名;

2.查詢指定字段

select 列1,列2,... from 表名;

3.使用 as 給字段起別名

select id as 標號, name as 名字, gender as 性別 from students;

4.可以通過 as 給表起別名

select s.id,s.name,s.gender from students as s;

5.在select後面列前使用distinct可以消除重複的行

select distinct 列1,... from 表名;

十三:條件

使用where子句對錶中的數據篩選,結果爲true的行會出現在結果集中

語法如下:select * from 表名 where 條件;

1.比較運算符

  • 等於: =
  • 大於: >
  • 大於等於: >=
  • 小於: <
  • 小於等於: <=
  • 不等於: != 或 <>

2.邏輯運算符

  • and
  • or
  • not

3.模糊查詢

  • like(關鍵字)
  • %表示任意多個任意字符
  • _表示一個任意字符

實例:查詢姓黃的學生

select * from students where name like '黃%';

4.範圍查詢

in表示在一個非連續的範圍內

實例:查詢編號是1或3或8的學生

select * from students where id in(1,3,8);

between ... and ...表示在一個連續的範圍內

實例:select * from students where id between 3 and 8;

5.空判斷

判空is null

實例:查詢沒有填寫身高的學生/填寫身高的學生

select * from students where height is null;

select * from students where height is not null;

十四:排序

語法:

select * from 表名 order by 列1 asc | desc;

說明

  • 將行數據按照列1進行排序,如果某些行列1的值相同時,則按照列2排序,以此類推
  • 默認按照列值從小到大排列(asc)
  • asc從小到大排列,即升序
  • desc從大到小排序,即降序

實例:查詢未刪除男生信息,按學號降序

select * from students where gender=1 and is_delete=0 order by id desc;

十五:聚合函數

爲了快速得到統計數據,經常會用到如下5個聚合函數

1.總數

  • count(*)表示計算總行數,括號中寫星與列名,結果是相同的
  • 聚合函數不能在 where 中使用

實例:查詢學生總數

select count(*) from students;

2.最大值

max(列)表示求此列的最大值

實例:查詢女生的編號最大值

select max(id) from students where gender=2;

3.最小值

min(列)表示求此列的最小值

實例;查詢未刪除的學生最小編號

select min(id) from students where is_delete=0;

4.求和

sum(列)表示求此列的和

實例:查詢男生的平均年齡

select sum(age)/count(*) from students where gender=1;

5.平均值

avg(列)表示求此列的平均值

實例;查詢未刪除女生的編號平均值

elect avg(id) from students where is_delete=0 and gender=2;

十六:分組(group by)

  1. group by的含義:將查詢結果按照1個或多個字段進行分組,字段值相同的爲一組
  2. group by可用於單個字段分組,也可用於多個字段分組

1.group by + group_concat()

  1. group_concat(字段名)可以作爲一個輸出字段來使用,
  2. 表示分組之後,根據分組結果,使用group_concat()來放置每一組的某字段的值的集合

實例:

select gender,group_concat(name) from students group by gender;

+--------+-----------------------------------------------------------+

| gender | group_concat(name) |

+--------+-----------------------------------------------------------+

| 男        | 彭于晏,劉德華,周杰倫,程坤,郭靖                                       |

| 女        | 小明,小月月,黃蓉,王祖賢,劉亦菲,靜香,周杰                         |

| 中性     | 金星                                                                              |

| 保密     | 鳳姐                                                                              |

+--------+-----------------------------------------------------------+

 

2.group by + 集合函數

通過group_concat()的啓發,我們既然可以統計出每個分組的某字段的值的集合,那麼我們也可以通過集合函數來對這個值的集合做一些操作

實例:分別統計性別爲男/女的人年齡平均值

select gender,avg(age) from students group by gender;

+--------+----------+

| gender | avg(age) |

+--------+----------+

| 男        | 32.6000  |

| 女        | 23.2857  |

| 中性     | 33.0000  |

| 保密     | 28.0000  |

+--------+----------+

3.group by + having

  1. having 條件表達式:用來分組查詢後指定一些條件來輸出查詢結果
  2. having作用和where一樣,但having只能用於group by

實例:

select gender,count(*) from students group by gender having count(*)>2;

4.group by + with rollup

with rollup的作用是:在最後新增一行,來記錄當前列裏所有記錄的總和

實例:

select gender,count(*) from students group by gender with rollup;

+--------+----------+

| gender | count(*) |

+--------+----------+

| 男        | 5            |

| 女        | 7            |

| 中性     | 1            |

| 保密     | 1            |

| NULL   | 14          |

+--------+----------+

十七:連接查詢(將連個表按照某種條件合併在一起)

1.內連接查詢:查詢的結果爲兩個表匹配到的數據(inner join ...on...)

實例:使用內連接查詢班級表與學生表

select * from students inner join classes on students.cls_id = classes.id;

2.左連接查詢

3.右連接查詢

十八:標量子查詢

實例:查詢大於平均年齡的學生

select * from students where age > (select avg(age) from students);

十九:pycharm與mysql的交互

(1)導入pymysql: import pymysql 

(2)連接數據庫: conn=pymysql.connect(host='localhost',user='root',passwd='root',db='ere',charset='utf8')    務必注意各等號前面的內容!charset參數可避免中文亂碼

(3)獲取操作遊標:cur=conn.cursor()

(4)執行sql語句,插入記錄:sta=cur.execute("insert 語句")  執行成功後sta值爲1。更新、刪除語句與此類似。

(5)執行sql語句,查詢記錄:cur.execute("select語句") 執行成功後cur變量中保存了查詢結果記錄集,然後再用循環打印結果:

for each in cur:

      print(each[1].decode('utf-8'))     # each[1] 表示當前遊標所在行的的第2列值,如果是中文則需要處理編碼

(6)關閉數據庫連接: cur.close(); conn.close();



MYSQL-性能優化篇

  1. 爲什麼要進行數據庫優化?
  1. 避免網站頁面出現訪問錯誤

由於數據庫連接timeout產生頁面5xx錯誤

由於慢查詢造成頁面無法加載

由於阻塞造成數據無法提交

  1. 增加數據庫的穩定性

很多數據庫問題都是由於低效的查詢引起的

  1. 優化用戶體驗

流暢頁面的訪問速度

良好的網站功能體驗

2、mysql數據庫優化

可以從哪幾個方面進行數據庫的優化?如下圖所示:

  1. SQL及索引優化

根據需求寫出良好的SQL,並創建有效的索引,實現某一種需求可以多種寫法,這時候我們就要選擇一種效率最高的寫法。這個時候就要了解sql優化

  1. 數據庫表結構優化

根據數據庫的範式,設計表結構,表結構設計的好直接關係到寫SQL語句。

  1. 系統配置優化

大多數運行在Linux機器上,如tcp連接數的限制、打開文件數的限制、安全性的限制,因此我們要對這些配置進行相應的優化。

  1. 硬件配置優化

選擇適合數據庫服務的cpu,更快的IO,更高的內存;cpu並不是越多越好,某些數據庫版本有最大的限制,IO操作並不是減少阻塞。

注:通過上圖可以看出,該金字塔中,優化的成本從下而上逐漸增高,而優化的效果會逐漸降低。

 

 

3、SQL及索引優化

1、mysql安裝與卸載(linux在線安裝與卸載)

2、數據庫版本選擇

1、查看數據庫的版本

2、準備數據

網址:https://dev.mysql.com/doc/sakila/en/sakila-installation.html

注:該表結構關係是用工具生成的。

 

 

  1. 如何發現有問題的SQL

MySQL慢查日誌的開啓方式和存儲格式

1、檢查慢查日誌是否開啓:

show variables like 'slow_query_log'

show variables like 'slow_query_log'  

//查看是否開啓慢查詢日誌

 

set global slow_query_log_file=' /usr/share/mysql/sql_log/mysql-slow.log'

//慢查詢日誌的位置

 

set global log_queries_not_using_indexes=on;

//開啓慢查詢日誌

 

set global long_query_time=1;  

//大於1秒鐘的數據記錄到慢日誌中,如果設置爲默認0,則會有大量的信息存儲在磁盤中,磁盤很容易滿掉

2、查看所有日誌的變量信息

show variables like '%log%'

 

mysql> show variables like '%log%';

+-----------------------------------------+------------------------------------+

| Variable_name                           | Value                              |

+-----------------------------------------+------------------------------------+

| back_log                                | 80                                 |

| binlog_cache_size                       | 32768                              |

| binlog_checksum                         | CRC32                              |

| binlog_direct_non_transactional_updates | OFF                                |

| binlog_error_action                     | IGNORE_ERROR                       |

| binlog_format                           | STATEMENT                          |

| binlog_gtid_simple_recovery             | OFF                                |

| binlog_max_flush_queue_time             | 0                                  |

| binlog_order_commits                    | ON                                 |

| binlog_row_image                        | FULL                               |

| binlog_rows_query_log_events            | OFF                                |

| binlog_stmt_cache_size                  | 32768                              |

| binlogging_impossible_mode              | IGNORE_ERROR                       |

| expire_logs_days                        | 0                                  |

| general_log                             | OFF                                |

| general_log_file                        | /var/lib/mysql/mysql-host.log      |

| innodb_api_enable_binlog                | OFF                                |

| innodb_flush_log_at_timeout             | 1                                  |

| innodb_flush_log_at_trx_commit          | 1                                  |

| innodb_locks_unsafe_for_binlog          | OFF                                |

| innodb_log_buffer_size                  | 8388608                            |

| innodb_log_compressed_pages             | ON                                 |

| innodb_log_file_size                    | 50331648                           |

| innodb_log_files_in_group               | 2                                  |

| innodb_log_group_home_dir               | ./                                 |

| innodb_mirrored_log_groups              | 1                                  |

| innodb_online_alter_log_max_size        | 134217728                          |

| innodb_undo_logs                        | 128                                |

| log_bin                                 | OFF                                |

| log_bin_basename                        |                                    |

| log_bin_index                           |                                    |

| log_bin_trust_function_creators         | OFF                                |

| log_bin_use_v1_row_events               | OFF                                |

| log_error                               | /var/log/mysqld.log                |

| log_output                              | FILE                               |

| log_queries_not_using_indexes           | ON                                 |

| log_slave_updates                       | OFF                                |

| log_slow_admin_statements               | OFF                                |

| log_slow_slave_statements               | OFF                                |

| log_throttle_queries_not_using_indexes  | 0                                  |

| log_warnings                            | 1                                  |

| max_binlog_cache_size                   | 18446744073709547520               |

| max_binlog_size                         | 1073741824                         |

| max_binlog_stmt_cache_size              | 18446744073709547520               |

| max_relay_log_size                      | 0                                  |

| relay_log                               |                                    |

| relay_log_basename                      |                                    |

| relay_log_index                         |                                    |

| relay_log_info_file                     | relay-log.info                     |

| relay_log_info_repository               | FILE                               |

| relay_log_purge                         | ON                                 |

| relay_log_recovery                      | OFF                                |

| relay_log_space_limit                   | 0                                  |

| simplified_binlog_gtid_recovery         | OFF                                |

| slow_query_log                          | OFF                                |

| slow_query_log_file                     | /var/lib/mysql/mysql-host-slow.log |

| sql_log_bin                             | ON                                 |

| sql_log_off                             | OFF                                |

| sync_binlog                             | 0                                  |

| sync_relay_log                          | 10000                              |

| sync_relay_log_info                     | 10000                              |

+-----------------------------------------+------------------------------------+

61 rows in set (0.01 sec)

開啓慢查日誌:

show variables like 'slow_query_log'  

//查看是否開啓慢查詢日誌

 

set global slow_query_log_file=' /var/lib/mysql/mysql-host-slow.log '

//慢查詢日誌的位置

 

set global log_queries_not_using_indexes=on;

//開啓慢查詢日誌

 

set global long_query_time=1;  

//大於1秒鐘的數據記錄到慢日誌中,如果設置爲默認0,則會有大量的信息存儲在磁盤中,磁盤很容易滿掉

 

驗證慢查詢日誌是否開啓:

 

在mysql操作中,

 

Show databases;

Use sakila;

select * from store;

select * from staff;

 

 

監聽日誌文件,看是否寫入

tail -50f /var/lib/mysql/mysql-host-slow.log

3MySQL慢查日誌的存儲格式

如下圖所示:

說明:

1、# Time: 180526  1:06:54 -------à查詢的執行時間

2、# User@Host: root[root] @ localhost []  Id:     4 -------à執行sql的主機信息

3、# Query_time: 0.000401  Lock_time: 0.000105 Rows_sent: 2  Rows_examined: 2-------àSQL的執行信息:

Query_time:SQL的查詢時間

Lock_time:鎖定時間

Rows_sent:所發送的行數

Rows_examined:鎖掃描的行數

4、SET timestamp=1527268014; -------àSQL執行時間

5、select * from staff; -------àSQL的執行內容

 

 

4、MySQL慢查日誌分析工具mysqldumpslow

1、介紹

如何進行查看慢查詢日誌,如果開啓了慢查詢日誌,就會生成很多的數據,然後我們就可以通過對日誌的分析,生成分析報表,然後通過報表進行優化。

 

2、用法

接下來我們查看一下這個工具的用法:

 

注意:在mysql數據庫所在的服務器上,而不是在mysql>命令行中

 

該工具如何使用:mysqldumpslow -h

查看verbose信息

Mysqldumpslow -v

查看慢查詢日誌的前10個,mysqldumpslow 分析的結果如下

mysqldumpslow -t 10 /var/lib/mysql/mysql-host-slow.log

如上圖兩條就是分析的結果,每條結果都顯示是執行時間,鎖定時間,發送的行數,掃描的行數

 

這個工具是最常用的工具,通過安裝mysql進行附帶安裝,但是該工具統計的結果比較少,對我們的優化鎖表現的數據還是比較少。

 

5MySQL慢查日誌分析工具(pt-query-digest)

1、介紹及作用

作爲一名優秀的mysql dba也需要有掌握幾個好用的mysql管理工具,所以我也一直在整理和查找一些能夠便於管理mysql的利器。以後的一段時間內,將會花一大部分的精力去搜索這些工具。

性 能的管理一直都是擺在第一位的,dba的很多工作管理層都看不到也沒有辦法衡量價值,但是如果一個系統慢的跟蝸牛一樣,dba通過監控調優把系統從崩潰邊 緣重新拉回到高鐵時代。這種價值和觸動應該是巨大的。(很多企業的領導認爲系統跑不動了就需要換更快的CPU、更大的內存、更快的存儲,而且這還不是少 數,所以DBA的價值也一直體現不出來,薪水自然也就不會很高)

mysql 的日誌是跟蹤mysql性能瓶頸的最快和最直接的方式了,系統性能出現瓶頸的時候,首先要打開慢查詢日誌,進行跟蹤;這段時間關於慢查詢日誌的管理和查看 已經整理過兩篇文章了,不經意間又發現了一個查看慢查詢日誌的工具:mk-query-digest,這個工具網上號稱mysql dba必須掌握的十大工具之首。

 

 

2、安裝pt-query-digest工具

1.1、快速安裝(注:必須先要安裝wget)

wget https://www.percona.com/downloads/percona-toolkit/2.2.16/RPM/percona-toolkit-2.2.16-1.noarch.rpm && yum localinstall -y  percona-toolkit-2.2.16-1.noarch.rpm

 

1.2、檢查是否安裝完成

 命令行中輸入:pt-summary

顯示如下圖所示:說明安裝成功!輸入【[root@node03 mysql]# pt-query-digest --help】

1.3、工具使用簡介:

1查看服務器信息

命令:pt-summary

2、查看磁盤開銷使用信息

命令:pt-diskstats

 

3、查看mysql數據庫信息

命令:pt-mysql-summary --user=root --password=admin

4、分析慢查詢日誌

命令:pt-query-digest /data/mysql/data/db-3-12-slow.log

 

5、查找mysql的從庫和同步狀態

命令:pt-slave-find --host=localhost --user=root --password=123456

 

6、查看mysql的死鎖信息

pt-deadlock-logger --user=root --password=123456 localhost

 

7、從慢查詢日誌中分析索引使用情況

pt-index-usage slow_20131009.log

 

8、查找數據庫表中重複的索引

pt-duplicate-key-checker --host=localhost --user=root --password=admin

 

9、查看mysql表和文件的當前活動IO開銷

pt-ioprofile

 

10、查看不同mysql配置文件的差異

pt-config-diff /etc/my.cnf /etc/my_master.cnf

 

11、pt-find查找mysql表和執行命令,示例如下

查找數據庫裏大於2G的表:

pt-find --user=root --password=123456 --tablesize +2G

查找10天前創建,MyISAM引擎的表:

pt-find --user=root --password=123456 --ctime +10 --engine MyISAM

查看錶和索引大小並排序

pt-find --user=root --password=123456 --printf "%T\t%D.%N\n" | sort -rn

 

 

12、pt-kill 殺掉符合標準的mysql進程

顯示查詢時間大於60秒的查詢

pt-kill --user=root --password=123456 --busy-time 60 --print

kill掉大於60秒的查詢

 pt-kill --user=root --password=123456 --busy-time 60 --kill

 

13、查看mysql授權

1pt-show-grants --user=root --password=123456

2pt-show-grants --user=root --password=123456 --separate –revoke

 

14、驗證數據庫複製的完整性

pt-table-checksum --user=root --password=123456

 

15、附錄:

6、如何通過慢查日誌發現有問題的SQL

 

1、查詢次數多且每次查詢佔用時間長的sql

通常爲pt-query-digest分析的前幾個查詢;該工具可以很清楚的看出每個SQL執行的次數及百分比等信息,執行的次數多,佔比比較大的SQL

2、IO大的sql

注意pt-query-digest分析中的Rows examine項。掃描的行數越多,IO越大。

3、未命中的索引的SQL

注意pt-query-digest分析中的Rows examine 和Rows Send的對比。說明該SQL的索引命中率不高,對於這種SQL,我們要重點進行關注。

 

7、通過explain查詢分析SQL的執行計劃

1、使用explain查詢SQL的執行計劃

SQL的執行計劃側面反映出了SQL的執行效率,具體執行方式如下所示:

在執行的SQL前面加上explain關鍵詞即可;

2、每個字段的說明:

1)、id列數字越大越先執行,如果說數字一樣大,那麼就從上往下依次執行,id列爲null的就表是這是一個結果集,不需要使用它來進行查詢。

 

2)、select_type列常見的有:

 

A:simple:表示不需要union操作或者不包含子查詢的簡單select查詢。有連接查詢時,外層的查詢爲simple,且只有一個

 

B:primary:一個需要union操作或者含有子查詢的select,位於最外層的單位查詢的select_type即爲primary。且只有一個

 

C:union:union連接的兩個select查詢,第一個查詢是dervied派生表,除了第一個表外,第二個以後的表select_type都是union

 

D:dependent union:與union一樣,出現在union 或union all語句中,但是這個查詢要受到外部查詢的影響

 

E:union result:包含union的結果集,在union和union all語句中,因爲它不需要參與查詢,所以id字段爲null

 

F:subquery:除了from字句中包含的子查詢外,其他地方出現的子查詢都可能是subquery

 

G:dependent subquery:與dependent union類似,表示這個subquery的查詢要受到外部表查詢的影響

 

H:derived:from字句中出現的子查詢,也叫做派生表,其他數據庫中可能叫做內聯視圖或嵌套select

 

3)、table

顯示的查詢表名,如果查詢使用了別名,那麼這裏顯示的是別名,如果不涉及對數據表的操作,那麼這顯示爲null,如果顯示爲尖括號括起來的<derived N>就表示這個是臨時表,後邊的N就是執行計劃中的id,表示結果來自於這個查詢產生。如果是尖括號括起來的<union M,N>,與<derived N>類似,也是一個臨時表,表示這個結果來自於union查詢的id爲M,N的結果集。

 

4)、type

依次從好到差:system,const,eq_ref,ref,fulltext,ref_or_null,unique_subquery,index_subquery,range,index_merge,index,ALL,除了all之外,其他的type都可以使用到索引,除了index_merge之外,其他的type只可以用到一個索引

 

A:system:表中只有一行數據或者是空表,且只能用於myisam和memory表。如果是Innodb引擎表,type列在這個情況通常都是all或者index

 

B:const:使用唯一索引或者主鍵,返回記錄一定是1行記錄的等值where條件時,通常type是const。其他數據庫也叫做唯一索引掃描

 

C:eq_ref:出現在要連接過個表的查詢計劃中,驅動表只返回一行數據,且這行數據是第二個表的主鍵或者唯一索引,且必須爲not null,唯一索引和主鍵是多列時,只有所有的列都用作比較時纔會出現eq_ref

 

D:ref:不像eq_ref那樣要求連接順序,也沒有主鍵和唯一索引的要求,只要使用相等條件檢索時就可能出現,常見與輔助索引的等值查找。或者多列主鍵、唯一索引中,使用第一個列之外的列作爲等值查找也會出現,總之,返回數據不唯一的等值查找就可能出現。

 

E:fulltext:全文索引檢索,要注意,全文索引的優先級很高,若全文索引和普通索引同時存在時,mysql不管代價,優先選擇使用全文索引

 

F:ref_or_null:與ref方法類似,只是增加了null值的比較。實際用的不多。

 

G:unique_subquery:用於where中的in形式子查詢,子查詢返回不重複值唯一值

 

H:index_subquery:用於in形式子查詢使用到了輔助索引或者in常數列表,子查詢可能返回重複值,可以使用索引將子查詢去重。

 

I:range:索引範圍掃描,常見於使用>,<,is null,between ,in ,like等運算符的查詢中。

 

J:index_merge:表示查詢使用了兩個以上的索引,最後取交集或者並集,常見and ,or的條件使用了不同的索引,官方排序這個在ref_or_null之後,但是實際上由於要讀取所個索引,性能可能大部分時間都不如range

 

K:index:索引全表掃描,把索引從頭到尾掃一遍,常見於使用索引列就可以處理不需要讀取數據文件的查詢、可以使用索引排序或者分組的查詢。

 

L:all:這個就是全表掃描數據文件,然後再在server層進行過濾返回符合要求的記錄。

 

5)、possible_keys

查詢可能使用到的索引都會在這裏列出來

 

6)、key

查詢真正使用到的索引,select_type爲index_merge時,這裏可能出現兩個以上的索引,其他的select_type這裏只會出現一個。

 

7)、key_len

用於處理查詢的索引長度,如果是單列索引,那就整個索引長度算進去,如果是多列索引,那麼查詢不一定都能使用到所有的列,具體使用到了多少個列的索引,這裏就會計算進去,沒有使用到的列,這裏不會計算進去。留意下這個列的值,算一下你的多列索引總長度就知道有沒有使用到所有的列了。要注意,mysql的ICP特性使用到的索引不會計入其中。另外,key_len只計算where條件用到的索引長度,而排序和分組就算用到了索引,也不會計算到key_len中。

 

8)、ref

如果是使用的常數等值查詢,這裏會顯示const,如果是連接查詢,被驅動表的執行計劃這裏會顯示驅動表的關聯字段,如果是條件使用了表達式或者函數,或者條件列發生了內部隱式轉換,這裏可能顯示爲func

 

9)、rows

這裏是執行計劃中估算的掃描行數,不是精確值

 

10)、extra

這個列可以顯示的信息非常多,有幾十種,常用的有

 

A:distinct:在select部分使用了distinc關鍵字

 

B:no tables used:不帶from字句的查詢或者From dual查詢

 

C:使用not in()形式子查詢或not exists運算符的連接查詢,這種叫做反連接。即,一般連接查詢是先查詢內表,再查詢外表,反連接就是先查詢外表,再查詢內表。

 

D:using filesort:排序時無法使用到索引時,就會出現這個。常見於order by和group by語句中

 

E:using index:查詢時不需要回表查詢,直接通過索引就可以獲取查詢的數據。

 

F:using join buffer(block nested loop),using join buffer(batched key accss):5.6.x之後的版本優化關聯查詢的BNL,BKA特性。主要是減少內表的循環數量以及比較順序地掃描查詢。

 

G:using sort_union,using_union,using intersect,using sort_intersection:

using intersect:表示使用and的各個索引的條件時,該信息表示是從處理結果獲取交集

using union:表示使用or連接各個使用索引的條件時,該信息表示從處理結果獲取並集

using sort_union和using sort_intersection:與前面兩個對應的類似,只是他們是出現在用and和or查詢信息量大時,先查詢主鍵,然後進行排序合併後,才能讀取記錄並返回。

 

H:using temporary:表示使用了臨時表存儲中間結果。臨時表可以是內存臨時表和磁盤臨時表,執行計劃中看不出來,需要查看status變量,used_tmp_table,used_tmp_disk_table才能看出來。

 

I:using where:表示存儲引擎返回的記錄並不是所有的都滿足查詢條件,需要在server層進行過濾。查詢條件中分爲限制條件和檢查條件,5.6之前,存儲引擎只能根據限制條件掃描數據並返回,然後server層根據檢查條件進行過濾再返回真正符合查詢的數據。5.6.x之後支持ICP特性,可以把檢查條件也下推到存儲引擎層,不符合檢查條件和限制條件的數據,直接不讀取,這樣就大大減少了存儲引擎掃描的記錄數量。extra列顯示using index condition

 

J:firstmatch(tb_name):5.6.x開始引入的優化子查詢的新特性之一,常見於where字句含有in()類型的子查詢。如果內表的數據量比較大,就可能出現這個

 

K:loosescan(m..n):5.6.x之後引入的優化子查詢的新特性之一,在in()類型的子查詢中,子查詢返回的可能有重複記錄時,就可能出現這個

 

除了這些之外,還有很多查詢數據字典庫,執行計劃過程中就發現不可能存在結果的一些提示信息

 

11)、filtered

使用explain extended時會出現這個列,5.7之後的版本默認就有這個字段,不需要使用explain extended了。這個字段表示存儲引擎返回的數據在server層過濾後,剩下多少滿足查詢的記錄數量的比例,注意是百分比,不是具體記錄數。

 

附圖:

3、具體慢查詢的優化案例

1、函數Max()的優化

用途:查詢最後支付時間-優化max()函數

語句:

 

select max(payment_date) from payment;

執行計劃:

explain select max(payment_date) from payment;

 

可以看到顯示的執行計劃,並不是很高效,可以拖慢服務器的效率,如何優化了?

創建索引

create index inx_paydate on payment(payment_date);

索引是順序操作的,不需要掃描表,執行效率就會比較恆定,

 

2、函數Count()的優化

需求:在一條SQL中同事查處2006年和2007年電影的數量

 

錯誤的方式:

語句:

select count(release_year='2006' or release_year='2007') from film;

2006和2007年分別是多少,判斷不出來

 

  select count(*) from film where release_year='2006' or release_year='2007';

 

正確的編寫方式:

select count(release_year='2006' or null) as '06films',count(release_year='2007' or null) as '07films' from film;

區別:count(*)和count(id)

創建表並插入語句

 

 create table t(id int);

 

 insert into t values(1),(2),(null);

Count(*):select count(*)from t;

Count(id):select count(id)from t;

說明:

Count(id)是不包含null的值

Count(*)是包含null的值

 

3、子查詢的優化

 

子查詢是我們在開發過程中經常使用的一種方式,在通常情況下,需要把子查詢優化爲join查詢但在優化是需要注意關聯鍵是否有一對多的關係,要注意重複數據。

查看我們所創建的t表

 

show create table t;

接下來我們創建一個t1表

create table t1(tid int);

並插入一條數據

我們要進行一個子查詢,需求:查詢t表中id在t1表中tid的所有數據;

select * from t where t.id in (select t1.tid from t1);

接下來我們用join的操作來進行操作

select id from t join t1 on t.id =t1.tid;

通過上面結果來看,查詢的結果是一致的,我們就將子查詢的方式優化爲join操作。

 

接下來,我們在t1表中再插入一條數據

insert into t1 values (1);

 

select * from t1;

在這種情況下,如果我們使用子查詢方式進行查詢,返回的結果就是如下圖所示:

如果使用join方式進行查找,如下圖所示:

 

在這種情況下出現了一對多的關係,會出現數據的重複,我們爲了方式數據重複,不得不使用distinct關鍵詞進行去重操作

select distinct id from t join t1 on t.id =t1.tid;

 

注意:這個一對多的關係是我們開發過程中遇到的一個坑,出現數據重複,需要大家注意一下。

 

例子:查詢sandra出演的所有影片:

explain select title,release_year,length

 from film

 where film_id in (

 select film_id from film_actor where actor_id in (

 select actor_id from actor where first_name='sandra'));

 

4、group by的優化

最好使用同一表中的列,

需求:每個演員所參演影片的數量-(影片表和演員表) 

explain select actor.first_name,actor.last_name,count(*)

from sakila.film_actor

inner join sakila.actor using(actor_id)

group by film_actor.actor_id;


優化後的SQL:

explain select actor.first_name,actor.last_name,c.cnt

from sakila.actor inner join (

select actor_id,count(*) as cnt from sakila.film_actor group by actor_id

)as c using(actor_id);

說明:從上面的執行計劃來看,這種優化後的方式沒有使用臨時文件和文件排序的方式了,取而代之的是使用了索引。查詢效率老高了。


 

這個時候我們表中的數據比較大,會大量的佔用IO操作,優化了sql執行的效率,節省了服務器的資源,因此我們就需要優化。

 

注意:

1mysql 中using關鍵詞的作用:也就是說要使用using,那麼表a和表b必須要有相同的列

2、在用Join進行多表聯合查詢時,我們通常使用On來建立兩個表的關係。其實還有一個更方便的關鍵字,那就是Using。

3、如果兩個表的關聯字段名是一樣的,就可以使用Using來建立關係,簡潔明瞭。

 

5、Limit查詢的優化

Limit常用於分頁處理,時長會伴隨order by從句使用,因此大多時候回使用Filesorts這樣會造成大量的IO問題。

例子:

需求:查詢影片id和描述信息,並根據主題進行排序,取出從序號50條開始的5條數據。

 

select film_id,description from sakila.film order by title limit 50,5;

執行的結果

在查看一下它的執行計劃:

對於這種操作,我們該用什麼樣的優化方式了?

優化步驟1:

使用有索引的列或主鍵進行order by操作,因爲大家知道,innodb是按照主鍵的邏輯順序進行排序的。可以避免很多的IO操作。

select film_id,description from sakila.film order by film_id limit 50,5;

查看一下執行計劃

那如果我們獲取從500行開始的5條記錄,執行計劃又是什麼樣的了?

explain select film_id,description from sakila.film order by film_id limit 500,5\G

隨着我們翻頁越往後,IO操作會越來越大的,如果一個表有幾千萬行數據,翻頁越後面,會越來越慢,因此我們要進一步的來優化。


 

優化步驟2、記錄上次返回的主鍵, 在下次查詢時使用主鍵過濾。(說明:避免了數據量大時掃描過多的記錄

上次limit是50,5的操作,因此我們在這次優化過程需要使用上次的索引記錄值,

select film_id,description from sakila.film  where film_id >55 and film_id<=60 order by film_id limit 1,5;

查看執行計劃:

結論:掃描行數不變,執行計劃是很固定,效率也是很固定的

注意事項:

主鍵要順序排序並連續的,如果主鍵中間空缺了某一列,或者某幾列,會出現列出數據不足5行的數據;如果不連續的情況,建立一個附加的列index_id列,保證這一列數據要自增的,並添加索引即可。

 

6、索引的優化

1、什麼是索引?

索引的作用相當於圖書的目錄,可以根據目錄中的頁碼快速找到所需的內容。

數據庫使用索引以找到特定值,然後順指針找到包含該值的行。在表中建立索引,然後在索引中找到符合查詢條件的索引值,最後通過保存在索引中的ROWID(相當於頁碼)快速找到表中對應的記錄。索引的建立是表中比較有指向性的字段,相當於目錄,比如說行政區域代碼,同一個地域的行政區域代碼都是相同的,那麼給這一列加上索引,避免讓它重複掃描,從而達到優化的目的!

2、如何創建索引

在執行CREATE TABLE語句時可以創建索引,也可以單獨用CREATE INDEX或ALTER TABLE來爲表增加索引。

1ALTER TABLE

ALTER TABLE用來創建普通索引、UNIQUE索引或PRIMARY KEY索引。

ALTER TABLE table_name ADD INDEX index_name (column_list)

ALTER TABLE table_name ADD UNIQUE (column_list)

ALTER TABLE table_name ADD PRIMARY KEY (column_list)

 

說明:其中table_name是要增加索引的表名,column_list指出對哪些列進行索引,多列時各列之間用逗號分隔。索引名index_name可選,缺省時,MySQL將根據第一個索引列賦一個名稱。另外,ALTER TABLE允許在單個語句中更改多個表,因此可以在同時創建多個索引。

2CREATE INDEX

CREATE INDEX可對錶增加普通索引或UNIQUE索引。

CREATE INDEX index_name ON table_name (column_list)

CREATE UNIQUE INDEX index_name ON table_name (column_list)

說明:table_name、index_name和column_list具有與ALTER TABLE語句中相同的含義,索引名不可選。另外,不能用CREATE INDEX語句創建PRIMARY KEY索引。

 

3索引類型

在創建索引時,可以規定索引能否包含重複值。如果不包含,則索引應該創建爲PRIMARY KEY或UNIQUE索引。對於單列惟一性索引,這保證單列不包含重複的值。對於多列惟一性索引,保證多個值的組合不重複。

 

PRIMARY KEY索引和UNIQUE索引非常類似。

 

事實上,PRIMARY KEY索引僅是一個具有名稱PRIMARY的UNIQUE索引。這表示一個表只能包含一個PRIMARY KEY,因爲一個表中不可能具有兩個同名的索引。

下面的SQL語句對students表在sid上添加PRIMARY KEY索引。

 

ALTER TABLE students ADD PRIMARY KEY (sid)

4刪除索引

可利用ALTER TABLE或DROP INDEX語句來刪除索引。類似於CREATE INDEX語句,DROP INDEX可以在ALTER TABLE內部作爲一條語句處理,語法如下。

 

DROP INDEX index_name ON talbe_name

ALTER TABLE table_name DROP INDEX index_name

ALTER TABLE table_name DROP PRIMARY KEY

 

其中,前兩條語句是等價的,刪除掉table_name中的索引index_name。

3條語句只在刪除PRIMARY KEY索引時使用,因爲一個表只可能有一個PRIMARY KEY索引,因此不需要指定索引名。如果沒有創建PRIMARY KEY索引,但表具有一個或多個UNIQUE索引,則MySQL將刪除第一個UNIQUE索引。

 

如果從表中刪除了某列,則索引會受到影響。對於多列組合的索引,如果刪除其中的某列,則該列也會從索引中刪除。如果刪除組成索引的所有列,則整個索引將被刪除。

   5、查看索引

 

mysql> show index from tblname;

 

mysql> show keys from tblname;

   6、什麼情況下,使用索引了?

1、表的主關鍵字

2、自動建立唯一索引

3、表的字段唯一約束

4、直接條件查詢的字段(在SQL中用於條件約束的字段)

5、查詢中與其它表關聯的字段

6、查詢中排序的字段(排序的字段如果通過索引去訪問那將大大提高排序速度)

7、查詢中統計或分組統計的字段

8、表記錄太少(如果一個表只有5條記錄,採用索引去訪問記錄的話,那首先需訪問索引表,再通過索引表訪問數據表,一般索引表與數據表不在同一個數據塊)

9、經常插入、刪除、修改的表(對一些經常處理的業務表應在查詢允許的情況下儘量減少索引)

10、數據重複且分佈平均的表字段(假如一個表有10萬行記錄,有一個字段A只有T和F兩種值,且每個值的分佈概率大約爲50%,那麼對這種表A字段建索引一般不會提高數據庫的查詢速度。)

11、經常和主字段一塊查詢但主字段索引值比較多的表字段

12、對千萬級MySQL數據庫建立索引的事項及提高性能的手段

3、如何選擇合適的列建立索引

1、在where從句,group by從句,order by從句,on從句中虛線的列添加索引

2、索引字段越小越好(因爲數據庫數據存儲單位是以“頁”爲單位的,數據存儲的越多,IO也會越大)

3、離散度大的列放到聯合索引的前面

例子:

select * from payment where staff_id =2 and customer_id =584;

注意:

是index(staff_idcustomer_id)好,還是index(customer_idstaff_id)好

那我們怎麼進行驗證離散度好了?

 

A、我們先查看一下表結構

desc payment;

B、分別查看這兩個字段中不同的id的數量,數量越多,則表明離散程度越大:因此可以通過下圖看出:customer_id 離散程度大。

結論:由於customer_id 離散程度大,使用index(customer_idstaff_id)好

 

C、mysql聯合索引

 

①命名規則 :表名_字段名

1、需要加索引的字段,要在where條件中

2、數據量少的字段不需要加索引

3、如果where條件中是OR關係,加索引不起作用

4、符合最左原則

什麼是聯合索引

  1. 兩個或更多個列上的索引被稱作聯合索引,又被稱爲是複合索引。
  2. 利用索引中的附加列,您可以縮小搜索的範圍,但使用一個具有兩列的索引 不同於使用兩個單獨的索引。複合索引的結構與電話簿類似,人名由姓和名構成,電話簿首先按姓氏對進行排序,然後按名字對有相同姓氏的人進行排序。如果您知 道姓,電話簿將非常有用;如果您知道姓和名,電話簿則更爲有用,但如果您只知道名不姓,電話簿將沒有用處。

 

所以說創建複合索引時,應該仔細考慮列的順序。對索引中的所有列執行搜索或僅對前幾列執行搜索時,複合索引非常有用;僅對後面的任意列執行搜索時,複合索引則沒有用處。

 

4、索引優化SQL的方法

1、索引的維護及優化(重複及冗餘索引)

增加索引會有利於查詢效率,但會降低insert,update,delete的效率,但實際上往往不是這樣的,過多的索引會不但會影響使用效率,同時會影響查詢效率,這是由於數據庫進行查詢分析時,首先要選擇使用哪一個索引進行查詢,如果索引過多,分析過程就會越慢,這樣同樣的減少查詢的效率,因此我們要知道如何增加,有時候要知道維護和刪除不需要的索引

 

2、如何找到重複和冗餘的索引

重複索引:

重複索引是指相同的列以相同的順序簡歷的同類型的索引,如下表中的 primary key和ID列上的索引就是重複索引

create table test(

id int not null primary key,

name varchar(10) not null,

title varchar(50) not null,

unique(id)

)engine=innodb;

 

冗餘索引:

冗餘索引是指多個索引的前綴列相同,或是在聯合索引中包含了主鍵的索引,下面這個例子中key(name,id)就是一個冗餘索引。

 

create table test(

id int not null primary key,

name varchar(10) not null,

title varchar(50) not null,

key(name,id)

)engine=innodb;

說明:對於innodb來說,每一個索引後面,實際上都會包含主鍵,這時候我們建立的聯合索引,又人爲的把主鍵包含進去,那麼這個時候就是一個冗餘索引。

 

3、如何查找重複索引

工具:使用pt-duplicate-key-checker工具檢查重複及冗餘索引

pt-duplicate-key-checker -uroot -padmin -h 127.0.0.1

4、索引維護的方法


由於業務變更,某些索引是後續不需要使用的,就要進行殺出。

在mysql中,目前只能通過慢查詢日誌配合pt-index-usage工具來進行索引使用情況的分析;

pt-index-usage -uroot -padmin /var/lib/mysql/mysql-host-slow.log

附:https://www.percona.com/downloads/


5、注意事項

設計好MySql的索引可以讓你的數據庫飛起來,大大的提高數據庫效率。設計MySql索引的時候有一下幾點注意:

 

1,創建索引

 

對於查詢佔主要的應用來說,索引顯得尤爲重要。很多時候性能問題很簡單的就是因爲我們忘了添加索引而造成的,或者說沒有添加更爲有效的索引導致。如果不加

 

索引的話,那麼查找任何哪怕只是一條特定的數據都會進行一次全表掃描,如果一張表的數據量很大而符合條件的結果又很少,那麼不加索引會引起致命的性能下降。

但是也不是什麼情況都非得建索引不可,比如性別可能就只有兩個值,建索引不僅沒什麼優勢,還會影響到更新速度,這被稱爲過度索引。

 

2,複合索引

比如有一條語句是這樣的:select * from users where area=’beijing’ and age=22;

 

如果我們是在area和age上分別創建單個索引的話,由於mysql查詢每次只能使用一個索引,所以雖然這樣已經相對不做索引時全表掃描提高了很多效

 

率,但是如果在area、age兩列上創建複合索引的話將帶來更高的效率。如果我們創建了(area, age,salary)的複合索引,那麼其實相當於創建了(area,age,salary)、(area,age)、(area)三個索引,這被稱爲最佳左前綴特性。

因此我們在創建複合索引時應該將最常用作限制條件的列放在最左邊,依次遞減。

 

3,索引不會包含有NULL值的列

 

只要列中包含有NULL值都將不會被包含在索引中,複合索引中只要有一列含有NULL值,那麼這一列對於此複合索引就是無效的。所以我們在數據庫設計時不要讓字段的默認值爲NULL。

 

4,使用短索引

 

對串列進行索引,如果可能應該指定一個前綴長度。例如,如果有一個CHAR(255)的 列,如果在前10 個或20 個字符內,多數值是惟一的,那麼就不要對整個列進行索引。短索引不僅可以提高查詢速度而且可以節省磁盤空間和I/O操作。

 

5,排序的索引問題

 

mysql查詢只使用一個索引,因此如果where子句中已經使用了索引的話,那麼order by中的列是不會使用索引的。因此數據庫默認排序可以符合要求的情況下不要使用排序操作;儘量不要包含多個列的排序,如果需要最好給這些列創建複合索引。

 

6,like語句操作

 

一般情況下不鼓勵使用like操作,如果非使用不可,如何使用也是一個問題。like “%aaa%” 不會使用索引而like “aaa%”可以使用索引。

 

7,不要在列上進行運算

 

select * from users where

 

YEAR(adddate)

 

8,不使用NOT IN和操作

 

NOT IN和操作都不會使用索引將進行全表掃描。NOT IN可以NOT EXISTS代替,id3則可使用id>3 or id

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章