Explain詳解與索引優化實踐

来源:https://www.cnblogs.com/ZekiChen/archive/2020/02/13/12295702.html
-Advertisement-
Play Games

1、何為explain執行計劃? 使用explain關鍵字可以模擬優化器執行SQL語句,從而知道MySQL是如何使用索引來處理你的SQL查詢語句以及連接表,可以分析查詢語句或是結構的性能瓶頸,幫助我們選擇更好的索引和寫出更優化的查詢語句。(說白了,就是優化SQL的工具) 2、如何使用explain? ...


1、何為explain執行計

使用explain關鍵字可以模擬優化器執行SQL語句,從而知道MySQL是如何使用索引來處理你的SQL查詢語句以及連接表,可以分析查詢語句或是結構的性能瓶頸,幫助我們選擇更好的索引和寫出更優化的查詢語句。(說白了,就是優化SQL的工具

2、如何使用explain?

在你的SQL查詢語句前加上 explain 即可,如explain select * from table,MySQL會在查詢上設置一個標記,執行查詢時,會返回執行計劃的信息,而不是執行這條SQL(如果 from 中包含子查詢,仍會執行該子查詢,將結果放入臨時表)。

3、使用explain的例子

需要使用三張表,分別為 actor 演員表,film 電影表,film_actor 電影-演員關聯表。

CREATE TABLE `actor` (
  `id` int(11) NOT NULL COMMENT '主鍵id',
  `name` varchar(45) DEFAULT NULL COMMENT '演員名稱',
  `update_time` datetime DEFAULT NULL COMMENT '修改時間',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `actor` (`id`, `name`, `update_time`) values('1','a','2020-02-11 22:56:00');
insert into `actor` (`id`, `name`, `update_time`) values('2','b','2020-02-11 22:56:00');
insert into `actor` (`id`, `name`, `update_time`) values('3','c','2020-02-11 22:56:00');

CREATE TABLE `film` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `name` varchar(10) DEFAULT NULL COMMENT '電影名稱',
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

insert into `film` (`id`, `name`) values('3','film0');
insert into `film` (`id`, `name`) values('1','film1');
insert into `film` (`id`, `name`) values('2','film2');

CREATE TABLE `film_actor` (
  `id` int(11) NOT NULL COMMENT '主鍵id',
  `film_id` int(11) NOT NULL COMMENT '電影id',
  `actor_id` int(11) NOT NULL COMMENT '演員id',
  `remark` varchar(255) DEFAULT NULL COMMENT '備註',
  PRIMARY KEY (`id`),
  KEY `idx_film_actor_id` (`film_id`,`actor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `film_actor` (`id`, `film_id`, `actor_id`, `remark`) values('1','1','1',NULL);
insert into `film_actor` (`id`, `film_id`, `actor_id`, `remark`) values('2','1','2',NULL);
insert into `film_actor` (`id`, `film_id`, `actor_id`, `remark`) values('3','2','1',NULL);

執行完以上SQL後,三張表數據對應如下:

下麵展示explain中每個列的信息:

(1)id列

id列的編號是select語句的序列號,有幾個 select 就有幾個id,並且id的序號是按 select 出現的順序而增長的(id越大,對應的select語句越先執行,如果id相等,則從上往下執行,id為NULL最後執行)。

MySQL將select查詢分為簡單查詢(SIMPLE)和複雜查詢(PRIMARY)。

複雜查詢分為三類:簡單子查詢、派生表(from語句中的子查詢)、union查詢。

1)簡單子查詢

執行SQL語句:EXPLAIN SELECT (SELECT 1 FROM actor LIMIT 1) FROM film

2)from子句中的子查詢

執行SQL語句:EXPLAIN SELECT id FROM (SELECT id FROM film) AS der

分析:這個查詢執行時有個臨時表別名為der,外部select查詢引用了這個臨時表。

3)union查詢

執行SQL語句:EXPLAIN SELECT 1 UNION ALL SELECT 1

分析:union結果總是放在一個匿名臨時表中,臨時表不在SQL中出現,因此它的id為NULL。(不推薦使用union,性能不高)

(2)select_type列

這一列表示對應行是簡單還是複雜查詢,如果是複雜查詢,又是上述三種複雜查詢中的哪一種。

1)SIMPLE:簡單查詢。查詢不包含子查詢和union。

執行SQL語句:EXPLAIN SELECT * FROM film WHERE id=2

2)PRIMARY:複雜查詢中最外層的select。

3)SUBQUERY:包含在select中的子查詢(不在from子句中)。

4)DERIVED:包含在from子句中的子查詢。MySQL會將結果存放在一個臨時表中,也稱為派生表(DERIVED的英文含義)。

執行SQL語句:EXPLAIN SELECT (SELECT 1 FROM actor WHERE id=1) FROM (SELECT * FROM film WHERE id=1) der

5)UNION:在union中的第二個和隨後的select。

6)UNION RESULT:從union臨時表檢索結果的select。

執行SQL語句:EXPLAIN SELECT 1 UNION ALL SELECT 1

(3)table列

這一列表示explain的一行正在訪問哪個表。

當from子句中有子查詢時,table列是<DERIVED N>格式,表示當前查詢依賴id=N的查詢,於是先執行id=N的查詢。

當有union時,UNION RESULT的table列的值為<union 1,2>,1和2表示參與union的select行id。

(4)type列

(溫馨提示:以下部分理論有可能解釋完還是懵逼,沒關係,繼續往下看,有實踐例子)

這一列表示關聯類型或訪問類型,即MySQL決定如何查找表中的行,查找數據記錄的大概範圍。

SQL語句查詢效率從最優到最差依次為:system > const > eq_ref > ref > range > index > ALL

一般來說,得保證查詢達到range級別,最好達到ref

NULL:MySQL能夠在SQL語句執行之前(即優化階段)分析分解查詢語句,在執行階段用不著再訪問表或索引。例如:在索引列中選取最小值,可以單獨查找索引來完成,不需要在執行時訪問表,出現的頻率不高。

const,system:MySQL能夠對查詢的某部分進行優化並將其轉化成一個常量(可以看show warnings的結果)。用於主鍵索引或唯一索引的所有列與常數比較時,表最多有一個匹配行,讀取1次,速度比較快。system是const的特例,表裡只有一條記錄匹配時為system。

執行SQL語句:EXPLAIN EXTENDED SELECT * FROM (SELECT * FROM film WHERE id=1) tmp

分析:上面的子查詢SELECT * FROM film WHERE id = 1語句where後面id使用的是主鍵索引查詢,主鍵是唯一的,所以查詢結果一定是只有一條記錄,對於明確知道結果集只有一條記錄的查詢,它的type為const類型,性能已經非常高了;而第一個select複雜查詢的表只有一條記錄,所以結果也肯定只有一條記錄(第二個select子查詢之前表中可能是多條記錄),這種特例它的type為system類型,性能最高。

執行SQL語句:EXPLAIN EXTENDED SELECT * FROM (SELECT * FROM film WHERE id=1) tmp;  SHOW WARNINGS;

分析:用explain extended查看執行計劃會比explain多一列filtered,該列給出一個百分比的值,這個值和rows列一起使用,可以估計出那些將要和explain中的前一個表進行連接的行的數目,前一個表就是指explain的id列的值比當前表的id小的表。explain extended還可以搭配show warnings一起使用,它可以給出一個優化建議,真正執行時是執行優化建議的那條SQL,但是如果是很複雜的SQL,它優化出來的結果可能都沒你原先的SQL性能高。

eq_ref:主鍵索引或唯一索引的所有部分被連接使用,最多只會返回一條符合條件的記錄。這可能是在const之外最好的連接類型了,簡單的select查詢不會出現這種type。

執行SQL語句:EXPLAIN SELECT * FROM film_actor LEFT JOIN film ON film_actor.film_id=film.id

分析:有兩條記錄,說明有2次查詢, id相等,則從上往下執行,說明第1條先執行查詢film_actor表,第2條左連接查詢film表。左連接film表並關聯film.id,由於film.id是唯一索引,film表只能關聯一行記錄,所以第2條select的type為eq_ref。

ref:相比eq_ref,不使用唯一索引,而是使用普通索引或者唯一索引的首碼部分,索引要和某個值相比較,可能會找到多條符合條件的記錄。

① 簡單select查詢,name是普通索引(非唯一索引)

執行SQL語句:EXPLAIN SELECT * FROM film WHERE NAME="film1"

② 關聯表查詢,idx_film_actor_id是film_id和actor_id的聯合索引,這裡使用了film_actor的索引左邊首碼部分 film_id。

執行SQL語句:EXPLAIN SELECT * FROM film LEFT JOIN film_actor ON film.id=film_actor.film_id

range:範圍掃描通常出現在in(),between,>,<,>=等操作中。使用一個索引來檢索給定範圍的行。

執行SQL語句:EXPLAIN SELECT * FROM actor WHERE id>1

index: 掃描全表索引,這通常會比ALL快一些。(index是從索引中讀取的,而ALL是從硬碟中讀取)

執行SQL語句:EXPLAIN SELECT * FROM film;(film表所有欄位都加了索引)

ALL: 即全表掃描,意味著MySQL需要從頭到尾去查找所需要的行(不走索引)。通常情況下這需要增加索引來優化了。

執行SQL語句:EXPLAIN SELECT * FROM actor;(actor表有一個欄位沒加索引)

(5)possible_keys列

這一列顯示查詢可能使用哪些索引來查找。

explain時可能出現possible_key有列,而key顯示NULL的情況,這種情況是因為表中數據不多,MySQL認為索引對此查詢幫助不大,選擇了全表查詢。

如果該列是NULL,則沒有相關的索引。在這種情況下,可以通過檢查where子句是否可以創造一個適當的索引來提高查詢性能,然後用explain查看效果。

(6)key列

這一列顯示MySQL實際採用哪個索引來優化對該表的訪問。

如果沒有使用索引,則該列是NULL。如果想強制MySQL使用或忽視possible_keys列中的索引,在查詢中使用force index、ignore index。

(7)key_len列

這一列顯示了MySQL在索引里使用的位元組數,通過這個值可以算出具體使用了索引中的哪些列。

舉例來說,film_actor表的聯合索引idx_film_actor_id由film_id和actor_id兩個int列組成,並且每個int是4位元組。通過下麵結果中的key_len=4可推斷出只使用了第一個列flim_id來執行索引查找。

執行SQL語句:EXPLAIN SELECT * FROM film_actor WHERE film_id=2

key_len計算規則如下:

① 字元串

  • char(n):n位元組長度
  • varchar(n):2位元組存儲字元串長度,如果是UTF-8,則長度為3n+2

② 數值類型

  • tinyint:1位元組
  • smallint:2位元組
  • int:4位元組
  • bigint:8位元組

③ 時間類型

  • date:3位元組
  • timestamp:4位元組
  • datetime:8位元組

④ 如果欄位允許為NULL,需要1位元組記錄是否為NULL

(8)ref列

這一列顯示了在key列記錄的索引中,表查找值所用到的列或常量,常見的有:const(常量)、欄位名(例:film.id)。

(9)rows列

這一列是MySQL估計要讀取並檢測的行數,註意這個不是結果集里的行數。

(10)Extra列 

這一列展示的是額外信息。常見的重要值如下:

Using index: 查詢的列被索引覆蓋,並且where篩選條件是索引的前導列(類似聯合索引的最左首碼原則),是性能高的表現。一般是使用了覆蓋索引(即索引包含了所有查詢的欄位)。對於InnoDB來說,如果是普通索引性能會有不少提高。

執行SQL語句:EXPLAIN SELECT film_id FROM film_actor WHERE film_id=1

Using where:查詢的列不完全被索引覆蓋,where篩選條件非索引的前導列。(不走索引,性能較低)

執行SQL語句:EXPLAIN SELECT * FROM actor WHERE name='a'

 Using where; Using index:查詢的列被索引覆蓋,並且where篩選條件是索引列之一但不是索引的前導列,意味著無法直接通過索引來查找符合條件的數據。

執行SQL語句:EXPLAIN SELECT film_id FROM film_actor WHERE actor_id=1

NULL:查詢的列未被索引覆蓋,並且where篩選條件是索引的前導列,意味著用到了索引,但是部分欄位未被索引覆蓋,必須通過“回表”來實現,不是純粹地用到了索引,也不是完全沒用到索引。

執行SQL語句:EXPLAIN SELECT * FROM film_actor WHERE film_id=1

Using index condition:MySQL 5.6版本開始加入的新特性,與Using where類似,查詢的列不完全被索引覆蓋,where條件中是一個前導列的範圍。

執行SQL語句:EXPLAIN SELECT * FROM film_actor WHERE film_id>1

Using temporary:MySQL需要創建一張臨時表來處理查詢。出現這種情況一般是要進行優化的,首先要想到用索引來優化。

① actor.name沒有索引,此時創建了一張臨時表來distinct。(distinct:去除查詢結果中的重覆記錄)

執行SQL語句:EXPLAIN SELECT DISTINCT NAME FROM actor

② film.name建立了idx_name索引,此時查詢時extra是Using index,沒有用臨時表。

執行SQL語句:EXPLAIN SELECT DISTINCT NAME FROM film

Using filesort:MySQL會對結果使用一個外部索引排序,而不是按照索引次序從表裡讀取行。此時MySQL會根據連接類型瀏覽所有符合條件的記錄,並保存排序關鍵字和行指針,然後排序關鍵字並按順序檢索行信息。這種情況下一般也是要考慮使用索引來優化。

① actor.name未創建索引,會瀏覽actor整個表,保存排序關鍵字name和對應的id,然後排序name並檢索行記錄。

執行SQL語句:EXPLAIN SELECT * FROM actor ORDER BY name

② film.name建立了idx_name索引,此時查詢時extra是Using index,因為索引底層數據結構已經是排好序的。

執行SQL語句:EXPLAIN SELECT * FROM film ORDER BY name

4、索引優化最佳實踐

使用了 employees 員工表:

CREATE TABLE `employees` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `name` varchar(24) NOT NULL COMMENT '員工姓名',
  `age` int(11) NOT NULL DEFAULT '0' COMMENT '員工年齡',
  `position` varchar(20) NOT NULL COMMENT '員工職位',
  `hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入職時間',
  PRIMARY KEY (`id`),
  KEY `idx_name_age_position` (`name`,`age`,`position`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

insert into `employees` (`id`, `name`, `age`, `position`, `hire_time`) values('1','LiLei','22','manager','2020-02-13 14:22:55');
insert into `employees` (`id`, `name`, `age`, `position`, `hire_time`) values('2','HanMeimei','23','dev','2020-02-13 14:22:57');
insert into `employees` (`id`, `name`, `age`, `position`, `hire_time`) values('3','Lucy','23','dev','2020-02-13 14:22:59');

(1)全值匹配

執行SQL語句:EXPLAIN SELECT * FROM employees WHERE name='LiLei'

執行SQL語句:EXPLAIN SELECT * FROM employees WHERE name='LiLei' AND age=22

執行SQL語句:EXPLAIN SELECT * FROM employees WHERE name='LiLei' AND age=22 AND position='manager'

(2)索引最左首碼原則 

如果索引了多列,要遵循最左首碼原則。指的是查詢從索引的最左前列開始並且不跳過索引中的列。

提問:為什麼聯合索引要想命中索引必須採用最左首碼原則?(命中索引:即是否用到了索引)

以下索引優化規則很多都可以結合下麵這張圖思考,聯合索引底層的索引數據結構圖(B+樹),索引的排序首先按10002排序,接著是Staff,最後才是1996-08-03,如果不先拿第一個欄位10002去比較,根本沒法比較,導致無法命中索引。

提問:以下SQL命中索引?

① EXPLAIN SELECT * FROM employees WHERE age = 22 AND position = 'manager';
② EXPLAIN SELECT * FROM employees WHERE position = 'manager';
③ EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
④ EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND position = 'manager';

分析:

①中的where條件後面age=22不是索引的最左前列,後面就不用看了,沒有命中索引,②也是如此。

③中的name是索引idx_name_age_position的最左前列,命中索引。

④中的name命中索引,position沒有命中索引,因為跳過索引中的age列,中間斷了,age列還是需要全表掃描。

(3)不要在索引列上做任何操作(如計算、函數、自動或手動類型轉換),否則會導致索引失效而轉向全表掃描

執行SQL語句:EXPLAIN SELECT * FROM employees WHERE LEFT(name, 3)='LiLei'

(4)存儲引擎不能使用索引中範圍條件右邊的列

執行SQL語句:EXPLAIN SELECT * FROM employees WHERE name='LiLei' AND age>22 AND position='manager'

分析:長度為78,name為74,age是int類型,所以為4,即只有name和age命中索引,position沒有命中索引,因為它屬於age範圍條件右邊的索引列。

(5)儘量使用覆蓋索引(只訪問索引的查詢,索引列包含查詢列),減少 select * 語句

執行SQL語句:EXPLAIN SELECT name,age FROM employees WHERE name='LiLei'

執行SQL語句:EXPLAIN SELECT * FROM employees WHERE name='LiLei'

 (6)MySQL在使用不等於(!= 或者 <>)的時候無法使用索引,會導致全表掃描

執行SQL語句:EXPLAIN SELECT * FROM employees WHERE name != 'LiLei'

(7)is null,is not null也無法使用索引

執行SQL語句:

EXPLAIN SELECT * FROM employees WHERE name IS NULL

(8)like以通配符開頭('$abc'),MySQL索引會失效導致全表掃描

執行SQL語句:EXPLAIN SELECT * FROM employees WHERE name LIKE '%Lei'

執行SQL語句:EXPLAIN SELECT * FROM employees WHERE name LIKE 'Lei%'

提問:如何解決like '%字元串%' 索引沒有命中?

① 使用覆蓋索引,查詢欄位必須是建立覆蓋索引欄位

執行SQL語句:EXPLAIN SELECT name,age,position FROM employees WHERE name LIKE '%Lei%'

② 當覆蓋索引指向的欄位是varchar(380)及以上的欄位時,覆蓋索引會失效!

(9)字元串不加單引號,索引失效(內部會做一個字元串轉換函數)

執行SQL語句:EXPLAIN SELECT * FROM employees WHERE name=1000

(10)少用or或in,用它查詢時,非主鍵欄位的索引會失效,主鍵索引有時生效,有時不生效,跟數據量有關,具體還得看MySQL的查詢優化結果

 執行SQL語句:EXPLAIN SELECT * FROM employees WHERE name='LiLei' OR name='Hanmeimei'

總結

like KK% 相當於等於常量,%KK 和 %KK% 相當於範圍。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • API:application program interface ABI:application binary interface linux系統的ABI文件是ELF格式的 windows系統的ABI文件是exe,msi格式的 系統級開發語言:c/c++ ​ 作品:httpd,vsftpd,ngi ...
  • ARM 前幾天剛發佈了 Cortex-M 家族最新一款內核 - Cortex-M55 以及首款面向 Cortex-M 系列的 microNPU - Ethos-U55。Cortex-M55 是第一款面向 AI/ML 的 Cortex-M 內核,痞子衡也專門為此寫過一篇小文 《為AI/ML而生(Cor... ...
  • 上一篇文章學習了用戶及文件相關許可權,本篇繼續學習防火牆技術。 ...
  • 一、 使用Java操作Redis前,請先運行Redis服務與下載Redis驅動,以maven工程為例,引入如下jar <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0 ...
  • 這是高級開發者面試時經常被問的問題。實際我們在平時的開發中,經常會遇到的,在用SQLyog等工具創建表時,就有一個引擎項要你去選。如下圖: Mysql的存儲引擎有這麼多種,實際我們在平時用的最多的莫過於InnoDB和MyISAM了。 所有如果面試官問道mysql有哪些存儲引擎,你只需要告訴這兩個常用 ...
  • 大數據技術體系的知識量是比較大的,而且涉及到的內容也具有一定的難度,對於初學者的知識結構還是有一定要求的。通常來說,要想學習大數據技術,需要具有一定的數學和電腦基礎,如果具有一定的統計學基礎會更好一些。 ...
  • 問題概述 "新冠期間"遠程辦公,需要重新搭建一套ClouderaManager(CM)開發環境,一位測試同事發現HBase的RegionServer無法啟動,在CM界面上啟動總是失敗,觀察一下日誌,也沒有什麼明顯的報錯。我就專門看了一下。 排查思路 1. 因為有opentsdb在讀寫Hbase Re ...
  • 創建 test 測試表 CREATE TABLE `test` ( `id` int(11) NOT NULL AUTO_INCREMENT, `c1` varchar(10) DEFAULT NULL, `c2` varchar(10) DEFAULT NULL, `c3` varchar(10) ...
一周排行
    -Advertisement-
    Play Games
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...