(四)全解MySQL之索引初識(shí)篇:索引機(jī)制、索引分類、索引使用與管理綜述

引言

? ?由于MySQL是作為存儲(chǔ)層部署在業(yè)務(wù)系統(tǒng)的最后端,所有的業(yè)務(wù)數(shù)據(jù)最終都要入庫(kù)落盤,但隨著一個(gè)項(xiàng)目在線上運(yùn)行的時(shí)間越來(lái)越久,數(shù)據(jù)庫(kù)中的數(shù)據(jù)量自然會(huì)越來(lái)越多,而數(shù)據(jù)體積出現(xiàn)增長(zhǎng)后,當(dāng)需要從表查詢一些數(shù)據(jù)時(shí),效率會(huì)越發(fā)低下。在正常情況下,表的查詢性能和數(shù)據(jù)量是成反比的,也就是數(shù)據(jù)越多,查詢?cè)铰?/p>

這是什么原因?qū)е碌哪兀坑捎?code>MySQL默認(rèn)的查詢方式導(dǎo)致的,舉個(gè)例子~

SELECT * FROM `zz_student`;
+------------+--------+------+--------+
| student_id | name   | sex  | height |
+------------+--------+------+--------+
|          1 | 竹子   | 男   | 185cm  |
|          2 | 熊貓   | 女   | 170cm  |
|          3 | 子竹   | 男   | 182cm  |
|          4 | 棕熊   | 男   | 187cm  |
|          5 | 黑豹   | 男   | 177cm  |
|          6 | 腦斧   | 男   | 178cm  |
|          7 | 兔紙   | 女   | 165cm  |
+------------+--------+------+--------+

SELECT * FROM `zz_student`  WHERE name = "腦斧";

上面給出了一張學(xué)生表,其中有七位學(xué)生信息,而此時(shí)要查詢姓名為「腦斧」的學(xué)生信息時(shí),MySQL底層是如何檢索數(shù)據(jù)的呢?會(huì)觸發(fā)磁盤IO,對(duì)表中的數(shù)據(jù)進(jìn)行逐條讀取并判斷,也就是說,在這里想要查找到符合要求的數(shù)據(jù),至少要經(jīng)過六次磁盤IO才能檢索到目標(biāo)(暫且先不考慮局部性讀取原理與隨機(jī)IO)。

  • 那假設(shè)這個(gè)表中有1000W條數(shù)據(jù)呢?要查的目標(biāo)數(shù)據(jù)位于表的900W行以后怎么辦?豈不是要觸發(fā)幾百萬(wàn)次磁盤IO才能檢索到數(shù)據(jù)啊,如果真的這樣去干,其效率大家可想而知。

在這種情況下,又該如何去提升數(shù)據(jù)庫(kù)的查詢性能呢?因?yàn)椴樵兺际且粋€(gè)業(yè)務(wù)系統(tǒng)中最頻繁的操作,一般項(xiàng)目的寫/讀請(qǐng)求比例都遵循三七定律,也就是30%的請(qǐng)求會(huì)涉及到寫庫(kù)操作,另外70%則屬于查庫(kù)類型的操作。

? ?在思考如何提升查詢性能前,咱們不妨先回想一下小時(shí)候的場(chǎng)景,小時(shí)候由于剛接觸漢字,很多字都不認(rèn)識(shí),所以通常每個(gè)人小時(shí)候都會(huì)擁有一本「新華字典」,但一本字典那么厚,我們是一頁(yè)頁(yè)去翻的嗎?并不是,字典中有目錄索引,我們可以根據(jù)音節(jié)、偏旁等方式查找不認(rèn)識(shí)的字。
[圖片上傳失敗...(image-83f86d-1670662044138)]
在「新華字典」中一頁(yè)頁(yè)翻找某個(gè)漢字,就類似于我們前面給出的全表掃描方式,效率特別特別低,而通過目錄索引則能夠在很短的時(shí)間內(nèi)找到目標(biāo)漢字。

? ?既然字典中都存在目錄索引頁(yè),能幫助小時(shí)候的我們快速檢索漢字,那這個(gè)思想能否應(yīng)用到數(shù)據(jù)庫(kù)中來(lái)呢?答案是當(dāng)然可以,并且MySQL也提供了索引機(jī)制,索引是數(shù)據(jù)庫(kù)中的核心組件之一,一張表中建立了合適的索引后,往往在面對(duì)海量數(shù)據(jù)查詢時(shí),能夠事半功倍,接下來(lái)一起聊一聊MySQL的索引。

索引機(jī)制會(huì)分為上、中、下三篇進(jìn)行闡述,大致內(nèi)容如下:
《上篇:索引初識(shí)篇》主要講解索引的概述、分類、使用與管理等;
《中篇:索引應(yīng)用篇》主要闡述索引優(yōu)劣分析、建立索引的原則、索引失效的場(chǎng)景、如何正確的使用索引、索引優(yōu)化機(jī)制等;
《下篇:索引原理篇》則主要講述索引的底層實(shí)現(xiàn)、B+Tree、Hash數(shù)據(jù)結(jié)構(gòu)、聚簇索引和非聚簇索引實(shí)現(xiàn)、索引查詢?cè)怼⑺饕芾韺?shí)現(xiàn)等;

一、MySQL索引機(jī)制概述

? ?對(duì)于MySQL索引機(jī)制的作用,經(jīng)過上述「新華字典」的案例后可得知:索引就是用來(lái)幫助表快速檢索目標(biāo)數(shù)據(jù)的。此時(shí)先來(lái)簡(jiǎn)單回顧一下MySQL中索引是如何使用的呢?首先需要?jiǎng)?chuàng)建索引,MySQL可以通過CREATE、ALTER、DDL三種方式創(chuàng)建一個(gè)索引。

1.1、MySQL索引的創(chuàng)建方式

  • ①使用CREATE語(yǔ)句創(chuàng)建
CREATE INDEX indexName ON tableName (columnName(length) [ASC|DESC]);

這種創(chuàng)建方式可以給一張已存在的表結(jié)構(gòu)添加索引,其中需要指定幾個(gè)值:

  • indexName:當(dāng)前創(chuàng)建的索引,創(chuàng)建成功后叫啥名字。
  • tableName:要在哪張表上創(chuàng)建一個(gè)索引,這里指定表名。
  • columnName:要為表中的哪個(gè)字段創(chuàng)建索引,這里指定字段名。
  • length:如果字段存儲(chǔ)的值過長(zhǎng),選用值的前多少個(gè)字符創(chuàng)建索引。
  • ASC|DESC:指定索引的排序方式,ASC是升序,DESC是降序,默認(rèn)ASC

當(dāng)然,上述語(yǔ)句中的INDEX也可更改為KEY,作用都是創(chuàng)建一個(gè)普通索引,而對(duì)于其他的索引類型,這點(diǎn)在后續(xù)的索引分類中再聊。

  • ②使用ALTER語(yǔ)句創(chuàng)建
ALTER TABLE tableName ADD INDEX indexName(columnName(length) [ASC|DESC]);

這里的參數(shù)都相同,所以不再重復(fù)贅述。

  • ③建表時(shí)DDL語(yǔ)句中創(chuàng)建
CREATE TABLE tableName(  
  columnName1 INT(8) NOT NULL,   
  columnName2 ....,
  .....,
  INDEX [indexName] (columnName(length))  
);

這種方式就比較適合在庫(kù)表設(shè)計(jì)時(shí),已經(jīng)確定了索引項(xiàng)的情況下建立。

1.2、查詢、刪除、指定索引

但不管通過哪種方式建立索引,本質(zhì)上創(chuàng)建的索引都是相同的,當(dāng)索引創(chuàng)建完成后,可通過SHOW INDEX FROM tableName;這條命令查詢一個(gè)表中擁有的索引,如下:

CREATE TABLE `zz_user`  (
  `user_id` int(8) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) NULL DEFAULT "",
  `user_sex` varchar(255) NULL DEFAULT "",
  `user_phone` varchar(255) NULL DEFAULT "",
    PRIMARY KEY (`user_id`) USING BTREE
)
    ENGINE = InnoDB 
    CHARACTER SET = utf8 
    COLLATE = utf8_general_ci 
    ROW_FORMAT = Compact;

在上述的建表SQL中,為user_id創(chuàng)建了一個(gè)主鍵索引,然后來(lái)查一下當(dāng)前表的索引信息:
[圖片上傳失敗...(image-5caef5-1670662044138)]
簡(jiǎn)單的概述一下查詢后,每個(gè)字段的含義:

  • Table:當(dāng)前索引屬于那張表。
  • Non_unique:目前索引是否屬于唯一索引,0代表是的,1代表不是。
  • Key_name:當(dāng)前索引的名字。
  • Seq_in_index:如果當(dāng)前是聯(lián)合索引,目前字段在聯(lián)合索引中排第幾個(gè)。
  • Column_name:當(dāng)前索引是位于哪個(gè)字段上建立的。
  • Collation:字段值以什么方式存儲(chǔ)在索引中,A表示有序存儲(chǔ),NULL表無(wú)序。
  • Cardinality:當(dāng)前索引的散列程度,也就是索引中存儲(chǔ)了多少個(gè)不同的值。
  • Sub_part:當(dāng)前索引使用了字段值的多少個(gè)字符建立,NULL表示全部。
  • Packed:表示索引在存儲(chǔ)字段值時(shí),以什么方式壓縮,NULL表示未壓縮,
  • Null:當(dāng)前作為索引字段的值中,是否存在NULL值,YES表示存在。
  • ?Index_type:當(dāng)前索引的結(jié)構(gòu)(BTREE, FULLTEXT, HASH, RTREE)。
  • ?Comment:創(chuàng)建索引時(shí),是否對(duì)索引有備注信息。

這條命令在后續(xù)排除問題、性能調(diào)優(yōu)時(shí),會(huì)有不小的作用,比如可以通過分析其中的Cardinality字段值,如果該值少于數(shù)據(jù)的實(shí)際行數(shù),那目前索引有可能失效(對(duì)于這些后續(xù)排查篇和SQL優(yōu)化篇再聊)。

OK~,到這里了解了一下索引相關(guān)的創(chuàng)建、查詢命令,接著再看看刪除、強(qiáng)制使用命令。

MySQL中并未提供修改索引的命令,也就說當(dāng)你建錯(cuò)了索引,只能先刪再重新建立一次,刪除索引的語(yǔ)句如下:

DROP INDEX indexName ON tableName;

當(dāng)然,當(dāng)建立了一條索引后,也可以強(qiáng)制性的為SELECT語(yǔ)句指定索引,如下:

SELECT * FROM table_name FORCE INDEX(index_name) WHERE .....;

FORCE INDEX關(guān)鍵字可以為一條查詢語(yǔ)句強(qiáng)制指定走哪個(gè)索引查詢,但要牢記的是:如果當(dāng)前的查詢SQL壓根不會(huì)走指定的索引字段,哪這種方式是行不通的,這個(gè)關(guān)鍵字的用法是:一條查詢語(yǔ)句在有多個(gè)索引可以檢索數(shù)據(jù)時(shí),顯式指定一個(gè)索引,減少優(yōu)化器選擇索引的耗時(shí)。

但要注意:如果你對(duì)于你整個(gè)業(yè)務(wù)系統(tǒng)十分熟悉,那可以這樣干。但如果不熟悉的話,還是交給優(yōu)化器來(lái)自行選擇,否則會(huì)適得其反!

1.3、數(shù)據(jù)庫(kù)索引的本質(zhì)

? ?前面一直在聊創(chuàng)建、查看、刪除、指定等一些索引的基本操作,但索引本質(zhì)上在數(shù)據(jù)庫(kù)中是什么呢?大家都知道,數(shù)據(jù)庫(kù)是基于磁盤工作的,所有的數(shù)據(jù)都會(huì)放到磁盤上存儲(chǔ),而索引也是數(shù)據(jù)的一種,因此與表數(shù)據(jù)相同,最終創(chuàng)建出的索引也會(huì)在磁盤生成本地文件。

? ?不過索引文件在磁盤中究竟以何種方式存儲(chǔ),這是由索引的數(shù)據(jù)結(jié)構(gòu)來(lái)決定的。同時(shí),由于索引機(jī)制最終是由存儲(chǔ)引擎實(shí)現(xiàn),因此不同存儲(chǔ)引擎下的索引文件,其保存在本地的格式也并不相同。

在這里有一個(gè)點(diǎn)需要注意:建立索引的工作在表數(shù)據(jù)越少時(shí)越好,如果你想要給一張百萬(wàn)、千萬(wàn)條數(shù)據(jù)級(jí)別的表新創(chuàng)建一個(gè)索引,那創(chuàng)建的耗時(shí)也不短,這是為什么呢?

因?yàn)閯倓偭倪^,索引本質(zhì)上和表是一樣的,都是磁盤中的文件,那也就代表著創(chuàng)建一個(gè)索引,并不像單純的給一張表加個(gè)約束那么簡(jiǎn)單,而是會(huì)基于原有的表數(shù)據(jù),重新在磁盤中創(chuàng)建新的本地索引文件。假設(shè)表中有一千萬(wàn)條數(shù)據(jù),那創(chuàng)建索引時(shí),就需要將索引字段上的1000W個(gè)值全部拷貝到本地索引文件中,同時(shí)做好排序并與表數(shù)據(jù)產(chǎn)生映射關(guān)系。

OK~,至此就對(duì)MySQL提供的索引機(jī)制做了簡(jiǎn)單回顧,下面再來(lái)說說數(shù)據(jù)庫(kù)中“多樣化”的索引類型。

二、MySQL的索引分類

? ?在前面我為什么用多樣化去形容數(shù)據(jù)庫(kù)索引呢?因?yàn)榇_實(shí)如此,先列一些大家都聽說過的索引稱呼:聚簇索引、非聚簇索引、唯一索引、主鍵索引、聯(lián)合索引、全文索引、單列索引、多列索引、復(fù)合索引、普通索引、二級(jí)索引、輔助索引、次級(jí)索引、有序索引、B+Tree索引、R-Tree索引、T-Tree索引、Hash索引、空間索引、前綴索引......

是不是看的眼花繚亂,這些都是MySQL中索引的一些稱呼,一通看下來(lái),估計(jì)大家看“索引”兩個(gè)字都有點(diǎn)不認(rèn)識(shí)了^_^

但實(shí)際上MySQL中真的有這么多索引類型嗎?其實(shí)并沒有,上述列出的索引稱呼中,有幾個(gè)稱呼對(duì)應(yīng)的索引是同一個(gè),有一部分只是邏輯上的索引,那索引究竟該如何分類呢?其實(shí)從不同的層面上來(lái)說,可以將索引劃分為不同的類型,接下來(lái)重點(diǎn)聊一聊。

2.1、數(shù)據(jù)結(jié)構(gòu)層次

? ?前面聊索引本質(zhì)的時(shí)候提到過,索引建立后也會(huì)在磁盤生成索引文件,那每個(gè)具體的索引節(jié)點(diǎn)該如何在本地文件中存放呢?這點(diǎn)是由索引的數(shù)據(jù)結(jié)構(gòu)來(lái)決定的。比如索引的底層結(jié)構(gòu)是數(shù)組,那所有的索引節(jié)點(diǎn)都會(huì)以Node1→Node2→Node3→Node4....這樣的形式,存儲(chǔ)在磁盤同一塊物理空間中,不過MySQL的索引不支持?jǐn)?shù)組結(jié)構(gòu),或者說數(shù)組結(jié)構(gòu)不適合作為索引結(jié)構(gòu),MySQL索引支持的數(shù)據(jù)結(jié)構(gòu)如下:

  • B+Tree類型:MySQL中最常用的索引結(jié)構(gòu),大部分引擎支持,有序。
  • Hash類型:大部分存儲(chǔ)引擎都支持,字段值不重復(fù)的情況下查詢最快,無(wú)序。
  • R-Tree類型:MyISAM引擎支持,也就是空間索引的默認(rèn)結(jié)構(gòu)類型。
  • T-Tree類型:NDB-Cluster引擎支持,主要用于MySQL-Cluster服務(wù)中。

在上述的幾種索引結(jié)構(gòu)中,B+樹和哈希索引是最常見的索引結(jié)構(gòu),幾乎大部分存儲(chǔ)引擎都實(shí)現(xiàn)了,對(duì)于后續(xù)兩種索引結(jié)構(gòu)在某些情況下也較為常見,但除開列出的幾種索引結(jié)構(gòu)外,MySQL索引支持的數(shù)據(jù)結(jié)構(gòu)還有R+、R*、QR、SS、X樹等結(jié)構(gòu)。

但為何后續(xù)的一些索引結(jié)構(gòu)大家沒聽說過呢?這是因?yàn)樗饕降字С质裁磾?shù)據(jù)結(jié)構(gòu),這是由存儲(chǔ)引擎決定的,不同的存儲(chǔ)引擎支持的索引結(jié)構(gòu)也并不同,目前較為常用的引擎就是MyISAM、InnoDB,因此大家未曾聽說后面列出的這些索引結(jié)構(gòu)也是正常的。

當(dāng)然,也正因?yàn)樗饕Y(jié)構(gòu)由存儲(chǔ)引擎決定,而MySQL引擎層在《MySQL架構(gòu)篇》中提到過,屬于可拔插式引擎,所以如果你有能力自己實(shí)現(xiàn)一個(gè)引擎,那你甚至可以讓引擎的索引機(jī)制支持任何數(shù)據(jù)結(jié)構(gòu)。

MySQL中創(chuàng)建索引時(shí),其默認(rèn)的數(shù)據(jù)結(jié)構(gòu)就為B+Tree,如何更換索引的數(shù)據(jù)結(jié)構(gòu)呢?如下:

CREATE INDEX indexName ON tableName (columnName(length) [ASC|DESC]) USING HASH;

也就是在創(chuàng)建索引時(shí),通過USING關(guān)鍵字顯示指定索引的數(shù)據(jù)結(jié)構(gòu)(必須要為當(dāng)前引擎支持的結(jié)構(gòu))。

同時(shí)索引會(huì)被分為有序索引和無(wú)序索引,這是指索引文件中存儲(chǔ)索引節(jié)點(diǎn)時(shí),會(huì)不會(huì)按照字段值去排序。那一個(gè)索引到底是有序還是無(wú)序,就是依據(jù)數(shù)據(jù)結(jié)構(gòu)決定的,例如B+Tree、R-Tree等樹結(jié)構(gòu)都是有序,而哈希結(jié)構(gòu)則是無(wú)序的。

2.2、字段數(shù)量層次

? ?前面從索引的數(shù)據(jù)結(jié)構(gòu)層次出發(fā),可以將索引分為不同結(jié)構(gòu)的類型,而從表字段的層次來(lái)看,索引又可以分為單列索引和多列索引,這兩個(gè)稱呼也比較好理解,單列索引是指索引是基于一個(gè)字段建立的,多列索引則是指由多個(gè)字段組合建立的索引。

單列索引也會(huì)分為很多類型,比如:

  • 唯一索引:指索引中的索引節(jié)點(diǎn)值不允許重復(fù),一般配合唯一約束使用。
  • 主鍵索引:主鍵索引是一種特殊的唯一索引,和普通唯一索引的區(qū)別在于不允許有空值。
  • 普通索引:通過KEY、INDEX關(guān)鍵字創(chuàng)建的索引就是這個(gè)類型,沒啥限制,單純的可以讓查詢快一點(diǎn)。
  • .....還有很多很多,只要是基于單個(gè)字段建立的索引都可以被稱為單列索引。

多列索引的概念前面解釋過了,不過它也有很多種叫法,例如:

  • 組合索引、聯(lián)合索引、復(fù)合索引、多值索引....

但不管名稱咋變,描述的含義都是相同的,即由多個(gè)字段組合建立的索引。

不過在使用多列索引時(shí)要注意:當(dāng)建立多列索引后,一條SELECT語(yǔ)句,只有當(dāng)查詢條件中了包含了多列索引的第一個(gè)字段時(shí),才能使用多列索引,下面舉個(gè)栗子。

比如在用戶表中,通過id、name、age三個(gè)字段建立一個(gè)多列索引,什么情況下會(huì)使用索引,什么時(shí)候不會(huì)呢?如下:

-- 無(wú)法使用多列索引的SQL語(yǔ)句
SELECT * FROM `zz_user` WHERE name = "竹子" AND age = "18";

-- 能命中多列索引的SQL語(yǔ)句
SELECT * FROM `zz_user` WHERE name = "竹子" AND id = 6;

OK,到這里就根據(jù)字段數(shù)量的層面出發(fā),簡(jiǎn)單講明了單列和多列索引的概念,但無(wú)論是單列還是多列,都可以存在一個(gè)前綴索引的概念,啥叫前綴索引呢?還記得創(chuàng)建索引時(shí)指定的length字段嗎?

  • length:如果字段存儲(chǔ)的值過長(zhǎng),選用值的前多少個(gè)字符創(chuàng)建索引。

使用一個(gè)字段值中的前N個(gè)字符創(chuàng)建出的索引,就可以被稱為前綴索引,前綴索引能夠在很大程度上,節(jié)省索引文件的存儲(chǔ)空間,也能很大程度上提升索引的性能,這是為什么呢?后面分析索引實(shí)現(xiàn)原理的時(shí)候細(xì)聊。

2.3、功能邏輯層次

? ?相信大家在面試時(shí),如果問到了MySQL索引機(jī)制,相信一定會(huì)問如下這道面試題:

請(qǐng)回答一下你知道的MySQL索引類型。

這題的答案該怎么回答呢?其實(shí)主要就是指MySQL索引從邏輯上可以分為那些類型,以功能邏輯劃分索引類型,這也是最常見的劃分方式,從這個(gè)維度來(lái)看主要可劃分為五種:

  • 普通索引、唯一索引、主鍵索引、全文索引、空間索引

對(duì)于普通索引、唯一索引、主鍵索引都介紹過了,就不再過多闡述,但稍微提一嘴,在主鍵字段上建立的索引被稱為主鍵索引,非主鍵字段上建立的索引一般被稱為輔助索引或、二級(jí)索引或次級(jí)索引,接著重點(diǎn)聊一下全文索引和空間索引。

全文索引和空間索引都是MySQL5.7版本后開始支持的索引類型,不過這兩種索引都只有MyISAM引擎支持,其他引擎要么我沒用過,要么就由于自身實(shí)現(xiàn)的原因不支持,例如InnoDB。對(duì)于全文索引而言,其實(shí)在MySQL5.6版本中就有了,但當(dāng)時(shí)并不支持漢字檢索,到了5.7.6版本的時(shí)候才內(nèi)嵌ngram全文解析器,才支持亞洲語(yǔ)種的分詞,同時(shí)InnoDB引擎也開始支持全文索引,在5.7版本之前,只有MyISAM引擎支持。

全文索引

? ?全文索引類似于ES、Solr搜索中間件中的分詞器,或者說和之前常用的like+%模糊查詢很類似,它只能創(chuàng)建在CHAR、VARCHAR、TEXT等這些文本類型字段上,而且使用全文索引查詢時(shí),條件字符數(shù)量必須大于3才生效。當(dāng)然,還是舉個(gè)栗子才有感覺:

+------------+--------------------------------------------+------------------+
| article_id | article_name                               | special_column   |
+------------+--------------------------------------------+------------------+
|          1 | MySQL架構(gòu)篇:自頂向下深入剖析MySQL整體架構(gòu) | 《全解MySQL》    |
|          2 | MySQL執(zhí)行篇:一條SQL語(yǔ)句從誕生至結(jié)束的歷程 | 《全解MySQL》    |
|          3 | MySQL設(shè)計(jì)篇:數(shù)據(jù)庫(kù)六范式與反范式設(shè)計(jì)準(zhǔn)則!| 《全解MySQL》    |
|          4 | MySQL索引篇:索引概述、分類及建立索引的原則| 《全解MySQL》    |
+------------+--------------------------------------------+------------------+

比如現(xiàn)在用戶想要搜索一篇文章,但是忘記文章全稱了,只記得「誕生至結(jié)束」這個(gè)詞匯,此時(shí)用戶搜索這個(gè)詞匯,走全文索引的情況下,照樣能夠定位到上表中的第二條記錄。

當(dāng)然,全文索引如何創(chuàng)建與使用,待會(huì)兒后面一起列出來(lái)。

空間索引

? ?空間索引這玩意兒其實(shí)用的不多,至少大部分項(xiàng)目的業(yè)務(wù)中不會(huì)用到,想要弄清楚空間索引,那么首先得知道一個(gè)概念:GIS空間數(shù)據(jù),GIS是什么意思呢?是地理信息系統(tǒng),這是一門新的學(xué)科,基于了計(jì)算機(jī)、信息學(xué)、地理學(xué)等多科構(gòu)建的,主要就是用于管理地理信息的數(shù)據(jù)結(jié)構(gòu),在國(guó)土、規(guī)劃、出行、配送、地圖等和地理有關(guān)的項(xiàng)目中,應(yīng)用較為頻繁。

地理空間數(shù)據(jù)主要包含矢量數(shù)據(jù)、3D模型、影像文件、坐標(biāo)數(shù)據(jù)等,說簡(jiǎn)單點(diǎn),空間數(shù)據(jù)也就是可以將地理信息以模型的方式,在地圖上標(biāo)注出來(lái)。在MySQL中總共支持GEOMETRY、POINT、LINESTRING、POLYGON四種空間數(shù)據(jù)類型,而空間索引則是基于這些類型的字段建立的,也就是可以幫助我們快捷檢索空間數(shù)據(jù)。

不過對(duì)于空間索引,一般用的較少,大家了解即可。

2.4、存儲(chǔ)方式層次

? ?上面聊完了三種不同層次的索引劃分后,接著從存儲(chǔ)方式的層面再聊聊,從存儲(chǔ)方式來(lái)看,MySQL的索引主要可分為兩大類:

  • 聚簇索引:也被稱為聚集索引、簇類索引
  • 非聚簇索引:也叫非聚集索引、非簇類索引、二級(jí)索引、輔助索引、次級(jí)索引

重點(diǎn)說一說這兩類索引存儲(chǔ)方式的區(qū)別,在說之前先回憶一下數(shù)組和鏈表的區(qū)別:

  • 數(shù)組是物理空間上的連續(xù),存儲(chǔ)的所有元素都會(huì)按序存放在同一塊內(nèi)存區(qū)域中。
  • 鏈表是邏輯上的連續(xù),存儲(chǔ)的所有元素可能不在同一塊內(nèi)存,元素之間以指針連接。

為啥要說這個(gè)呢?因?yàn)榫鄞厮饕头蔷鄞厮饕膮^(qū)別也大致是相同的:

  • 聚簇索引:邏輯上連續(xù)且物理空間上的連續(xù)。
  • 非聚簇索引:邏輯上的連續(xù),物理空間上不連續(xù)。

當(dāng)然,這里的連續(xù)和數(shù)組不同,因?yàn)樗饕蟛糠侄际鞘褂?code>B+Tree結(jié)構(gòu)存儲(chǔ),所以在磁盤中數(shù)據(jù)是以樹結(jié)構(gòu)存放的,所以連續(xù)并不是指索引節(jié)點(diǎn),而是指索引數(shù)據(jù)和表數(shù)據(jù),也就是說聚簇索引中,索引數(shù)據(jù)和表數(shù)據(jù)在磁盤中的位置是一起的,而非聚簇索引則是分開的,索引節(jié)點(diǎn)和表數(shù)據(jù)之間,用物理地址的方式維護(hù)兩者的聯(lián)系。

不過一張表中只能存在一個(gè)聚簇索引,一般都會(huì)選用主鍵作為聚簇索引,其他字段上建立的索引都屬于非聚簇索引,或者稱之為輔助索引、次級(jí)索引。但也不要走進(jìn)一個(gè)誤區(qū),雖然MySQL默認(rèn)會(huì)使用主鍵上建立的索引作為聚簇索引,但也可以指定其他字段上的索引為聚簇索引,一般聚簇索引要求索引必須是非空唯一索引才行。

其實(shí)就算表中沒有定義主鍵,InnoDB中會(huì)選擇一個(gè)唯一的非空索引作為聚簇索引,但如果非空唯一索引也不存在,InnoDB隱式定義一個(gè)主鍵來(lái)作為聚簇索引。

當(dāng)然,主鍵或者說聚簇索引,一般適合采用帶有自增性的順序值。

對(duì)于聚簇、非聚簇索引的區(qū)別、兩者的查找過程、隱式主鍵、為何主鍵適合自增值等這些問題,在后續(xù)的《索引原理篇》中會(huì)詳細(xì)講解。

2.5、索引分類小結(jié)

? ?至此,對(duì)于MySQL“多樣化”的索引機(jī)制,一大堆索引名詞,就已經(jīng)梳理清楚啦!相信到這里為止,大家也對(duì)MySQL的索引機(jī)制有了系統(tǒng)化的認(rèn)知,其實(shí)最開始給出的一大堆索引名詞,只是從不同角度劃分出來(lái)的,在上述中分別從數(shù)據(jù)結(jié)構(gòu)、字段數(shù)量、功能邏輯以及存儲(chǔ)方式多個(gè)層面進(jìn)行了描述。當(dāng)然,要牢記的是,以功能邏輯的層次來(lái)劃分索引,這也是最常用的方式。

三、MySQL其他索引的創(chuàng)建使用方式

? ?前面的案例中,聊到了咱們有三種方式創(chuàng)建索引,在創(chuàng)建時(shí)可通過INDEX、KEY兩個(gè)關(guān)鍵字創(chuàng)建,但這種方式建立的索引僅是普通索引,接著再來(lái)聊一聊MySQL數(shù)據(jù)庫(kù)其他類型的索引該如何創(chuàng)建以及使用。

但不管是何種類型的索引,都可以通過前面聊到的三種方式創(chuàng)建。

3.1、唯一索引的創(chuàng)建與使用

? ?唯一索引在創(chuàng)建時(shí),需要通過UNIQUE關(guān)鍵字創(chuàng)建:如下:

-- 方式①
CREATE UNIQUE INDEX indexName ON tableName (columnName(length));

-- 方式②
ALTER TABLE tableName ADD UNIQUE INDEX indexName(columnName);

-- 方式③
CREATE TABLE tableName(  
  columnName1 INT(8) NOT NULL,   
  columnName2 ....,
  .....,
  UNIQUE INDEX [indexName] (columnName(length))  
);

在已有的表基礎(chǔ)上創(chuàng)建唯一索引時(shí)要注意,如果選用的字段,表中字段的值存在相同值時(shí),這時(shí)唯一索引是無(wú)法創(chuàng)建的,比如:

SELECT * FROM `zz_article`;
+------------+--------------------------+-------------------+
| article_id | article_name             | special_column    |
+------------+--------------------------+-------------------+
|          1 | MySQL架構(gòu)篇:.......     | 《全解MySQL》     |
|          2 | MySQL執(zhí)行篇:.......     | 《全解MySQL》     |
|          3 | MySQL設(shè)計(jì)篇:.......     | 《全解MySQL》     |
|          4 | MySQL索引篇:.......     | 《全解MySQL》     |
|          5 | MySQL索引篇:.......     | 《全解MySQL》     |
+------------+--------------------------+-------------------+

CREATE UNIQUE INDEX i_article_name ON zz_article (article_name);

比如上述文章表中,第4、5條數(shù)據(jù)是重復(fù)的,此時(shí)創(chuàng)建利用SQL語(yǔ)句創(chuàng)建唯一索引,就會(huì)拋出1062錯(cuò)誤碼:

ERROR 1062 (23000): Duplicate entry 'MySQL索引篇:.......' for key 'i_article_name'

在這種情況下,就只能先刪除重復(fù)數(shù)據(jù),然后才能創(chuàng)建唯一索引成功。

同時(shí),當(dāng)唯一索引創(chuàng)建成功后,它同時(shí)會(huì)對(duì)表具備唯一約束的作用,當(dāng)再使用INSERT語(yǔ)句插入相同值時(shí),會(huì)同樣會(huì)拋出1062錯(cuò)誤碼:

INSERT INTO `zz_article` VALUES(6,"MySQL索引篇:.......","《全解MySQL》");

1062 - Duplicate entry 'MySQL索引篇:.......' for key 'i_article_name'

這里會(huì)提示你插入的哪個(gè)值,已經(jīng)在表中存在,因此無(wú)法插入當(dāng)前這條數(shù)據(jù)。

3.2、主鍵索引的創(chuàng)建與使用

? ?前面聊到過,主鍵索引其實(shí)是一種特殊的唯一索引,但主鍵索引卻并不是通過UNIQUE關(guān)鍵字創(chuàng)建的,而是通過PRIMARY關(guān)鍵字創(chuàng)建:

-- 方式①
ALTER TABLE tableName ADD PRIMARY KEY indexName(columnName);

-- 方式②
CREATE TABLE tableName(  
  columnName1 INT(8) NOT NULL,   
  columnName2 ....,
  .....,
  PRIMARY KEY [indexName] (columnName(length))  
);

在這里要注意:

  • 創(chuàng)建主鍵索引時(shí),必須要將索引字段先設(shè)為主鍵,否則會(huì)拋1068錯(cuò)誤碼。
  • 這里也不能使用CREATE語(yǔ)句創(chuàng)建索引,否則會(huì)提示1064語(yǔ)法錯(cuò)誤。
  • 同時(shí)創(chuàng)建索引時(shí),關(guān)鍵字要換成KEY,并非INDEX,否則也會(huì)提示語(yǔ)法錯(cuò)誤。

還是以之前的文章表為例,如下:

-- 對(duì)非主鍵字段創(chuàng)建主鍵索引
ALTER TABLE zz_article ADD PRIMARY KEY i_special_column(special_column);
-- 報(bào)錯(cuò)信息如下:
1068 - Multiple primary key defined

-- 使用CREATE關(guān)鍵字創(chuàng)建主鍵索引
CREATE PRIMARY KEY i_article_id ON zz_article (article_id);
-- 報(bào)錯(cuò)信息如下:
1064 - You have an error in your SQL syntax; check....

-- 使用INDEX關(guān)鍵字創(chuàng)建索引
ALTER TABLE zz_article ADD PRIMARY INDEX i_article_id(article_id);
-- 報(bào)錯(cuò)信息如下:
1064 - You have an error in your SQL syntax; check....

-- 創(chuàng)建主鍵索引正確的方式
ALTER TABLE zz_article ADD PRIMARY KEY i_article_id(article_id);

當(dāng)然,一般主鍵索引都會(huì)在建表的DDL語(yǔ)句中創(chuàng)建,不會(huì)在表已經(jīng)建立后再創(chuàng)建。

但似乎無(wú)論在講普通索引,還是唯一索引、主鍵索引的時(shí)候,我們都沒有講如何使用這些創(chuàng)建好的索引查詢數(shù)據(jù),其實(shí)這一點(diǎn)無(wú)需咱們考慮,參考之前《SQL執(zhí)行篇》中查詢語(yǔ)句的執(zhí)行流程,在一條SELECT語(yǔ)句來(lái)到MySQL時(shí),會(huì)經(jīng)歷優(yōu)化器優(yōu)化的過程,而優(yōu)化器則會(huì)自動(dòng)幫咱們選擇一個(gè)最合適的索引查詢數(shù)據(jù)。當(dāng)然,前提是查詢條件中涉及到了索引字段才行。

前面也說過,你不想讓優(yōu)化器自動(dòng)選擇,也可以手動(dòng)通過FORCE INDEX關(guān)鍵字強(qiáng)制指定。

3.3、全文索引的創(chuàng)建與使用

? ?全文索引和其他索引不同,首先如果你想要?jiǎng)?chuàng)建全文索引,那么MySQL版本必須要在5.7及以上,同時(shí)使用時(shí)也需要手動(dòng)指定,一起來(lái)先看看如何創(chuàng)建全文索引,此時(shí)需要使用FULLTEXT關(guān)鍵字:

-- 方式①
ALTER TABLE tableName ADD FULLTEXT INDEX indexName(columnName);

-- 方式②
CREATE FULLTEXT INDEX indexName ON tableName(columnName);

不過在創(chuàng)建全文索引時(shí),有三個(gè)注意點(diǎn):

  • 5.6版本的MySQL中,存儲(chǔ)引擎必須為MyISAM才能創(chuàng)建。
  • 創(chuàng)建全文索引的字段,其類型必須要為CHAR、VARCHAR、TEXT等文本類型。
  • 如果想要?jiǎng)?chuàng)建出的全文索引支持中文,需要在最后指定解析器:with parser ngram

此時(shí)還依舊是以文章表為例,為文章名稱字段創(chuàng)建一個(gè)全文索引,命令如下:

ALTER TABLE 
    zz_article ADD 
FULLTEXT INDEX 
    ft_article_name(article_name) 
WITH PARSER NGRAM;

創(chuàng)建好全文索引后,當(dāng)你想要使用全文索引時(shí),優(yōu)化器這時(shí)不能自動(dòng)選擇,因?yàn)槿乃饕凶约旱恼Z(yǔ)法,但在了解如何使用之前,得先清楚兩個(gè)概念:最小搜索長(zhǎng)度和最大搜索長(zhǎng)度,先來(lái)看看全文索引的一些參數(shù),可通過show variables like '%ft%';命令查詢,如下:
[圖片上傳失敗...(image-d00ebc-1670662044138)]
多余的參數(shù)就不介紹了,重點(diǎn)講一下其中的幾個(gè)重要參數(shù):

  • ft_min_word_len:使用MyISAM引擎的表中,全文索引最小搜索長(zhǎng)度。
  • ft_max_word_len:使用MyISAM引擎的表中,全文索引最大搜索長(zhǎng)度。
  • ft_query_expansion_limitMyISAM中使用with query expansion搜索的最大匹配數(shù)。
  • innodb_ft_min_token_sizeInnoDB引擎的表中,全文索引最小搜索長(zhǎng)度。
  • innodb_ft_max_token_sizeInnoDB引擎的表中,全文索引最大搜索長(zhǎng)度。

那么究竟做最小搜索長(zhǎng)度、最大搜索長(zhǎng)度的作用是什么呢?其實(shí)這個(gè)是一個(gè)限制,對(duì)于長(zhǎng)度小于最小搜索長(zhǎng)度和大于最大搜索長(zhǎng)度的詞語(yǔ),都無(wú)法觸發(fā)全文索引。也就是說,如果想要使用全文索引對(duì)一個(gè)詞語(yǔ)進(jìn)行搜索,那這個(gè)詞語(yǔ)的長(zhǎng)度必須在這兩個(gè)值之間。

其實(shí)這兩個(gè)值自己可以手動(dòng)調(diào)整的,最小值可以手動(dòng)調(diào)整為1MyISAM引擎的最大值可以調(diào)整為3600,但InnoDB引擎最大似乎就是84

OK~,了解全文索引中的一些概念后,接下來(lái)看看如何使用全文索引,全文索引中有兩個(gè)專門用于檢索的關(guān)鍵字,即MATCH(column)、AGAINST(關(guān)鍵字),同時(shí)這兩個(gè)檢索函數(shù)也支持三種搜索模式:

  • 自然語(yǔ)言模式(默認(rèn)搜索模式)
  • 布爾搜索模式
  • 查詢拓展搜索

MATCH()主要是負(fù)責(zé)指定要搜索的列,這里要指定創(chuàng)建全文索引的字段,AGAINST()則指定要搜索的關(guān)鍵字,也就是要搜索的詞語(yǔ),接下來(lái)簡(jiǎn)單的講一下三種搜索模式。

自然語(yǔ)言模式

這種模式也是在使用全文索引時(shí),默認(rèn)的搜索模式,使用方法如下:

+------------+--------------------------+-------------------+
| article_id | article_name             | special_column    |
+------------+--------------------------+-------------------+
|          1 | MySQL架構(gòu)篇:.......     | 《全解MySQL》     |
|          2 | MySQL執(zhí)行篇:.......     | 《全解MySQL》     |
|          3 | MySQL設(shè)計(jì)篇:.......     | 《全解MySQL》     |
|          4 | MySQL索引篇:.......     | 《全解MySQL》     |
+------------+--------------------------+-------------------+

SELECT 
    COUNT(article_id) AS '搜索結(jié)果數(shù)量' 
FROM 
    `zz_article` 
WHERE 
    MATCH(article_name) AGAINST('MySQL');

-- 運(yùn)行結(jié)果如下:
+--------------+
| 搜索結(jié)果數(shù)量 |
+--------------+
|           4 |
+--------------+

一眼看過去,SQL就能看懂,畢竟都可以排版了一下SQL,不過多介紹了。唯一要注意的是,如果給定的關(guān)鍵詞長(zhǎng)度小于默認(rèn)的最小搜索長(zhǎng)度,那是無(wú)法使用全文索引的,比如下述這條SQL就不會(huì)觸發(fā):

SELECT 
    COUNT(article_id) AS '搜索結(jié)果數(shù)量' 
FROM 
    `zz_article` 
WHERE 
    MATCH(article_name) AGAINST('M');

布爾搜索模式

布爾搜索模式有些特殊,因?yàn)樵谶@種搜索模式中,還需要掌握特定的搜索語(yǔ)法:

  • +:表示必須匹配的行數(shù)據(jù)必須要包含相應(yīng)關(guān)鍵字。
  • -:和上面的+相反,表示匹配的數(shù)據(jù)不能包含相應(yīng)的關(guān)鍵字。
  • >:提升指定關(guān)鍵字的相關(guān)性,在查詢結(jié)果中靠前顯示。
  • <:降低指定關(guān)鍵字的相關(guān)性,在查詢結(jié)果中靠后顯示。
  • ~:表示允許出現(xiàn)指定關(guān)鍵字,但出現(xiàn)時(shí)相關(guān)性為負(fù)。
  • *:表示以該關(guān)鍵字開頭的詞語(yǔ),如A*,可以匹配A、AB、ABC....
  • "":雙引號(hào)中的關(guān)鍵字作為整體,檢索時(shí)不允許再分詞。
  • "X Y"@n""包含的多個(gè)詞語(yǔ)之間的距離必須要在n之間,單位-字節(jié),如:
    • 竹子 熊貓@10:表示竹子和熊貓兩個(gè)詞語(yǔ)之間的距離要在10字節(jié)內(nèi)。
  • .......

舉個(gè)幾個(gè)例子使用一下,如下:

-- 查詢文章名中包含 [MySQL] 但不包含 [設(shè)計(jì)] 的數(shù)據(jù)
SELECT 
    *
FROM 
    `zz_article` 
WHERE 
    MATCH(article_name) AGAINST('+MySQL -設(shè)計(jì)' IN BOOLEAN MODE);

-- 查詢文章名中包含 [MySQL] 和 [篇] 的數(shù)據(jù),但兩者間的距離不能超過10字節(jié)
SELECT 
    *
FROM 
    `zz_article` 
WHERE 
    MATCH(article_name) AGAINST('"MySQL 篇"@10' IN BOOLEAN MODE);
    
-- 查詢文章名中包含[MySQL] 的數(shù)據(jù),
--    但包含 [執(zhí)行] 關(guān)鍵字的行相關(guān)性要高于包含 [索引] 關(guān)鍵字的行數(shù)據(jù)
SELECT 
    *
FROM 
    `zz_article` 
WHERE 
    MATCH(article_name) AGAINST('+MySQL +(>執(zhí)行 <索引)' IN BOOLEAN MODE);

-- 查詢文章名中包含 [MySQL] 的數(shù)據(jù),但包含 [設(shè)計(jì)] 時(shí)則將相關(guān)性降為負(fù)
SELECT 
    *
FROM 
    `zz_article` 
WHERE 
    MATCH(article_name) AGAINST('+MySQL ~設(shè)計(jì)' IN BOOLEAN MODE);

-- 查詢文章名中包含 [執(zhí)行] 關(guān)鍵字的行數(shù)據(jù)
SELECT 
    *
FROM 
    `zz_article` 
WHERE 
    MATCH(article_name) AGAINST('執(zhí)行*' IN BOOLEAN MODE);

-- 查詢文章名中必須要包含 [MySQL架構(gòu)篇] 關(guān)鍵字的數(shù)據(jù)
SELECT 
    *
FROM 
    `zz_article` 
WHERE 
    MATCH(article_name) AGAINST('"MySQL架構(gòu)篇"' IN BOOLEAN MODE);

同樣的,上述的SQL語(yǔ)句應(yīng)該都能看明白,最后的IN BOOLEAN MODE表示使用布爾搜索模式,除此外,大家唯一疑惑的就在于:相關(guān)性這個(gè)詞,其實(shí)這個(gè)詞也不難理解,就是檢索數(shù)據(jù)后,數(shù)據(jù)的優(yōu)先級(jí)順序,當(dāng)相關(guān)性越高,對(duì)應(yīng)數(shù)據(jù)在結(jié)果中越靠前,當(dāng)相關(guān)性為負(fù),則相應(yīng)的數(shù)據(jù)排到最后。

查詢拓展搜索

查詢拓展搜索其實(shí)是對(duì)自然語(yǔ)言搜索模式的拓展,比如舉個(gè)例子:

SELECT 
    COUNT(article_id) AS '搜索結(jié)果數(shù)量' 
FROM 
    `zz_article` 
WHERE 
    MATCH(article_name) AGAINST('MySQL' WITH QUERY EXPANSION);

在自然語(yǔ)言模式的查詢語(yǔ)句基礎(chǔ)上,最后面多加一個(gè)WITH QUERY EXPANSION表示使用查詢拓展搜索,這種模式下會(huì)比自然語(yǔ)言模式多一次檢索過程,比如上述的例子中:

  • 首先會(huì)根據(jù)指定的關(guān)鍵字MySQL進(jìn)行一次全文檢索。
  • 然后第二階段還會(huì)對(duì)指定的關(guān)鍵進(jìn)行分詞,然后再進(jìn)行一次全文檢索。

之前介紹全文索引參數(shù)時(shí),也列出來(lái)了一個(gè)名為ft_query_expansion_limit的參數(shù),這個(gè)參數(shù)就是控制拓展搜索時(shí)的拓展行數(shù)的,最大可以調(diào)整到1000。但由于Query Expansion的全文檢索可能帶來(lái)許多非相關(guān)性的查詢結(jié)果,因此在實(shí)際情況中要慎用!!!

實(shí)際上,全文索引引入MySQL后,可以用它代替之前的like%模糊查詢,效率會(huì)更高。

3.4、空間索引的創(chuàng)建與使用

? ?空間索引這玩意兒實(shí)際上很多項(xiàng)目不會(huì)用到,我用的次數(shù)也不多,但如果你要用到這個(gè)索引,那可以通過SPATIAL關(guān)鍵字創(chuàng)建,如下:

ALTER TABLE tableName ADD SPATIAL KEY indexName(columnName);

但在創(chuàng)建空間索引的時(shí)候,有幾個(gè)注意點(diǎn)需要牢記:

  • 目前MySQL常用引擎中,僅有MyISAM支持空間索引,所以表引擎必須要為它。
  • 空間索引必須要建立在類型為GEOMETRY、POINT、LINESTRING、POLYGON的字段上。

這個(gè)用的較少,就不展開細(xì)聊了~

3.5、聯(lián)合索引的創(chuàng)建與使用

? ?聯(lián)合索引呢,實(shí)際上并不是一種邏輯索引分類,它是索引的一種特殊結(jié)構(gòu),前面給出的所有案例中,都僅僅是在單個(gè)字段的基礎(chǔ)上建立索引,而聯(lián)合索引的意思是可以使用多個(gè)字段建立索引。那該如何創(chuàng)建聯(lián)合索引呢,不需要特殊的關(guān)鍵字,方法如下:

CREATE INDEX indexName ON tableName (column1(length),column2...);
ALTER TABLE tableName ADD INDEX indexName(column1(length),column2...);
  • 你可以使用INDEX關(guān)鍵字,讓多個(gè)列組成一個(gè)普通聯(lián)合索引
  • 也可以使用UNIQUE INDEX關(guān)鍵字,讓多個(gè)列組成一個(gè)唯一聯(lián)合索引
  • 甚至還可以使用FULLTEXT INDEX關(guān)鍵字,讓多個(gè)列組成一個(gè)全文聯(lián)合索引
  • .......

但是前面也提過,SELECT語(yǔ)句的查詢條件中,必須包含組成聯(lián)合索引的第一個(gè)字段,此時(shí)才會(huì)觸發(fā)聯(lián)合索引,否則是無(wú)法使用聯(lián)合索引的。

四、索引初識(shí)篇總結(jié)

? ?OK~,在本篇中就對(duì)MySQL的索引機(jī)制有了全面認(rèn)知,從索引的由來(lái),到索引概述、索引管理、索引分類、唯一/全文/聯(lián)合/空間索引的創(chuàng)建與使用等內(nèi)容,進(jìn)行了全面概述,相信本章看下來(lái),足夠讓你對(duì)MySQL索引機(jī)制有一個(gè)系統(tǒng)化的體系,那么我們下篇再見。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,224評(píng)論 6 529
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,916評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,014評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,466評(píng)論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,245評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,795評(píng)論 1 320
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,869評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,010評(píng)論 0 285
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,524評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,487評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,634評(píng)論 1 366
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,173評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,884評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,282評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,541評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,236評(píng)論 3 388
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,623評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容