MySQL索引


 

前言

因為現在使用的mysql默認存儲引擎是Innodb,所以本篇文章重點講述Innodb下的索引, 順帶簡單講述其他引擎。希望小伙伴們能通過這片文章對mysql的索引有更加清晰的認識,廢話不多說,我們開始吧。

索引介紹

首先,我們先帶着一些問題來看接下來的內容。

  • 索引是個什么東西?
  • 我們可以創建哪些索引?
  • 哪些字段適合建立索引呢?
  • 索引是不是越多越好呢?
  • 為什么我們不建議使用uuid、身份證號等數據做為主鍵?
  • 為什么不建議使用select * from table?
  • 我們使用模糊匹配 ’%三‘ ’張%‘ 在前在后會影響索引的使用嗎?

上面的問題我們大家可能都存在或者部分存在疑惑,接下來就是解惑的時間。

什么是索引

在關系數據庫中,索引是一種單獨的、物理的對數據庫表中一列或多列的值進行排序的一種存儲結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。索引的作用相當於圖書的目錄,可以根據目錄中的頁碼快速找到所需的內容。

 

 

 

mysql中索引有哪些類型

普通索引

通索引是mysql里最基本的索引,沒有什么特殊性,在任何一列上都能進行創建。

-- 創建索引的基本語法
CREATE INDEX indexName ON table(column(length));
-- 例子 length默認我們可以忽略
CREATE INDEX idx_name ON user(name);

主鍵索引

我們知道每張表一般都會有自己的主鍵,mysql會在主鍵上建立一個索引,這就是主鍵索引。主鍵是具有唯一性並且不允許為NULL,所以他是一種特殊的唯一索引。一般在建立表的時候選定。

復合索引

復合索引也叫組合索引,指的是我們在建立索引的時候使用多個字段,例如同時使用身份證和手機號建立索引,同樣的可以建立為普通索引或者是唯一索引。

復合索引的使用復合最左原則。舉個例子 我們使用 phone和name創建索引。

-- 創建索引的基本語法
CREATE  INDEX indexName ON table(column1(length),column2(length));
-- 例子 
CREATE INDEX idx_phone_name ON user(phone,name);

我們看下面的查詢語句,

SELECT * FROM user_innodb where name = '程馮馮';
SELECT * FROM user_innodb where phone = '15100046637';
SELECT * FROM user_innodb where phone = '15100046637' and name = '程馮馮';
SELECT * FROM user_innodb where name = '程馮馮' and phone = '15100046637';

三條sql只有 2 、 3、4能使用的到索引idx_phone_name,因為條件里面必須包含索引前面的字段才能夠進行匹配。而3和4相比where條件的順序不一樣,為什么4可以用到索引呢?是因為mysql本身就有一層sql優化,他會根據sql來識別出來該用哪個索引,我們可以理解為3和4在mysql眼中是等價的。

全文索引

全文索引主要用來查找文本中的關鍵字,而不是直接與索引中的值相比較。fulltext索引跟其它索引大不相同,它更像是一個搜索引擎,而不是簡單的where語句的參數匹配。fulltext索引配合match against操作使用,而不是一般的where語句加like。

它可以在create table,alter table ,create index使用,不過目前只有char、varchar,text 列上可以創建全文索引。正常情況下我們也不會使用到全文索引,因為這不是mysql的專長。

空間索引

空間索引是對空間數據類型的字段建立的索引,MYSQL中的空間數據類型有4種,分別是GEOMETRY、POINT、LINESTRING、POLYGON。MYSQL使用SPATIAL關鍵字進行擴展,使得能夠用於創建正規索引類型的語法創建空間索引。

創建空間索引的列,必須將其聲明為NOT NULL,空間索引只能在存儲引擎為MYISAM的表中創建。空間索引一般是用不到了,了解即可。

索引的數據結構

B+Tree

innodb默認索引數據結構是B+Tree,什么是B+Tree呢,它的全名叫做平衡多路查找樹PLUS。他是由平衡二叉樹查找樹(AVL樹)演化而來。我們來介紹一下他的演化史(敲黑板,必考題)。

我們上面講到,索引是一種有序的數據結構,因為有序才能快速的進行查找,所以我們一步步看一下索引的定型演化,首先我們講一下什么是二叉查找樹。

二叉查找樹(Binary Search Trees)

二叉樹查找樹具有以下性質:左子樹的鍵值小於根的鍵值,右子樹的鍵值大於根的鍵值。

 

 

 節點的順序就是11、25、36、80、110、120、300。他的問題是不夠穩定,上圖我們看到了這是最好的一種情況,插入順序是80、25、11、36、120、110、300,但是如果我們的插入順序變成11、25、36、80、110、120、300,那么他的樹結構會變成下圖這樣。

 

 

 

上圖好好的一個二叉樹變成了一個鏈表。之前我們查找到300需要3次查詢,后者則需要7次效率是直線下降。

這里大家可以去這個網址Data StructureVisualizations自己去操作下這個流程。

https://www.cs.usfca.edu/~galles/visualization/Algorithms.html 

 

 

那么如何解決掉這種不平衡的問題呢?

AVL Trees (Balanced binary search trees)

這個時候平衡二叉查找樹出現了。什么是AVL樹,在計算機科學中,AVL樹是最先發明的自平衡二叉查找樹。在AVL樹中任何節點的兩個子樹的高度最大差別為1,所以它也被稱為高度平衡樹。增加和刪除可能需要通過一次或多次樹旋轉來重新平衡這個樹。

AVL樹得名於它的發明者G. M. Adelson-Velsky和E. M. Landis,名字已拼接AVL樹的大名就出來了。我們下面看下avl按照11、25、36、80、110、120、300順序進行插入的效果圖。

 

 

當子樹的高度超過1時他會通過自旋的方式重新平衡樹,所以這樣我們查詢數據的時間復雜度就穩定了。有關avl樹是怎樣進行旋轉平衡的這里就不概述了。

那么,我們使用AVL樹作為索引是不是就可以了呢,答案是否定的。我們的索引是存儲到磁盤上的,每次進行數據查詢會將磁盤里的數據讀取到內存中,對磁盤io是非常耗時的,而內存操作非常快。計算機的最小存儲單元是塊(block)默認4k大小,讀取數據是一塊一塊讀取的,而不是隨意的讀取1/2塊數據,對應的我們mysql存儲數據也是已頁(page)為單位進行存儲,默認為16K(16384B),mysql在讀取的時候也是一頁一頁讀取的。

--下面的這個命令就是查詢page大小
MYSQL> show variables like 'innodb_page_size';



 

 

如果使用AVL樹,我們的一個節點就是一頁,但是一個節點是16k啊兄弟們,一頁就放一個節點肯定是太浪費空間了,而且如果有1000w的數據,那么二叉樹深度是55,我們要查找一個數據io的次數就有點太多了,顯然這樣是不合理的,我們可以怎么做呢?

B-Tree(讀作 b樹 不是b減樹)

為了解決AVL浪費磁盤空間以及IO次數過多的問題,我們在一個節點中多存儲一些數據,之前我們放一個,現在我們放多個。如果放int值(4B)我們近乎可以放4096個值,當然索引里面還包含其他的數據,不能夠放這么多,但是這也是足夠的多了。

這樣一個節點的值多了那么樹的分叉肯定就多了,假如一個節點可以存儲1000的值,那么1000 * 1000 * 1000 = 10億節點,3層的結構就能存儲10億的數據,這樣是不是最多IO3次就足夠了呢。

所以AVL的進化體B-Tree出現了,B-Tree的全名是多路平衡查找樹,B-tree中,每個結點包含:

  • 本結點所含關鍵字的個數;
  • 指向父結點的指針;
  • 關鍵字;
  • 指向子結點的指針;

對於一棵m階B-tree,每個結點至多可以擁有m個子結點。各結點的關鍵字和可以擁有的子結點數都有限制,規定m階B-tree中,根結點至少有2個子結點,除非根結點為葉子節點,相應的,根結點中關鍵字的個數為1~m-1 ;非根結點至少有[m/2]([],向上取整)個子結點,相應的,關鍵字個數為[m/2]-1 ~ m-1。

 

 

B-Tree的度是可以設置的,上面截圖我設置的度為3(達到3即進行分裂),真正索引度就比較大了,一般度的大小會根據索引列的類型進行變更。大家利用好這個網站Data StructureVisualizations,自己多做一些模擬會理解的更加深刻。

說到這里我們越來越接近真相了,我們mysql索引的數據結構到底是不是B-Tree呢?

這就需要說道mysql設計的另外一個概念了——聚集索引和輔助索引。

聚集索引和輔助索引(非聚集索引)

什么是聚集索引(clustered index organize table ),聚集索引中鍵值的邏輯順序和表中相應行的物理順序相同。

聚集索引類似於電話簿,后者按姓氏排列數據。由於聚集索引規定數據在表中的物理存儲順序,因此一個表只能包含一個聚集索引。但該索引可以包含多個列(聯合索引),就像電話簿按姓氏和名字進行組織一樣,但是在innodb的設計中聚集索引包含整行的數據,所以innodb中索引就是數據本身,這就是大家常說的索引即數據。

官方解釋聚集索引:

Every InnoDB table has a special index called the clustered index where the data for the rows is stored. Typically, the clustered index is synonymous with the primary key.

每個InnoDB表都有一個特殊的索引,稱為聚簇索引 ,用於存儲行數據。通常,聚簇索引與主鍵同義 。

非聚集索引的話其實就是一個普通索引,但是非聚集索引不存儲全部數據,只存儲聚集索引的值(一般為主鍵id)。

所以我們如果使用B-Tree來作為索引結構的話,如果數據行過大,那么一個頁存儲的數據就會大大減少,這就違背了我們B-Tree的初衷了——在一個頁中盡可能的存儲多的數據。像前面說的如果我們存儲int類型可以存儲幾千個,那么如果我們存儲整行數據呢,可能只能存儲三四個,那么樹的深度就會大大增加,而且我們的內存空間是有限的,每次mysql預讀進來的索引數量有限,這進一步導致搜索效率變差。

所以我們想要的索引就是只包含索引字段,不應該包含全部的數據 ,看下面的對比圖。

 

 

好了,該主角出場了。

B+Tree

為了解決只存儲索引的問題,B-Tree的plus版本橫空出世,那就是B+樹。

B+ 樹是一種樹數據結構,是一個n叉樹,每個節點通常有多個孩子,一顆B+樹包含根節點、內部節點和葉子節點,和B-Tree幾乎一樣,只不過B+Tree不再包含整行的數據了。B+ 樹通常用於數據庫和操作系統的文件系統中。B+ 樹的特點是能夠保持數據穩定有序,其插入與修改擁有較穩定的對數時間復雜度。B+ 樹元素自底向上插入。

一個m階的B樹具有如下幾個特征:

  1. 根結點至少有兩個子女。
  2. 每個中間節點都至少包含ceil(m / 2)個孩子,最多有m個孩子。
  3. 每一個葉子節點都包含k-1個元素,其中 m/2 <= k <= m。
  4. 所有的葉子結點都位於同一層。
  5. 每個節點中的元素從小到大排列,節點當中k-1個元素正好是k個孩子包含的元素的值域分划。

下面是一個簡單的展示圖,讓大家了解B+Tree的數據結構。相對於B-Tree最大的變化有三點:

  1. 數據下移,所有的非葉子節點不再存儲數據而將數據全部存儲到葉子節點。
  2. 所有的葉子節點都有一個雙向的指針,做了一個雙向鏈表
  3. 使用B+Tree查詢次數相對固定,因為數據都在葉子節點,每一個層級都會被加載掃描。

 

 

還有一點為什么使用B+Tree呢,因為mysql查詢路徑的選擇是根據cost(cost = cpu cost + io cost)計算的,因為索引的查詢次數固定,所以io cost計算中他就可以直接舍去了,減輕了myslq的計算量。具體cost的計算不在本篇文章展開。

  • cpu cost:server層對返回的記錄數的compare時間。
  • io cost:引擎層根據掃描記錄的記錄數計算cost。

另外需要補充的一點,我們已經了解到了innodb引擎中數據和索引是在一起的,而myisam引擎數據和索引是分開的,這個我們可以直接查看本地文件可以看到。

MYSQL> show variables like 'datadir';

上面的命令可以讓你看到mysql的庫文件存儲位置。

 

 

以我本機為例,user_innodb表的存儲引擎是innodb,他有兩個文件.frm(表描述文件)和.ibd(索引和數據文件).

user_myisam表的存儲引擎是myisam,他會有三個文件.MYD(數據文件)、.MYI(索引文件)和.frm(表描述文件)。MYD其中D就是data的意思I就是index的意思這樣就記住了。ibd猜測下 index + B+Tree + data…。

MYSIAM引擎的索引文件持有的是數據文件的地址引用。

MYSIAM和Innodb的索引區別:

  • innodb數據和索引在一起(數據即索引,索引即數據),而mysiam是分開存儲的
  • innodb索引是有主次的,也就是區分聚集索引和非聚集索引。而mysiam是不區分主次的。

非聚集索引是怎么查找數據的

上面我們已經了解了聚集索引(一般是主鍵索引)是如何獲取的,那非聚集索引呢?下面我們看一張圖。

 

 

從這個圖我們就可以直觀的看到,非聚集索引是怎么查詢數據的。每次查非聚集索引都會再次通過主鍵再次去聚集索引里面查詢。

這里我們再引申出一個概念那就是回表,我們上圖所描述的流程就是回表。回表的原因是我們需要獲取的是整行或者是包含非索引字段的數據,因非聚集索引沒有該字段所以需要回表查詢。

因此我們建議盡量少用SELECT * FROM TABLE,例如我們查詢SELECT * FROM USER WHERE name LIKE '張%',但是我們其實想要的只是名字的集合而已,那么我們就可以改造成SELECT name FROM USER WHERE name LIKE '張%',前者會回表查詢而后者不會,這應就減少了數據查詢的時間同時也減少了數據庫的壓力。

HASH索引

Hash索引就是將索引字段進行hash存儲,整個hash索引的結構是Hash表+鏈表(因為會存在hash沖突)。

不知道大家有沒有碰到過這么一種情況,我們在給數據庫創建索引的時候選擇了HASH但是創建完成后會默認的給我們改成B+Tree索引!沒碰到的小伙伴自己去試一下看看是不是這樣。

翻了一下官網找到這么一個圖。

 

 InnoDB和MyISAM竟然不支持創建HASH索引。

 

 

行了,這下次一巴掌打的腦瓜子嗡嗡的,只有MEMORY/NDB才能夠創建Hash索引。

那InnoDB里有Hash索引嗎?

Hash索引在InnoDB中的使用

在官網的InnoDB架構中有這么一張圖。

 

 

 在我們Buffer Pool中有個Adaptive Hash Index(自適應hash索引)。官網是這么介紹的。

The adaptive hash index feature enables InnoDB to perform more like an in-memory database on systems with appropriate combinations of workload and sufficient memory for the buffer pool without sacrificing transactional features or reliability. The adaptive hash index feature is enabled by the innodb_adaptive_hash_index variable, or turned off at server startup by --skip-innodb-adaptive-hash-index. Based on the observed pattern of searches, a hash index is built using a prefix of the index key. The prefix can be any length, and it may be that only some values in the B-tree appear in the hash index. Hash indexes are built on demand for the pages of the index that are accessed often. If a table fits almost entirely in main memory, a hash index can speed up queries by enabling direct lookup of any element, turning the index value into a sort of pointer. InnoDB has a mechanism that monitors index searches. If InnoDB notices that queries could benefit from building a hash index, it does so automatically. With some workloads, the speedup from hash index lookups greatly outweighs the extra work to monitor index lookups and maintain the hash index structure. Access to the adaptive hash index can sometimes become a source of contention under heavy workloads, such as multiple concurrent joins. Queries with LIKE operators and % wildcards also tend not to benefit. For workloads that do not benefit from the adaptive hash index feature, turning it off reduces unnecessary performance overhead. Because it is difficult to predict in advance whether the adaptive hash index feature is appropriate for a particular system and workload, consider running benchmarks with it enabled and disabled. Architectural changes in MySQL 5.6 make it more suitable to disable the adaptive hash index feature than in earlier releases.

他說自適應哈希索引可以在InnoDB不犧牲事務功能或可靠性的情況下創建,但是他的使用范圍就是Buffer Pool,那么最終這個hash索引仍然只是一個內存索引。而我們B+Tree索引是存儲在磁盤的,一般只有跟節點常駐內存。推薦:250期面試題匯總

是否使用自適應hash索引由參數innodb_adaptive_hash_index控制,具體:

https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_adaptive_hash_index

Hash索引的優缺點

由於Hash是基於內存的索引,那么他的檢索效率是非常快的,那既然Hash索引效率這個高,我們是不是都需用Hash索引啊。

我覺得hash索引的優點只有一個,那就是快,不需要磁盤io,直接內存一次性搞定。但是要說他的缺點可真的是太多了。

  • Hash索引僅僅能滿足"=",“IN"和”<=>"查詢,不能使用范圍查詢。  哈希索引只支持等值比較查詢,包括=、 IN 、<=> (注意<>和<=>是不同的操作)。也不支持任何范圍查詢,例如WHERE price > 100。
  • 由於Hash索引比較的是進行Hash運算之后的Hash值 ,所以它只能用於等值的過濾,不能用於基於范圍的過濾,因為經過相應的Hash算法處理之后的Hash值的大小關系,並不能保證和Hash運算前完全一樣。
  • Hash索引無法被用來避免數據的排序操作。 由於Hash索引中存放的是經過Hash計算之后的Hash值,而且Hash值的大小關系並不一定和Hash運算前的鍵值完全一樣,所以數據庫無法利用索引的數據來避免任何排序運算;
  • Hash索引不能利用部分索引鍵查詢。 對於組合索引,Hash索引在計算Hash值的時候是組合索引鍵合並后再一起計算Hash值,而不是單獨計算Hash值,所以通過組合索引的前面一個或幾個索引鍵進行查詢的時候,Hash索引也無法被利用。
  • Hash索引在任何時候都不能避免表掃描。 前面已經知道,Hash索引是將索引鍵通過Hash運算之后,將 Hash運算結果的Hash值和所對應的行指針信息存放於一個Hash表中,由於不同索引鍵存在相同Hash值,所以即使取滿足某個Hash鍵值的數據的記錄條數,也無法從Hash索引中直接完成查詢,還是要通過訪問表中的實際數據進行相應的比較,並得到相應的結果。
  • Hash索引遇到大量Hash值相等的情況后性能並不一定就會比BTree索引高。 對於選擇性比較低的索引鍵,如果創建Hash索引,那么將會存在大量記錄指針信息存於同一個Hash值相關聯。這樣要定位某一條記錄時就會非常麻煩,會浪費多次表數據的訪問,而造成整體性能低下。

疑問回答環節(主要針對InnoDB)

為什么輔助索引不直接存數據的地址而存主鍵id呢

因為數據會不斷的變動,所以他的地址會跟着一起變。如果直接存儲地址,下次找的數據可能就不是原先的數據了。

索引是不是創建的越多越好呢

答:並不是

  • 我們已經知道了索引即數據,那么我們過多的創建索引就會導致數據量的增加。
  • 我們知道索引是一顆平衡樹,我們在更新數據的同時,索引也在頻繁的進行頁分裂和合並,非常耗時。

有關什么是頁分裂和合並推薦一篇知乎文章InnoDB中的頁合並與分裂,這里就不單獨講述。

為什么我們推薦使用自增id而不推薦使用uuid或者身份證號等呢

上面我們提到過B+Tree是自底向上插入的,什么意思呢。我們優先會將數據插入到葉子節點中,然后整個樹會根據底部的葉子節點進行變動。

當我們使用的是自增主鍵呢,我們葉子節點鏈表會根據當前最后一條的位置,將最新的一條數據順序的插入到后面,看下圖。

 

 但是當你插入一個uuid時,mysql根本不知道他該插入到哪個位置,需要從頭開始尋找插入的位置。但是當我們的插入的頁滿了時,這就造成了頁的分裂和合並,極大的影響了效率。

 

 

而且我們使用uuid的話,uuid所占字節也比較長,就導致了每一頁存儲的數據就會變少,也不利於索引的數據查詢。

哪些列適合添加索引呢

  • 需要經常where的字段
  • 需要join連表的字段
  • 需要排序的字段
  • 需要group by的字段

我們需不需要在性別上加索引呢?

這個呢我們就做個測試,我有一個300w數據的表。

CREATE TABLE `user_innodb` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `gender` tinyint(1) DEFAULT NULL,
  `phone` varchar(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3000001 DEFAULT CHARSET=utf8mb4;

我們首先查詢一下性別為男的所有數據。

SELECT *
from user_innodb
where gender = 1;

沒加索引之前,我們用explain看下執行效率。

 

 

執行結果是1s,還可以接受。

加了索引之后,最終結果出來沒讓我失望22s。

 

 

所以實驗就證明了不能夠在性別上創建索引。為什么會有這么大的差別呢,加了索引反而比不加索引更慢。

因為,在沒有索引的情況下,mysql只需遍歷底部的鏈表即可。但是加了索引以后他會查詢index(gender)找到合法的索引的主鍵,然后通過主鍵再去index(id)里面去找這樣一來一回效率自然就直線下降。

那么我們創建索引有什么特別的依據嗎,這里就給大家一個公式:count(distinct(column_name)) : count(*),這個可以簡單地計算出這個字段的離散值,離散值越高說明建立索引效果更明顯。例如我們給手機號加索引,最后計算出來的離散度是1,說明非常有必要加索引。

like '%張’一定不走索引嗎

我們再次進行個測試,我們給phone和name兩個字段建立一個聯合索引idx_phone_name。然后看下下面這條語句的執行計划。

EXPLAIN SELECT *
FROM user_innodb
WHERE name LIKE '%張' and phone = '13204776301';

這種情況下因為phone在索引第一位,所以無論有沒有name這個條件都會走索引。

 

 

我們可以看到extra里面存在Using index condition(ICP),ICP的全名是index condition pushdown索引條件下推。

ICP 索引條件下推

索引條件下推(ICP)是針對MySQL使用索引從表中檢索行的情況的一種優化。如果不使用ICP,則存儲引擎將遍歷索引以在基表中定位行,並將其返回給MySQL服務器,后者將評估WHERE行的條件。

啟用ICP后,如果WHERE可以僅使用索引中的列來評估部分 條件,則MySQL服務器會將這部分條件壓入WHERE條件下降到存儲引擎。然后,存儲引擎通過使用索引條目來評估推送的索引條件,並且只有在滿足此條件的情況下,才從表中讀取行。ICP可以減少存儲引擎必須訪問基表的次數以及MySQL服務器必須訪問存儲引擎的次數。

索引條件下推式優化的適用性取決於以下條件:

  • ICP用於 range, ref, eq_ref,和 ref_or_null訪問方法時,有一個需要訪問的全部表行。
  • ICP可用於InnoDB 和MyISAM表,包括分區表InnoDB和 MyISAM表。
  • 對於InnoDB表,ICP僅用於二級索引。ICP的目標是減少全行讀取次數,從而減少I / O操作。對於 InnoDB聚集索引,完整的記錄已被讀入InnoDB 緩沖區。在這種情況下使用ICP不會減少I / O。
  • 在虛擬生成的列上創建的二級索引不支持ICP。InnoDB 支持虛擬生成的列上的二級索引。
  • 引用子查詢的條件不能下推。
  • 涉及存儲功能的條件不能下推。
  • 存儲引擎無法調用存儲的功能。
  • 觸發條件不能下推。

具體的IPC相關的信息,建議參考官網

我那上面的那條sql進行個舉例,說明下什么是ICP,看下圖,一切都在圖里。

 

 

如果表沒有主鍵怎么辦,聚集索引怎么建立

  • 默認情況下我們在設置表主鍵的時候,數據庫會默認將其設置為聚集索引。
  • 如果沒有定義主鍵,那么mysql會找第一個唯一索引來作為局促索引,前提是聚集索引是NOT NULL
  • 如果上面的兩個條件都沒有滿足,那么InnoDB會生成一個隱藏的聚集索引GEN_CLUST_INDEX,每一行都生成一個默認自增的主鍵id。

來源:blog.csdn.net/qq_30062181/article/details/112712362

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM