สาเหตุของการสืบค้นที่ช้าเป็นครั้งคราว?


16

เรากำลังใช้งาน MySQL 5.1 บน Windows Server 2008 R2

เราทำการวิเคราะห์บางอย่างในฐานข้อมูลของเราล่าช้าและพบสิ่งรบกวนที่เราไม่สามารถอธิบายได้ เราเพิ่มรหัสเพื่อเข้าสู่ระบบเมื่อเรามีการสืบค้นที่ใช้เวลานาน (> 2000ms) ผลลัพธ์น่าประหลาดใจ (และอาจเป็นคำอธิบายสำหรับการหยุดชะงักของเรา)

แบบสอบถามเป็นครั้งคราวซึ่งโดยปกติจะใช้เวลาน้อยมาก (<10ms) ใช้เวลาตั้งแต่ 4 ถึง 13 วินาที เพื่อความชัดเจนนี่คือข้อความค้นหาที่ทำงานอย่างต่อเนื่อง (หลาย ๆ ครั้งต่อวินาที) และไม่ได้รับผลกระทบจากเวลาในการค้นหาเหล่านี้

เราผ่านดัชนีของเราเพื่อค้นหาข้อผิดพลาดที่ชัดเจนและไม่มีโชคมาก

ปรับปรุง

ตารางผู้คน:

| people | CREATE TABLE `people` (
`people_id` bigint(20) NOT NULL AUTO_INCREMENT,
`company_id` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`temp_password` varchar(10) DEFAULT NULL,
`reset_password_hash` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`phone` varchar(32) DEFAULT NULL,
`mobile` varchar(32) DEFAULT NULL,
`iphone_device_id` varchar(160) DEFAULT NULL,
`iphone_device_time` datetime DEFAULT NULL,
`last_checkin` datetime DEFAULT NULL,
`location_lat` double DEFAULT NULL,
`location_long` double DEFAULT NULL,
`gps_strength` smallint(6) DEFAULT NULL,
`picture_blob_id` bigint(20) DEFAULT NULL,
`authority` int(11) NOT NULL DEFAULT '0',
`active` tinyint(1) NOT NULL DEFAULT '1',
`date_created` datetime NOT NULL,
`last_login` datetime NOT NULL,
`panic_mode` tinyint(1) NOT NULL DEFAULT '0',
`battery_level` double DEFAULT NULL,
`battery_state` varchar(32) DEFAULT NULL,
PRIMARY KEY (`people_id`),
KEY `email` (`email`),
KEY `company_id` (`company_id`),
KEY `iphone_device_id` (`iphone_device_id`),
KEY `picture_blob_id` (`picture_blob_id`),
CONSTRAINT `people_ibfk_1` FOREIGN KEY (`company_id`) REFERENCES `companies` (`company_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `people_ibfk_2` FOREIGN KEY (`picture_blob_id`) REFERENCES `blobs` (`blob_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4658 DEFAULT CHARSET=utf8 |

ดัชนี:

+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table  | Non_unique | Key_name         | Seq_in_index | Column_name      | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| people |          0 | PRIMARY          |            1 | people_id        | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | email            |            1 | email            | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | company_id       |            1 | company_id       | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | iphone_device_id |            1 | iphone_device_id | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | picture_blob_id  |            1 | picture_blob_id  | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+

เรามีแถว 5,000 แถวในตารางบนเซิร์ฟเวอร์ที่ทำให้เรามีปัญหา


1
มีบางสิ่งที่คุณยังไม่ได้แสดงในคำถามสองข้อก่อนหน้านี้ โปรดเพิ่มคำถามนี้สาม (3) สิ่ง: 1) แสดงการสร้างตารางผู้คน \ G 2) แสดงดัชนีจากผู้คน; 3) เลือก COUNT (1) จากคน;
RolandoMySQLDBA

@RolandoMySQLDBA ฉันจะทำทันทีที่ฉันได้ทำงานวันพรุ่งนี้ ไชโย :)
RedBlueThing

ฉันปรับปรุงคำตอบของฉัน กรุณาอ่าน !!!
RolandoMySQLDBA

@RolandoMySQLDBA ขอบคุณ :) ยังแยกวิเคราะห์สิ่งนี้อยู่ ฉันจะให้คุณรู้ว่าเราไปอย่างไร
RedBlueThing

คำตอบ:


14

แบบสอบถาม UPDATE ในสองคำถามก่อนหน้านี้ ( คำถาม 1 , คำถาม 2 ) กำลังกดปุ่ม 'people' ของตารางโดยคีย์หลักพร้อมการล็อคระดับแถว นี่คือสิ่งที่ฉันระบุไว้ในคำถาม 1 เมื่อวันที่ 6 มิถุนายน 2011 เวลา 10:03 น

ธุรกรรมทั้งหมดกำลังผ่านคีย์หลัก เนื่องจาก Primary เป็นดัชนีคลัสเตอร์ใน InnoDB คีย์หลักและแถวจึงอยู่ด้วยกัน ดังนั้นการข้ามแถวและคีย์หลักเป็นแบบเดียวกัน ดังนั้นการล็อกดัชนีใด ๆ ในคีย์หลักคือการล็อคระดับแถวเช่นกัน

มีบางสิ่งที่ยังไม่ได้รับการพิจารณาที่สามารถทำให้เกิดความเชื่องช้าต่อดัชนี: การใช้ดัชนีที่ไม่ซ้ำกันใน InnoDB ทุกการค้นหาที่จัดทำดัชนีใน InnoDB ที่ใช้ดัชนีที่ไม่ซ้ำกันจะมี rowID ของแต่ละแถวที่แนบมากับคีย์ที่ไม่ซ้ำกัน ROWID พื้น eminates จากดัชนีคลัสเตอร์ การอัปเดตดัชนีที่ไม่ซ้ำกันต้องมีการโต้ตอบกับดัชนีคลัสเตอร์เสมอแม้ว่าตารางจะไม่มีคีย์หลัก

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

หากตาราง 'people' มีดัชนีที่ไม่ซ้ำกันจำนวนมากให้เตรียมหน้าดัชนีจำนวนมากใน tablespace รวมถึงการมีแถวเล็ก ๆ เล็ก ๆ ล็อคคุณเข้ามาเป็นครั้งคราว

ยังมีอีกปัจจัยที่ไม่ชัดเจน: ประชากรที่สำคัญ

บางครั้งเมื่อดัชนีได้รับการเติมค่าคีย์ในการสร้างดัชนีอาจไม่สมดุลเมื่อเวลาผ่านไปและทำให้ MySQL Query Optimizer เปลี่ยนจากการค้นหาแบบใช้คีย์การสแกนดัชนีและการสแกนแบบเต็มตาราง ที่คุณไม่สามารถควบคุมได้เว้นแต่คุณจะออกแบบตารางใหม่ด้วยดัชนีใหม่เพื่อชดเชยคีย์ ot แบบ lopsidedness โปรดให้โครงสร้างตารางสำหรับตาราง 'คน' นับตาราง 'คน' และเอาท์พุทแสดงดัชนีสำหรับตาราง

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

ปรับปรุง 2011-06-14 22:19

แบบสอบถามจากคำถาม 1

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>',
iphone_device_time = '2011-06-06 05:35:09', last_checkin = '2011-06-06 05:24:42',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>-<id>-<id>',
iphone_device_time = '2011-06-06 05:24:42', last_checkin = '2011-06-06 05:35:07',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

รูปภาพลำดับเหตุการณ์

  1. ค้นหาแถวด้วยคีย์หลัก
  2. ล็อคแถวและดัชนีคลัสเตอร์
  3. สร้างข้อมูล MVCC สำหรับคอลัมน์ทั้งหมดที่กำลังอัปเดต
  4. มีการจัดทำดัชนีสี่คอลัมน์ (อีเมล, company_id, iphone_device_id, picture_blob_id)
  5. แต่ละดัชนีต้องการการจัดการ BTREE
  6. ภายในพื้นที่การทำธุรกรรมเดียวกันขั้นตอนที่ 1-5 พยายามทำซ้ำในแถวเดียวกันอัปเดตคอลัมน์เดียวกัน (ส่งอีเมลเหมือนกันในทั้งสองแบบสอบถาม company_id เหมือนกันในทั้งสองแบบสอบถาม picture_blob_id เหมือนกันในทั้งสองแบบสอบถาม iphone_device_id ต่างกัน)

แบบสอบถามจากคำถามที่ 2

UPDATE people SET iphone_device_id=NULL
WHERE iphone_device_id='iphone:<device_id_blah>' AND people_id<>666;

UPDATE people SET company_id = 444, name = 'Dad', password = '<pass>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@gmail.com',
phone = NULL, mobile = NULL, iphone_device_id = 'iphone:<device_id_blah>',
iphone_device_time = '2011-06-06 19:12:29', last_checkin = '2011-06-07 02:49:47',
location_lat = <lat>, location_long = <lng>, gps_strength = 66,
picture_blob_id = 1661,
authority = 1, active = 1, date_created = '2011-03-20 19:18:34',
last_login = '2011-06-07 11:15:01', panic_mode = 0, battery_level = 0.55,
battery_state = 'unplugged' WHERE people_id = 666;

แบบสอบถามทั้งสองนี้ทำให้เกิดความสับสนมากขึ้นเนื่องจากคิวรีแรกกำลังอัปเดตทุกอย่างยกเว้น people_id 666 หลายร้อยแถวถูกล็อคอย่างเจ็บปวดด้วยคิวรีแรก เคียวรีที่สองกำลังอัพเดต people_id 666 ที่รันเหตุการณ์ 5 ลำดับ แบบสอบถามแรกกำลังเรียกใช้เหตุการณ์ 5 ลำดับเดียวกันเหล่านั้นในทุกแถวที่เกี่ยวข้องยกเว้น people_id 666 แต่ดัชนีสำหรับ iphone_device_id อยู่ในหลักสูตร interecept ที่มีสองแบบสอบถามที่แตกต่างกัน ใครบางคนต้องล็อคในหน้า BTREE บนพื้นฐานมาก่อนได้ก่อน

ในการเผชิญกับการสืบค้นสองคู่ในหลักสูตรการชนกันเพื่อล็อคหน้า BTREE เดียวกันภายในดัชนีหนึ่ง ๆ อาจเป็นประสบการณ์ที่น่าทึ่งสำหรับ InnoDB หรือ RDBMS ที่สอดคล้องกับกรดใด ๆ ดังนั้นการชะลอตัวของดัชนีคือชะตากรรมของการสืบค้นคู่เหล่านี้เว้นแต่ว่าคุณจะสามารถรับประกันได้ว่าการสืบค้นที่รันด้วย AUTOCOMMIT = 1 หรือโดยการอนุญาตให้การอ่านสกปรก (แม้ว่าการชนเช่นนี้จะทำให้ READ-COMMITTED

อัพเดท 2011-06-15 10:29

@RedBlueThing: ในแบบสอบถามจากคำถาม 2 แบบสอบถามแรกคือแบบสอบถามช่วงดังนั้นจึงมีการล็อกแถวจำนวนมาก นอกจากนี้สังเกตว่าการสืบค้นทั้งสองพยายามที่จะล็อค id พื้นที่เดียวกัน 0 หน้า no 4611 n บิต 152 จะถูกล็อคในคีย์หลักซึ่งเป็นดัชนีคลัสเตอร์

เพื่อให้แน่ใจว่าแอพของคุณทำงานอย่างน้อยที่สุดโดยอิงตามซีรีส์ของเหตุการณ์ที่คุณคาดหวังมีสองตัวเลือกที่แตกต่างกันที่คุณสามารถลองได้:

ตัวเลือก 1) แปลงตารางนี้เป็น MyISAM (อย่างน้อยบนเซิร์ฟเวอร์การพัฒนา) UPDATE, INSERT และ DELETE แต่ละรายการจะมีการล็อคตารางเต็มรูปแบบสำหรับผู้ที่มาก่อนได้ก่อน

ตัวเลือก 2) ลองใช้ระดับการแยกของSERIALIZABLE นั่นจะล็อคแถวที่ต้องการทั้งหมดในโหมด SHARED

ลำดับของเหตุการณ์ที่คุณคาดหวังจะแตกหรือประสบความสำเร็จโดยใช้ตัวเลือกสองทางเลือกเหล่านี้ หากทั้งสองตัวเลือกเหล่านี้ล้มเหลวคุณจะต้องตรวจสอบแอพของคุณและจัดลำดับความสำคัญของการดำเนินการค้นหา เมื่อคุณสร้างลำดับความสำคัญนั้นคุณสามารถยกเลิกตัวเลือกเหล่านี้ได้ (สำหรับตัวเลือก 1 กลับไปที่ InnoDB สำหรับตัวเลือก 2 กลับไปที่ระดับการแยกเริ่มต้น [หยุดใช้ SERIALIZABLE])


@RolandoMySQLDBA ฉันได้อัปเดตคำถามของเราพร้อมรายละเอียดที่คุณขอ
RedBlueThing

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

@RolandoMySQLDBA ตามข้อเสนอแนะของคุณจากคำถามที่ 1 เราได้ตรวจสอบการตั้งค่าอัตโนมัติของเราและยืนยันว่ามันเปิดอยู่
RedBlueThing

@RolandoMySQLDBA มีปัญหาเฉพาะกับแบบสอบถามจากคำถามแรก (นอกเหนือจากการปรับปรุงเขตข้อมูลทั้งหมดในแถว) มีบางสิ่งที่จะอธิบายเวลาดำเนินการ 13 วินาทีสำหรับเคียวรีหรือไม่ ฉันเข้าใจว่าการจัดทำดัชนีสี่คอลัมน์ไม่ใช่สิ่งที่คุณอยากจะแนะนำ แต่สิ่งนี้จะส่งผลให้ประสิทธิภาพต่ำหรือไม่
RedBlueThing

@RolandoMySQLDBA +1 และขอบคุณสำหรับคำแนะนำทั้งหมดของคุณ เราไม่ได้สิ้นสุดการเปลี่ยนระดับการแยกเพื่อแก้ไขปัญหา แต่เราได้ทำการอัปเดตฟิลด์บางส่วนสำหรับคำถามที่ 2 & ปรับคำค้นหาให้เหมาะสมในเส้นทางการอัปเดต Voila! ไม่มีการหยุดชะงักอีกต่อไป :)
RedBlueThing

3

แสดงความหลากหลายเช่น 'innodb%'; - โดยเฉพาะอย่างยิ่งหากข้อมูลและดัชนีไม่ถึงขนาดของบัฟเฟอร์พูลคุณอาจกดดิสก์ได้หนักกว่าเดิมมาก I / O เป็นนักฆ่าประสิทธิภาพที่ยิ่งใหญ่

ฟิลด์ส่วนใหญ่ของคุณมีขนาดใหญ่เป็นสองเท่าตามต้องการ BIGINT (8 bytes) เป็นวิธีที่ overkill สำหรับรหัสส่วนใหญ่ 5,000 แถวต้องการเพียง SMALLINT UNSIGNED (จำกัด 65K, 2 ไบต์เท่านั้น) หรือใช้ MEDIUMINT เพื่อความปลอดภัย

DOUBLE ให้ 16 หลักสำคัญในราคา 8 ไบต์ battery_level มีความแม่นยำมากกว่า 2 หลักหรือไม่ FLOAT ใช้เวลา 4 ไบต์

จุดของฉันที่นี่คือ "เล็กกว่า -> แคชได้มากกว่า -> เร็วกว่า"

กรุณาแสดงแบบสอบถามที่ช้า; อย่างน้อยก็บางคนที่ช้าก็กลายเป็น เราสามารถเดาได้โดยไม่มีพวกเขาเท่านั้น เปิดใช้งานช้าและตั้ง long_query_time = 1 สิ่งเหล่านี้จะช่วยค้นหาข้อความค้นหาที่ช้าที่สุด

คุณเข้าใจถึงประโยชน์ของดัชนี "ผสม" หรือไม่?

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.