ลำดับของคอลัมน์ในดัชนีมีความสำคัญมากแค่ไหน?


173

ฉันได้ยินมาว่าคุณควรใส่คอลัมน์ที่จะเลือกมากที่สุดในตอนต้นของการประกาศดัชนี ตัวอย่าง:

CREATE NONCLUSTERED INDEX MyINDX on Table1
(
   MostSelective,
   SecondMost,
   Least
)

ก่อนอื่นสิ่งที่ฉันพูดถูกต้อง? ถ้าเป็นเช่นนั้นฉันมีแนวโน้มที่จะเห็นความแตกต่างอย่างมากในประสิทธิภาพโดยการจัดเรียงลำดับของคอลัมน์ในดัชนีของฉันอีกครั้งหรือเป็นแบบฝึกหัด

เหตุผลที่ฉันถามเพราะหลังจากใส่เคียวรีผ่าน DTA แล้วขอแนะนำให้ฉันสร้างดัชนีที่มีคอลัมน์เดียวกันเกือบทั้งหมดในดัชนีนั้นเป็นดัชนีที่มีอยู่ในลำดับที่แตกต่างกัน ฉันกำลังพิจารณาเพียงแค่เพิ่มคอลัมน์ที่ขาดหายไปในดัชนีที่มีอยู่และเรียกมันว่าดี คิด?

คำตอบ:


193

ดูดัชนีเช่นนี้:

Cols
  1   2   3
-------------
|   | 1 |   |
| A |---|   |
|   | 2 |   |
|---|---|   |
|   |   |   |
|   | 1 | 9 |
| B |   |   |
|   |---|   |
|   | 2 |   |
|   |---|   |
|   | 3 |   |
|---|---|   |

ดูว่าการ จำกัด ใน A เป็นอย่างไรคอลัมน์แรกของคุณกำจัดผลลัพธ์มากกว่าการ จำกัด ในคอลัมน์ที่สองก่อน? มันง่ายกว่าถ้าคุณนึกภาพว่าดัชนีต้องถูกข้ามไปอย่างไรคอลัมน์ 1 จากนั้นคอลัมน์ 2 ฯลฯ ... คุณจะเห็นว่าการตัดผลลัพธ์ส่วนใหญ่ในการผ่านกำปั้นทำให้ขั้นตอนที่ 2 นั้นเร็วกว่ามาก

อีกกรณีหนึ่งหากคุณสอบถามในคอลัมน์ 3 เครื่องมือเพิ่มประสิทธิภาพจะไม่ใช้ดัชนีเพราะจะไม่มีประโยชน์ในการ จำกัด ชุดผลลัพธ์ให้แคบลง เมื่อใดก็ตามที่คุณอยู่ในแบบสอบถามให้ จำกัด จำนวนผลลัพธ์ที่จะจัดการก่อนที่ขั้นตอนต่อไปจะหมายถึงประสิทธิภาพที่ดีขึ้น

เนื่องจากดัชนีจะถูกจัดเก็บด้วยวิธีนี้จึงไม่มีการย้อนรอยข้ามดัชนีเพื่อค้นหาคอลัมน์แรกเมื่อคุณทำการสืบค้น

กล่าวโดยย่อ: ไม่มันไม่ได้มีไว้สำหรับแสดงมีประโยชน์ด้านประสิทธิภาพที่แท้จริง


13
ในภาพด้านบนโปรดทราบว่าดัชนีนั้นจะมีประโยชน์เฉพาะเมื่อมีการระบุคอลัมน์ 1 ในแบบสอบถาม หากข้อความค้นหาของคุณระบุเฉพาะคอลัมน์ 2 ในการเข้าร่วมหรือค้นหาคำกริยาจะไม่เป็นประโยชน์ ดังนั้นการสั่งซื้อมีความสำคัญเช่นกัน อาจจะเป็นไปได้โดยไม่บอก แต่ต้องการพูดถึงมัน
CodeCowboyOrg

3
โปรดจำไว้ว่าดัชนีของคุณเป็นเหมือนภาพด้านบนและตัวกรองข้อความค้นหาของคุณในคอลัมน์ 1 และคอลัมน์ 2 แต่คอลัมน์ 2 นั้นมีความเป็นเอกลักษณ์มากกว่าและสิ่งที่คุณต้องการกรองจริงๆคือคอลัมน์ 2 จริง ๆ แล้วมันมีประโยชน์มากกว่าที่จะมีดัชนี คอลัมน์ 2 เป็นครั้งแรก สิ่งนี้อาจดูขัดแย้งกันได้ แต่จำไว้ว่าดัชนีถูกเก็บไว้ในหลาย ๆ หน้าและเป็นต้นไม้ที่มีช่วงของค่าในขณะที่คอลัมน์ที่ 1 ด้านบนไม่เห็นความเป็นไปได้ที่ 1 ดัชนีจะรู้ว่าหน้าดัชนีใดที่ตรงไป ค่า Column2 ไม่จำเป็นต้องใช้คอลัมน์ 1 เพื่อ จำกัด ให้แคบลง
CodeCowboyOrg

4
รูปภาพนี้ไม่ได้เป็นตัวแทนที่แม่นยำของวิธีการจัดทำดัชนีหรือการนำทาง ได้ส่งคำตอบที่แก้ไขstackoverflow.com/a/39080819/73226
Martin Smith

6
@ มาร์ตินฉันไม่เห็นด้วยว่ามันไม่ถูกต้อง เป็นที่เข้าใจง่ายมากซึ่งเป็นความตั้งใจของฉัน คำตอบของคุณขุดลงไปในรายละเอียดเพิ่มเติมเกี่ยวกับระดับที่ชื่นชมแม้ว่าสำหรับผู้ที่ต้องการขุดลึกลงไปในมัน ถ้าคุณดูรูปต้นไม้คุณจะเห็นสิ่งที่ฉันกำลังอธิบายในวิธีที่ง่ายมาก สิ่งนี้ไม่ได้มีลักษณะเฉพาะหรือแม้แต่ SQL ที่เฉพาะเจาะจง การทำดัชนีต้นไม้ B นั้นค่อนข้างพบได้ทั่วไปในหลาย ๆ สิ่ง
Nick Craver

@MartinSmith ฉันยังไม่เห็นด้วยว่ามันไม่ถูกต้องสิ่งที่คุณอธิบายคือพฤติกรรมมาตรฐานของวิธีที่จะครอบคลุมดัชนี - การเลือกมีความสำคัญมากขึ้นเมื่อคุณทำการค้นหาแบบช่วงเนื่องจากสิ่งนี้จะลดจำนวนหน้าดัชนีที่เครื่องมือเพิ่มประสิทธิภาพ ต้องสแกน สิ่งนี้อาจมีความสำคัญในตารางขนาดใหญ่ที่มีแถวนับล้าน
Paul Hatcher

127

ลำดับของคอลัมน์มีความสำคัญ ตอนนี้คำสั่งซื้อใดที่ถูกต้องขึ้นอยู่กับวิธีที่คุณจะสืบค้น ดัชนีสามารถใช้เพื่อทำการค้นหาที่แน่นอนหรือสแกนช่วง การค้นหาที่แน่นอนคือเมื่อมีการระบุค่าสำหรับคอลัมน์ทั้งหมดในดัชนีและการสืบค้นตรงกับแถวนั้นอย่างแน่นอนสำหรับการค้นหาลำดับของคอลัมน์นั้นไม่เกี่ยวข้อง การสแกนแบบช่วงคือเมื่อมีการระบุเฉพาะบางคอลัมน์และในกรณีนี้เมื่อการสั่งซื้อมีความสำคัญ SQL Server สามารถใช้ดัชนีสำหรับการสแกนช่วงเฉพาะเมื่อมีการระบุคอลัมน์ด้านซ้ายสุดและจากนั้นเฉพาะเมื่อมีการระบุคอลัมน์ทางซ้ายสุดถัดไปและต่อไปเรื่อย ๆ ถ้าคุณมีดัชนีใน (A, B, C) ก็สามารถนำมาใช้ในช่วงสแกนA=@aสำหรับA=@a AND B=@bแต่ไม่ได้สำหรับB=@bสำหรับมิได้C=@c B=@b AND C=@cกรณีที่A=@a AND C=@cมีการผสมอย่างใดอย่างหนึ่งเช่นเดียวกับในA=@aส่วนจะใช้ดัชนี แต่C=@cไม่ใช่ (แบบสอบถามจะสแกนค่า B ทั้งหมดสำหรับA=@aจะไม่ 'ข้าม' ถึงC=@c) ระบบฐานข้อมูลอื่นมีตัวดำเนินการ 'สแกนข้าม' ที่เรียกว่าสามารถใช้ประโยชน์จากคอลัมน์ภายในในดัชนีเมื่อไม่ได้ระบุคอลัมน์ด้านนอก

ด้วยความรู้ที่มีอยู่ในมือคุณสามารถดูคำจำกัดความของดัชนีอีกครั้ง ดัชนีใน(MostSelective, SecondMost, Least)จะมีผลก็ต่อเมื่อMostSelectiveมีการระบุคอลัมน์ แต่นั่นเป็นการเลือกที่ดีที่สุดความเกี่ยวข้องของคอลัมน์ด้านในจะลดลงอย่างรวดเร็ว มากมักจะคุณจะพบว่าดัชนีที่ดีกว่าอยู่ในหรือบน(MostSelective) include (SecondMost, Least) (MostSelective, SecondMost) include (Least)เนื่องจากคอลัมน์ด้านในมีความเกี่ยวข้องน้อยกว่าการวางคอลัมน์หัวกะทิต่ำในตำแหน่งที่ถูกต้องในดัชนีทำให้ไม่มีสิ่งใดนอกจากเสียงรบกวนสำหรับการค้นหาดังนั้นจึงเหมาะสมที่จะย้ายคอลัมน์เหล่านั้นออกจากหน้ากลางและเก็บไว้ในใบไม้หน้าเท่านั้น วัตถุประสงค์ความครอบคลุมแบบสอบถาม กล่าวอีกนัยหนึ่งย้ายพวกเขาไปที่ INCLUDE สิ่งนี้มีความสำคัญมากขึ้นเมื่อขนาดของLeastคอลัมน์เพิ่มขึ้น แนวคิดก็คือดัชนีนี้จะได้ประโยชน์เฉพาะคิวรีที่ระบุเท่านั้นMostSelective ไม่ว่าจะเป็นค่าที่แน่นอนหรือช่วงและคอลัมน์นั้นเป็นคอลัมน์ที่มีการคัดเลือกมากที่สุดก็จะ จำกัด แถวผู้สมัครในระดับที่ดี

ในทางตรงกันข้ามดัชนีบน(Least, SecondMost, MostSelective)อาจดูเหมือนผิด แต่จริงๆแล้วค่อนข้างดัชนีที่มีประสิทธิภาพ เนื่องจากมีLeastคอลัมน์เป็นคิวรีด้านนอกสุดจึงสามารถใช้สำหรับคิวรีที่ต้องรวมผลลัพธ์ในคอลัมน์การเลือกต่ำ แบบสอบถามดังกล่าวแพร่หลายใน OLAP และคลังข้อมูลการวิเคราะห์และนี่คือสิ่งที่ดัชนีดังกล่าวมีกรณีที่ดีมากสำหรับพวกเขา ดัชนีดังกล่าวทำให้ดัชนีคลัสเตอร์ที่ยอดเยี่ยมจริง ๆเพราะพวกเขาจัดรูปแบบทางกายภาพในชิ้นใหญ่ของแถวที่เกี่ยวข้อง ( Leastค่าเดียวกันซึ่งมักจะระบุประเภทหรือประเภทบางประเภท) และพวกเขาอำนวยความสะดวกแบบสอบถามการวิเคราะห์

ดังนั้นน่าเสียดายที่ไม่มีคำสั่ง 'ถูกต้อง' คุณไม่ควรทำตามสูตรของตัวตัดคุกกี้ แต่ควรวิเคราะห์รูปแบบแบบสอบถามที่คุณจะใช้กับตารางเหล่านั้นและตัดสินใจว่าคำสั่งของคอลัมน์ดัชนีใดที่เหมาะสม


3
การตอบสนองที่ยอดเยี่ยมตามปกติของ Remus ฉันจะอ่านย่อหน้าที่สามของคุณอีกสองสามครั้งแล้วติดตาม ฉันสงสัยว่าอาจเป็นสิ่งที่ฉันต้องทำ
Abe Miessler

"SQL Server สามารถใช้ดัชนีสำหรับช่วงการสแกนเฉพาะเมื่อมีการระบุคอลัมน์ด้านซ้ายสุดและเฉพาะในกรณีที่มีการระบุคอลัมน์ทางซ้ายสุดถัดไปและอื่น ๆ " นี่คือสิ่งที่ขาดหายไปจากความเข้าใจของฉันขอบคุณ! ฉันไม่ทราบว่าการสแกนแบบช่วงสามารถทำได้ในคอลัมน์ดัชนีที่ใช้มากที่สุดเท่านั้น แต่ตอนนี้ฉันทำได้แล้ว
Allon Guralnek

คำอธิบายนี้ใช้ได้กับ Oracle DB หรือไม่
อีก

1
@Roizpi ใช่แล้วโดยทั่วไปฐานข้อมูลความสัมพันธ์ใด ๆ กับดัชนีนั้นทำงานในลักษณะเดียวกันหรือคล้ายกันมาก
Tatranskymedved

45

รีมัสบอกว่ามันขึ้นอยู่กับภาระงานของคุณ

ฉันต้องการพูดถึงเรื่องที่ทำให้เข้าใจผิดของคำตอบที่ยอมรับ

สำหรับแบบสอบถามที่ดำเนินการค้นหาความเท่าเทียมกันในคอลัมน์ทั้งหมดในดัชนีไม่มีความแตกต่างที่สำคัญ

ด้านล่างสร้างสองตารางและเติมข้อมูลที่เหมือนกัน ข้อแตกต่างเพียงอย่างเดียวคือปุ่มหนึ่งมีการเรียงลำดับจากมากไปน้อยให้เลือกและอีกอันหนึ่งมีปุ่มย้อนกลับ

CREATE TABLE Table1(MostSelective char(800), SecondMost TINYINT, Least  CHAR(1), Filler CHAR(4000) null);
CREATE TABLE Table2(MostSelective char(800), SecondMost TINYINT, Least  CHAR(1), Filler CHAR(4000) null);

CREATE NONCLUSTERED INDEX MyINDX on Table1(MostSelective,SecondMost,Least);
CREATE NONCLUSTERED INDEX MyINDX2 on Table2(Least,SecondMost,MostSelective);

INSERT INTO Table1 (MostSelective, SecondMost, Least)
output inserted.* into Table2
SELECT TOP 26 REPLICATE(CHAR(number + 65),800), number/5, '~'
FROM master..spt_values
WHERE type = 'P' AND number >= 0
ORDER BY number;

ตอนนี้ทำแบบสอบถามกับทั้งสองตาราง ...

SELECT *
FROM   Table1
WHERE  MostSelective = REPLICATE('P', 800)
       AND SecondMost = 3
       AND Least = '~';

SELECT *
FROM   Table2
WHERE  MostSelective = REPLICATE('P', 800)
       AND SecondMost = 3
       AND Least = '~'; 

... ทั้งคู่ใช้การปรับดัชนีและทั้งสองจะได้รับค่าใช้จ่ายเท่ากัน

ป้อนคำอธิบายรูปภาพที่นี่

ASCII art ในคำตอบที่ยอมรับนั้นไม่ได้มีความเป็นจริงว่าดัชนีนั้นมีโครงสร้างอย่างไร หน้าดัชนีสำหรับ Table1 แสดงไว้ด้านล่าง (คลิกที่ภาพเพื่อเปิดขนาดเต็ม)

ป้อนคำอธิบายรูปภาพที่นี่

หน้าดัชนีประกอบด้วยแถวที่มีคีย์ทั้งหมด (ในกรณีนี้มีคอลัมน์คีย์เพิ่มเติมต่อท้ายตัวระบุแถวเนื่องจากดัชนีไม่ได้ประกาศว่าไม่ซ้ำกัน แต่สามารถละเว้นข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่นี่ )

สำหรับแบบสอบถามด้านบน SQL Server ไม่สนใจเกี่ยวกับการเลือกคอลัมน์ มันไม่ค้นหาไบนารีของหน้ารากและค้นพบว่าที่สำคัญ (PPP...,3,~ )คือ>=(JJJ...,1,~ )และดังนั้นจึงควรอ่านหน้า< (SSS...,3,~ ) 1:118จากนั้นจะทำการค้นหาแบบไบนารีของรายการคีย์บนหน้านั้นและหาตำแหน่งของเพจแบบ leaf เพื่อเลื่อนลงไป

การเปลี่ยนแปลงดัชนีตามลำดับการเลือกไม่มีผลต่อจำนวนการเปรียบเทียบคีย์ที่คาดหวังจากการค้นหาแบบไบนารีหรือจำนวนหน้าที่ต้องสำรวจเพื่อทำดัชนีการค้นหา ที่ดีที่สุดมันอาจเร่งความเร็วการเปรียบเทียบคีย์เอง

บางครั้งการเรียงลำดับดัชนีที่เลือกมากที่สุดก่อนจะเหมาะสมสำหรับคิวรีอื่นในเวิร์กโหลดของคุณ

เช่นถ้าปริมาณงานมีแบบสอบถามของทั้งสองแบบฟอร์มต่อไปนี้

SELECT * ... WHERE  MostSelective = 'P'

SELECT * ...WHERE Least = '~'

ดัชนีข้างต้นไม่ครอบคลุมดัชนีใดดัชนีหนึ่ง MostSelectiveมีความสามารถเพียงพอที่จะทำแผนด้วยการค้นหาและการค้นหาที่คุ้มค่า แต่การค้นหาLeastไม่ใช่

อย่างไรก็ตามสถานการณ์นี้ (ไม่ครอบคลุมดัชนีการค้นหาในส่วนย่อยของคอลัมน์นำของดัชนีคอมโพสิต) เป็นเพียงหนึ่งระดับของการสืบค้นที่เป็นไปได้ที่สามารถช่วยได้โดยดัชนี หากคุณไม่เคยค้นหาด้วยMostSelectiveตัวเองหรือรวมกันMostSelective, SecondMostและค้นหาด้วยการรวมกันของทั้งสามคอลัมน์แล้วความได้เปรียบเชิงทฤษฎีนี้ไม่มีประโยชน์กับคุณ

ข้อความค้นหาตรงกันข้ามเช่น

SELECT MostSelective,
       SecondMost,
       Least
FROM   Table2
WHERE  Least = '~'
ORDER  BY SecondMost,
          MostSelective 

จะได้รับความช่วยเหลือโดยมีลำดับย้อนกลับของรายการที่กำหนดโดยทั่วไป - เนื่องจากครอบคลุมแบบสอบถามสามารถรองรับการค้นหาและส่งคืนแถวในลำดับที่ต้องการในการบูต

ดังนั้นนี่เป็นคำแนะนำซ้ำ ๆ บ่อยๆ แต่ที่จริงแล้วมันเป็นฮิวริสติกเกี่ยวกับประโยชน์ที่อาจเกิดขึ้นกับข้อความค้นหาอื่น ๆ - และมันไม่สามารถทดแทนการมองภาระงานของคุณได้


31

คุณควรใส่คอลัมน์ที่จะเลือกมากที่สุดที่จุดเริ่มต้นของการประกาศดัชนี

แก้ไข. ดัชนีสามารถเป็นคอมโพสิตประกอบด้วยหลายคอลัมน์และลำดับนั้นมีความสำคัญเนื่องจากหลักการด้านซ้ายสุด เหตุผลคือฐานข้อมูลจะตรวจสอบรายการจากซ้ายไปขวาและต้องค้นหาการอ้างอิงคอลัมน์ที่เกี่ยวข้องที่ตรงกับคำสั่งที่กำหนด ตัวอย่างเช่นการมีดัชนีในตารางที่อยู่พร้อมคอลัมน์:

  • ที่อยู่
  • เมือง
  • สถานะ

แบบสอบถามใด ๆ ที่ใช้addressคอลัมน์สามารถใช้ดัชนีได้ แต่หากแบบสอบถามนั้นมีเพียงcityและ / หรือstateการอ้างอิงเท่านั้น - ดัชนีนั้นไม่สามารถใช้ได้ นี่เป็นเพราะคอลัมน์ซ้ายสุดไม่ได้ถูกอ้างอิง ประสิทธิภาพของข้อความค้นหาควรบอกคุณว่าดัชนีใดเหมาะสมที่สุด - ดัชนีแต่ละรายการหรือหลายคอมโพสิตที่มีคำสั่งซื้อต่างกัน อ่านได้ดี: The Tipping Pointโดย Kimberley Tripp


เกิดอะไรขึ้นถ้ามันเป็นเพียงคอลัมน์ขวาสุดที่ไม่ได้ใช้? ดังนั้นแบบสอบถามจึงใช้ที่อยู่และเมือง แต่ไม่ระบุ จะใช้ดัชนีหรือไม่
Abe Miessler

@Abe: ที่สุดจะไม่ถูกใช้ - คุณต้องทำตามคำสั่งของดัชนีโดยเริ่มจากด้านซ้าย พลาดหนึ่งใช้ไม่ได้
OMG Ponies

4
@Abe: หากคุณสอบถามที่อยู่และเมือง แต่ไม่ระบุ - ก็ใช่แล้วจะใช้ดัชนีนั้น กล่าวอีกนัยหนึ่งฐานข้อมูลสามารถใช้ดัชนีบางส่วนเพื่อตอบสนองการร้องขอตราบใดที่สามารถเริ่มต้นจากด้านซ้ายของดัชนีและย้ายไปทางขวาในการใช้เขตข้อมูลที่ถูกสอบถาม อย่างไรก็ตามหากคุณสอบถามโดยใช้ที่อยู่และรัฐ แต่ไม่ใช่เมืองก็อาจใช้ดัชนีได้ แต่จะไม่มีประสิทธิภาพเท่านี้เพราะตอนนี้สามารถใช้ส่วนที่อยู่ของดัชนีได้เท่านั้น (b / c ถัดไปคือ เมืองและมันไม่ได้ถูกใช้ในการสืบค้น)
JaredC

6

คำตอบอื่น ๆ ทั้งหมดผิด

การเลือกของแต่ละคอลัมน์ในดัชนีคอมโพสิตไม่สำคัญเมื่อเลือกคำสั่ง

นี่คือกระบวนการคิดที่เรียบง่าย: อย่างมีประสิทธิภาพดัชนีคือการต่อข้อมูลของคอลัมน์ที่เกี่ยวข้อง

การให้เหตุผลนั้นความแตกต่างเพียงอย่างเดียวคือการเปรียบเทียบ 'สตริง' สองตัวที่แตกต่างไปจากก่อนหน้านี้กับในภายหลังในสตริง นี่เป็นส่วนเล็ก ๆ ของค่าใช้จ่ายทั้งหมด ไม่มี "การผ่านครั้งแรก / ครั้งที่สอง" ตามที่กล่าวไว้ในคำตอบเดียว

ดังนั้นควรใช้คำสั่งซื้อแบบใด

  1. เริ่มต้นด้วยการทดสอบคอลัมน์=ในลำดับใด ๆ
  2. จากนั้นตรึงที่คอลัมน์หนึ่งช่วง

ตัวอย่างเช่นคอลัมน์หัวกะทิต่ำมากต้องมาก่อนในสิ่งนี้:

WHERE deleted = 0  AND  the_datetime > NOW() - INTERVAL 7 DAY
INDEX(deleted, the_datetime)

deletedสลับการสั่งซื้อในดัชนีจะมีมันทั้งหมดไม่สนใจ

(มีกฎมากขึ้นสำหรับการสั่งซื้อคอลัมน์)


คะแนนลบเป็นเพราะฉันผิดหรือเปล่า? หรือเพราะฉันมีความคิดเห็นที่ดี? หรืออย่างอื่น?
Rick James

ไม่ใช่ downvote ของฉัน แต่ถูกลบ = 0 สำหรับฉันดูเหมือนว่าไม่ใช่การเลือกต่ำ ฉันคิดว่ามันจะเป็นแถวส่วนใหญ่ในตาราง
เกร็ก

@ Greg - ฉันคิดว่านั่นหมายถึง "การเลือกน้อย" - นั่นคือการใช้deletedไม่ได้ช่วยกรองแถวที่ไม่ต้องการออกไปมากนัก คุณมีตัวอย่างที่ดีกว่านี้ไหม? (นั่นคือสิ่งที่โผล่เข้ามาในใจของฉันเมื่อฉันเขียนคำตอบ)
Rick James

ความเข้าใจผิดในส่วนของฉัน
เกร็ก

1
@ClickOk - ขอบคุณ ตำราของฉันให้ข้อมูลพื้นฐาน: mysql.rjweb.org/doc.php/index_cookbook_mysql
Rick James
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.