1. 程式人生 > >MySQL全面瓦解24:構建高效能索引(策略篇)

MySQL全面瓦解24:構建高效能索引(策略篇)

學習如果構建高效能的索引之前,我們先來了解下之前的知識,以下兩篇是基礎原理,瞭解之後,對面後續索引構建的原則和優化方法會有更清晰的理解:

MySQL全面瓦解22:索引的介紹和原理分析

MySQL全面瓦解23:MySQL索引實現和使用

我們編寫索引的目的是什麼?就是使我們的sql語句執行得更加高效,更快的獲取或者處理資料,這個也是建設高效能Web的必要條件。

只有我們深刻理解了索引的原理和執行過程,才能知道怎麼恰當地使用索引,以及怎麼達到最優的查詢。

innodb是MySQL預設的儲存引擎,使用範圍也較廣,我們就以innodb儲存引擎為例來進行下面方案的說明。

b+樹儲存結構

MySQL採用b+樹的方式儲存索引資訊。b+樹圖例如下:

1、葉子節點儲存關鍵字(索引欄位的值)資訊及對應的值(完整記錄)。
2、其他非葉子節點只儲存鍵值資訊及子節點的鏈指標
3、每個葉子節點相當於MySQL中的一頁,同層級的葉子節點以雙向連結串列的形式相連
4、每個節點(頁)中儲存了多條記錄,記錄之間用單鏈表的形式連線組成了一條有序的連結串列,順序是按照索引欄位排序的
5、b+樹中檢索資料時:每次檢索都是從根節點開始,一直需要搜尋到葉子節點,如圖中所示.

InnoDB 的資料是按資料頁為單位來讀寫的。也就是說,當需要讀取一條記錄的時候,並不是將這個記錄本身從磁碟讀取出來,而是以頁為單位,將整個也載入到記憶體中,一個頁中可能有很多記錄,然後在記憶體中對頁進行檢索。

在innodb中,每個頁的大小預設是16kb。

聚集索引(主鍵索引)

每個表一定會有一個聚集索引(即主鍵索引),而表中的資料以b+樹的方式儲存,b+樹葉子節點中的key為主鍵值,data為完整記錄的資訊,非葉子節點儲存的是主鍵的值。

通過聚集索引檢索資料只需要按照b+樹的搜尋過程,即可以檢索到對應的記錄。

非聚集索引(輔助索引)

每個表可以有多個非聚集索引,b+樹結構,葉子節點的key為索引欄位欄位的值,data為主鍵的值,非葉子節點只儲存索引欄位的值。

通過非聚集索引檢索記錄的時候,需要2次操作,先在非聚集索引中檢索出主鍵,然後再到聚集索引中檢索出主鍵對應的記錄,這個過程叫做回表,比聚集索引多了一次操作。

保證索引有效使用

有效索引是指我們的某些欄位上建立了索引,並且在對該欄位進行檢索過程中,能夠快速定位到目標資料所在的頁,有效的降低頁的io操作,而不是去掃描所有的資料頁,這樣才算有效的利用索引,

保證了檢索是有效使用索引的(俗稱檢索走了索引)。但如果檢索過程不能夠確定資料在某些頁中,而進行了大量的資料頁掃描,我們則認為這種情況下索引對查詢是無效的。 

索引的優點

1、索引大大減少了伺服器需要掃描的資料量

2、索引可以幫助伺服器避免排序和臨時表

3、索引可以將隨機I/O變為順序I/O

上圖中所有的資料都是唯一的,查詢306的記錄,過程如下:

1、將Disk1頁載入到記憶體

2、在記憶體中採用二分法查詢,可以確定306位於[300,350)中間,所以我們需要去載入100關聯Disk5

3、將Disk5載入到記憶體中,採用二分法找到306的記錄

 

上圖中306有多條記錄,我們查詢306的所有記錄步驟如下:

1、將Disk1頁載入到記憶體

2、在記憶體中採用二分法查詢,可以確定306位於[300,350)中間,所以我們需要去載入100關聯Disk5

3、將Disk5載入到記憶體中,採用二分法找到第一個小於306的記錄,即300,從300這個點按照連結串列向後訪問,找到所有的306記錄,遇到大於306的停止

 

資料如上圖,查詢[255,355]所有記錄,因為Page和Page之間是雙向連結串列並且是ASC順序結構,頁內部的資料是單項ASC順序連結串列結構,所以只用找到範圍的起始值所在的位置,然後通過依靠連結串列訪問兩個位置之間所有的資料即可,

步驟如下:

1、將PDisk1頁載入到記憶體

2、記憶體中採用二分法找到255位於200關聯的Disk4中,355位於Disk6頁中

3、將Disk4載入到記憶體中,採用二分法找到第一個255的記錄,然後繼續完後掃描,Disk4掃描完成之後,通過連結串列結構繼續向後訪問Disk5中的300、306,當Disk5訪問完畢之後,通過Disk5的nextpage指標訪問下一頁Disk6中,繼續掃Disk6中的記錄,遇到大於355的值停止。

 

資料如上圖。

檢索以c字元開頭的所有記錄

執行步驟如下:

1、將Disk1資料載入到記憶體中

2、在Disk1頁的記錄中採用二分法找到最後一個小於等於c的值,這個值是ce,以及第一個大於c的,這個值是d,ce指向葉節點Disc4,d指向葉節點Disk6,此時可以斷定以f開頭的記錄可能存在於[Disk4,Disk6)這個範圍的頁內,即Disk4、Disk5這兩個頁中

3、載入Disk4這個頁,在內部以二分法找到第一條c開頭的記錄,然後以連結串列方式繼續向後訪問Disk5中的記錄,即可以找到所有已c開頭的資料

檢索包含c字元的記錄

包含一般是採用%c%來計算,但是這種寫法是破壞索引的有效使用,上面的資料中,c在每個頁中都存在,

我們通過Disk1頁中的記錄是無法判斷包含c的記錄會分佈在哪些頁中,只能載入所有的頁(一次次的IO操作),並且遍歷所有葉節點上的記錄資訊進行篩選,才可以找到包含c字元的記錄。

所以如果使用了%value%這種方式,索引對查詢是無效的。

如下圖,b+樹的資料項是複合的資料結構,比如(empname,depno,job)這種(即構建一個聯合索引)時,b+樹是按照從左到右的順序來建立搜尋樹的。

示例,當以('brand',106,'SALEMAN')這樣的資料來檢索的時候,b+樹會優先比較empname來確定下一步的所搜方向,如果empname相同再依次比較depno和job,最後得到檢索的資料。 但如果是(106,'SALEMAN')這樣,沒有empname的資料來的時候,b+樹就不知道下一步該查哪個節點,因為empname就是第一個比較因子,必須要先根據empname來搜尋才能知道下一步去哪裡查詢。 比如當('brand','SALEMAN')這樣的資料來檢索時,b+樹可以用empname來指定搜尋方向,但下一個欄位depno的缺失,所以只能把名字等於 'brand' 的資料都找到,然後再匹配職位是SALEMAN的資料了。 這個重要特徵就是索引的最左匹配原則,按照這個原則執行索引效率特別高。 我們試試在b+樹上分析和舉例: 下圖中是3個欄位(depno,empname,job)的聯合索引,資料以depno asc,empname asc,job asc這種排序方式儲存在節點中的, 排序原則: 1、索引以depno欄位升序 2、depno相同時,以empname欄位升序, 3、empname相同的時候,以job欄位升序   

第一位置

檢索depno=7的記錄

由於頁中的記錄是以depno asc,empname asc,job asc這種排序方式儲存的,所以depno欄位是有序的,可以通過二分法快速檢索到,步驟如下: 1、將Disk1載入到記憶體中 2、在記憶體中對Disk1中的記錄採用二分法找,可以確定depno=7的記錄位於{7,Brand,1}和{7,dyny,1}關聯的範圍內,這兩個所指向的頁分別是 Disk2 和 Disk4。 3、載入頁Disk2,在Disk2中採用二分法快速找到第一條depno=7的記錄,然後通過連結串列向下一條及下一頁開始掃描,直到在Disk4中找到第一個不滿足depno=7的記錄為止。

第一+第二位置

檢索depno=7 and empname like 'B%'的記錄

步驟跟上面是一致的,可以確定depno=1 and empname like 'B%'的記錄位於{7,Band,1}和{7,Bec,1}關聯的範圍內,查詢過程和depno=7查詢步驟類似。

第二位置

檢索empname like 'C%'的記錄

這種情況通過Disk1頁中的記錄,無法判斷empname like 'C%' 的記錄在哪些頁中的,只能逐個載入索引樹的頁,對所有記錄進行遍歷,然後進行過濾,此時索引無效。

第三位置

檢索job=8的記錄

這種情況和查詢 empname like 'C%' 也一樣,也只能掃描所有葉子節點,索引也無效。

第二+第三位置

empname和job一起查

這種原理跟前面兩個一致,無法使用索引,只能對所有資料進行掃描。

第一+第三位置

按照(depno,job)欄位順序檢索

這種僅使用到索引中的depno欄位了,通過depnon確定範圍之後,載入所有depno下的資料,再對job條件進行過濾。如果的depno查出來的資料基數巨大,也會慢。 比如我們的測試資料中 depno=16 的資料有50W左右,也是比較多的。

停止匹配的條件

檢索depno=1 and empname>'' and job=1的記錄

根據上面的圖,這種檢索方式只能先確定depno=1 and empname>''所在頁的範圍,然後對這個範圍的所有頁進行遍歷,job欄位在這個查詢的過程中,是無法確定資料在哪些頁的,此時我們說job是不走索引的,只有depno、empname能夠有效的確定索引頁的範圍。 類似這種的還有>、<、between and、like,多欄位聯合索引的情況下,mysql會一直向右匹配直到遇到範圍查詢(>、<、between and、like)就停止匹配。 通過上面的示例可以知道,遵循最左匹配原則才會真正有效利用索引。

衡量策略

假設我們有兩個有序的陣列,都包含10條記錄 [a,b,c,d,e,f,g,h,i,j,k] 和 [a,a,a,a,a,b,b,b,b,b] 如果要檢索值為b的所有記錄,哪個效率會高一點? 使用二分法查詢執行步驟如下: 1、使用二分法找到最後一個小於b(就是上面陣列中標紅色的a)的記錄 2、沿著這條記錄向後掃描,和b對比,直到遇到第一個大於b的值結束,或者直到掃描完所有資料。 採用上面的方法找到b的記錄,第一個陣列中更快的一些。因為第二個陣列中含有b的基數更大,需要訪問和比較的次數也更多一點。  這種區分是有一種計算公式來衡量的:
1 selecttivity = count(distinct c_name)/count(*) 
當索引區分度越高,檢索速度越快,索引區分度低,則說明重複的資料比較多,檢索的時候需要訪問更多的記錄才能夠找到所有目標資料。 當索引區分度小到無限趨近於0的時候,基本等同於全表掃描了,此時檢索效率肯定是慢的。 第一個陣列沒有重複資料,索引區分度為1,第二個區分度為0.2,所以第一個檢索效率更高。 我們建立索引的時候,儘量選擇區分度高的列作為索引。

資料對比 

我們來看看emp 表中的兩個欄位,empname 和 depno 欄位, empname基本不重複,所以每個empname只有一條資料;而 500W的資料大約分配了10個部門,所以每個的depno下有約50W的資料。
1 mysql> select count(distinct empname)/count(*),count(distinct depno)/count(*) from emp; 
2 +----------------------------------+--------------------------------+
3 | count(distinct empname)/count(*) | count(distinct depno)/count(*) |
4 +----------------------------------+--------------------------------+
5 | 0.1713                           | 0.0000                         |
6 +----------------------------------+--------------------------------+
7 1 row in set

請參考第21篇(MySQL全面瓦解21(番外):一次深夜優化億級資料分頁的奇妙經歷)中模擬的千萬資料,我們以這個資料為測試資料。

emp表中有500W資料 我們用emp來做測試

1 mysql> select count(*) from emp;
2 +----------+
3 | count(*) |
4 +----------+
5 |  5000000 |
6 +----------+
7 1 row in set 

無索引的效果

我們之前在emp表上做過索引,所以先看一下這個表目前所有的索引

可以看到,目前主鍵欄位id和depno欄位上都有建立索引

 1 mysql> desc emp;
 2 +----------+-----------------------+------+-----+---------+----------------+
 3 | Field    | Type                  | Null | Key | Default | Extra          |
 4 +----------+-----------------------+------+-----+---------+----------------+
 5 | id       | int(10) unsigned      | NO   | PRI | NULL    | auto_increment |
 6 | empno    | mediumint(8) unsigned | NO   |     | 0       |                |
 7 | empname  | varchar(20)           | NO   |     |         |                |
 8 | job      | varchar(9)            | NO   |     |         |                |
 9 | mgr      | mediumint(8) unsigned | NO   |     | 0       |                |
10 | hiredate | datetime              | NO   |     | NULL    |                |
11 | sal      | decimal(7,2)          | NO   |     | NULL    |                |
12 | comn     | decimal(7,2)          | NO   |     | NULL    |                |
13 | depno    | mediumint(8) unsigned | NO   | MUL | 0       |                |
14 +----------+-----------------------+------+-----+---------+----------------+
15 9 rows in set
16 
17 mysql> show index from emp;
18 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
19 | Table | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
20 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
21 | emp   |          0 | PRIMARY       |            1 | id          | A         |     4952492 | NULL     | NULL   |      | BTREE      |         |               |
22 | emp   |          1 | idx_emp_id    |            1 | id          | A         |     4952492 | NULL     | NULL   |      | BTREE      |         |               |
23 | emp   |          1 | idx_emp_depno |            1 | depno       | A         |          18 | NULL     | NULL   |      | BTREE      |         |               |
24 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
25 3 rows in set

 我們在沒有做索引的欄位上做一下查詢看看,在500W資料中查詢一個名叫LsHfFJA的員工,消耗 2.239S  ,獲取到一條id為4582071的資料。

再看看他的執行過程,掃描了4952492 條資料才找到該行資料:

1 mysql> explain select * from emp where empname='LsHfFJA';
2 +----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
3 | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
4 +----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
5 |  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL | 4952492 | Using where |
6 +----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
7 1 row in set 

我們按照主鍵id來檢索 

 

 因為在id上有聚集索引,所以檢索效率很高

1 mysql> explain select * from emp where  id=4582071;
2 +----+-------------+-------+-------+--------------------+---------+---------+-------+------+-------+
3 | id | select_type | table | type  | possible_keys      | key     | key_len | ref   | rows | Extra |
4 +----+-------------+-------+-------+--------------------+---------+---------+-------+------+-------+
5 |  1 | SIMPLE      | emp   | const | PRIMARY,idx_emp_id | PRIMARY | 4       | const |    1 | NULL  |
6 +----+-------------+-------+-------+--------------------+---------+---------+-------+------+-------+
7 1 row in set

這個速度很快,這個走的是上面介紹的唯一記錄檢索。 

字首索引和索引長度計算

這個我們上一篇有討論過,需要選擇適當的索引長度。

有時候需要索引很長的字元列,這會讓索引變得大且慢(每個頁儲存的內容是有限的,如果一個頁中可以儲存的索引記錄越多,那麼查詢效率就會提高,所以我們可以指定索引的欄位長度)。

通常可以索引開始的部分字元,這樣可以大大節約索引空間(每個頁),從而提高索引效率。但這樣也會降低索引的選擇性。

索引的選擇性是指不重複的索引值(也稱為基數,cardinality)和資料表的記錄總數的比值,範圍從1/#T到1之間。索引的選擇性越高則查詢效率越高,因為選擇性高的索引可以讓MySQL在查詢時過濾掉更多的行。唯一索引的選擇性是1,這是最好的索引選擇性,效能也是最好的。

MySQL官方:一般情況下某個字首的選擇性也是足夠高的,足以滿足查詢效能。對於BLOB,TEXT,或者很長的VARCHAR型別的列,必須使用字首索引,因為MySQL不允許索引這些列的完整長度。

字首索引的長度的判斷方法 
1 select count(distinct left(`c_name`,calcul_len))/count(*) from t_name;

 

1 SELECT
2     count(DISTINCT LEFT(cname, 3)) / count(*) AS sel3,
3     count(DISTINCT LEFT(cname, 4)) / count(*) AS sel4,
4     count(DISTINCT LEFT(cname, 5)) / count(*) AS sel5,
5     count(DISTINCT LEFT(cname, 6)) / count(*) AS sel6,
6     count(DISTINCT LEFT(cname, 7)) / count(*) AS sel7
7 FROM
8     tname
新增字首索引 
1 ALTER TABLE tname ADD KEY (cname[(lenth)]); 

測試emp_name最合適的長度,因為empname的長度基本維持在6個字元左右,少數量超過6長度,所以指定empname索引長度時6是最建議的

 1 mysql>  SELECT
 2      count(DISTINCT LEFT(empname, 3)) / count(*) AS sel3,
 3      count(DISTINCT LEFT(empname, 4)) / count(*) AS sel4,
 4      count(DISTINCT LEFT(empname, 5)) / count(*) AS sel5,
 5      count(DISTINCT LEFT(empname, 6)) / count(*) AS sel6,
 6      count(DISTINCT LEFT(empname, 7)) / count(*) AS sel7
 7  FROM
 8      emp;
 9 +--------+--------+--------+--------+--------+
10 | sel3   | sel4   | sel5   | sel6   | sel7   |
11 +--------+--------+--------+--------+--------+
12 | 0.0012 | 0.0076 | 0.0400 | 0.1713 | 0.1713 |
13 +--------+--------+--------+--------+--------+
14 1 row in set

我們可以使用 不同的長度來測試檢索效率

當長度為2的時候,匹配度低於 0.0012,檢索效率自然比較慢

 1 mysql> create index idx_emp_empname on emp(empname(2));
 2 Query OK, 0 rows affected
 3 Records: 0  Duplicates: 0  Warnings: 0
 4 
 5 mysql> select * from emp where empname='LsHfFJA';
 6 +---------+---------+---------+---------+-----+---------------------+------+------+-------+
 7 | id      | empno   | empname | job     | mgr | hiredate            | sal  | comn | depno |
 8 +---------+---------+---------+---------+-----+---------------------+------+------+-------+
 9 | 4582071 | 4582071 | LsHfFJA | SALEMAN |   1 | 2021-01-23 16:46:03 | 2000 | 400  |   106 |
10 +---------+---------+---------+---------+-----+---------------------+------+------+-------+
11 1 row in set  (1.793 sec) 

當長度為6的時候,檢索效率就比較高

 1 mysql> create index idx_emp_empname on emp(empname(6));
 2 Query OK, 0 rows affected
 3 Records: 0  Duplicates: 0  Warnings: 0
 4 
 5 mysql> select * from emp where empname='LsHfFJA';
 6 +---------+---------+---------+---------+-----+---------------------+------+------+-------+
 7 | id      | empno   | empname | job     | mgr | hiredate            | sal  | comn | depno |
 8 +---------+---------+---------+---------+-----+---------------------+------+------+-------+
 9 | 4582071 | 4582071 | LsHfFJA | SALEMAN |   1 | 2021-01-23 16:46:03 | 2000 | 400  |   106 |
10 +---------+---------+---------+---------+-----+---------------------+------+------+-------+
11 1 row in set  (0.003 sec)

聯合索引的使用

當我們需要在多個欄位上面做索引的時候,經常的做法是每個欄位都建一個索引,這種策略一般是不建議的,優先的做法是優化索引列的順序,或者建立一個全覆蓋的索引。

在多個列上建立獨立的單列索引大部分情況下並不能提高MySQL的查詢效能。MySQL的“索引合併”(index merge)策略一定程式上可以使用表上的多個單列索引來定位指定的行。索引合併策略能夠同時使用多個單列索引進行掃描,並將結果進行合併。那麼什麼時候選擇聯合索引呢?  1、當出現伺服器對多個索引做相交操作時(通常有多個and條件),更好的操作是建立一個包含所有相關列的多列索引,而不是多個獨立的單列索引。 2、當伺服器需要對多個索引做聯合操作時(通常有多個or條件),通常需要消耗大量CPU和記憶體資源在演算法的快取、排序和合並操作上。特別是當其中有些索引的選擇性不高,需要合併掃描返回的大量資料的時候。   這兩種情況下更好的操作是建立一個包含所有相關列的多列索引,而不是多個獨立的單列索引。

聯合索引列的順序策略

在建設聯合索引中,我們經常遇到的困惑就是索引的順序問題。正確的索引應該依賴使用該索引的查詢,並能同時考慮更好的滿足排序和分組的需求。 聯合索引意味著他按照我們上面描述的最左匹配原則來進行搜素。 當不考慮分組和排序的時候,我們經常是將選擇性高的內容放在前面,以下面的查詢為例:
1 select * from emp where  empname like 'LsHfFJ%' and depno=106;

 是建立(empname,depno)順序的索引 還是(depno,empname)順序的索引?我們應該是找一個選擇性高(也就是匹配內容少)的欄位放在前面。

1 mysql> select sum(empname like 'LsHfFJ%'),sum(depno=106) from emp;
2 +-----------------------------+----------------+
3 | sum(empname like 'LsHfFJ%') | sum(depno=106) |
4 +-----------------------------+----------------+
5 | 7                           | 500194         |
6 +-----------------------------+----------------+
7 1 row in set

可以看出 empname欄位的選擇性更高一點,對應條件下的資料值  empname like 'LsHfFJ%'  下的數量會少很多 。所以答案是將他作為索引的第一列。

但是這樣也有個問題,索引的設計是根據現有的資料執行情況進行處理的,可能對其他條件的查詢不公平,也可能隨著資料的膨脹或者收縮,欄位的選擇性發生了變化。

另外一種經驗做法是 考慮全域性基數和選擇性,而不是某個具體的查詢:

1 mysql> select count(distinct empname)/count(*) as empname_selecttivity,count(distinct depno)/count(*) as depno_selecttivity,count(*) from emp;
2 +----------------------+--------------------+----------+
3 | empname_selecttivity | depno_selecttivity | count(*) |
4 +----------------------+--------------------+----------+
5 | 0.1713               | 0.0000             |  5000000 |
6 +----------------------+--------------------+----------+
7 1 row in set 

empname欄位的選擇性更高一點,所以答案是將他作為索引的第一列。

1 mysql> select count(*) from emp where id between 18 and 28;
2 +----------+
3 | count(*) |
4 +----------+
5 |       11 |
6 +----------+
7 1 row in set  (0.001 sec) 

速度也很快,id上有主鍵索引,這個採用的上面介紹的範圍查詢可以快速定位目標資料。

但是如果範圍太大,跨度的page也太多,速度也會比較慢,如下:

1 mysql> select count(*) from emp where id between 1 and 4990000;
2 +----------+
3 | count(*) |
4 +----------+
5 |  4990000 |
6 +----------+
7 1 row in set  (0.878 sec)

上面id的值跨度太大,1所在的頁和499萬所在頁中間有很多頁需要讀取,所以比較慢。

所以使用between and的時候,區間跨度不要太大。 

in方式檢索資料,我們還是經常用的。

平時我們做專案的時候,建議少用表連線,比如電商中需要查詢訂單的資訊和訂單中商品的名稱,可以先查詢查詢訂單表,然後訂單表中取出商品的id列表,採用in的方式到商品表檢索商品資訊,由於商品id是商品表的主鍵,所以檢索速度還是比較快的。

通過id在400萬資料中檢索100條資料,看看效果:

 1 mysql>  select * from emp a where 
 2 a.id in (800000, 800001, 800002, 800003, 800004, 800005, 800006, 800007, 800008, 800009, 800010, 800011, 800012, 800013,
 3  800014, 800015, 800016, 800017, 800018, 800019, 800020, 800021, 800022, 800023, 800024, 800025, 800026, 800027, 800028, 
 4 800029, 800030, 800031, 800032, 800033, 800034, 800035, 800036, 800037, 800038, 800039, 800040, 800041, 800042, 800043, 800044, 
 5 800045, 800046, 800047, 800048, 800049, 800050, 800051, 800052, 800053, 800054, 800055, 800056, 800057, 800058, 800059, 800060, 
 6 800061, 800062, 800063, 800064, 800065, 800066, 800067, 800068, 800069, 800070, 800071, 800072, 800073, 800074, 800075, 800076, 
 7 800077, 800078, 800079, 800080, 800081, 800082, 800083, 800084, 8