MySQL chưa bao giờ đến với Hồ sơ truy vấn. Bây giờ MySQL đang được Oracle phát triển, tôi biết điều này sẽ tiếp tục như vậy.
Tuy nhiên, tất cả hy vọng không bị mất.
Từ năm 2007, Percona đã đưa ra một số công cụ tuyệt vời cho mọi thứ mà Nhà phát triển và DBA muốn, bao gồm Hồ sơ truy vấn.
Bộ công cụ đầu tiên của Percona, được gọi là MAATKIT , đã tạo ra một lĩnh vực cho người dùng nghiêm túc của MySQL. Nó có nhiều tính năng , chẳng hạn như:
- Hồ sơ truy vấn
- Nhịp tim tái tạo
- Quản lý nhân rộng
- Bảng tổng kiểm tra và đồng bộ hóa
Percona gần đây đã chia MAATKIT thành một bộ công cụ cập nhật hơn, ngày nay được gọi là Bộ công cụ Percona . Các công cụ này đã chọn nơi MAATKIT rời đi bằng cách mở rộng lĩnh vực hoạt động cho người dùng MySQL nghiêm túc để bao gồm những thứ như:
- Kiểm tra lỗi khóa ngoài
- Thay đổi lược đồ trực tuyến
- Giải thích trực quan kế hoạch
- và nhiều hơn nữa ...
Quay trở lại câu hỏi ban đầu, các công cụ hiện có để lập hồ sơ truy vấn là
Dưới đây là một ví dụ về loại thông tin phong phú có thể đến từ việc sử dụng một trong những công cụ sau:
Tôi đã giúp một khách hàng thực hiện mk-query-digest để báo cáo 20 truy vấn có hiệu suất kém nhất cứ sau 20 phút. Tôi có ý tưởng từ video YouTube này . Máy khách sẽ chuyển bất kỳ đầu ra của truy vấn xấu nào sang memcached, do đó làm giảm tỷ lệ truy vấn gây tổn hại cho cơ sở dữ liệu.
Đây là kịch bản tôi đã thực hiện để gọi mk-query-digest (chỉ kiểm tra danh sách quy trình)
#!/bin/sh
RUNFILE=/tmp/QueriesAreBeingDigested.txt
if [ -f ${RUNFILE} ] ; then exit ; fi
MKDQ=/usr/local/sbin/mk-query-digest
RUNTIME=${1}
COPIES_TO_KEEP=${2}
DBVIP=${3}
WHICH=/usr/bin/which
DATE=`${WHICH} date`
ECHO=`${WHICH} echo`
HEAD=`${WHICH} head`
TAIL=`${WHICH} tail`
AWK=`${WHICH} awk`
SED=`${WHICH} sed`
CAT=`${WHICH} cat`
WC=`${WHICH} wc`
RM=`${WHICH} rm | ${TAIL} -1 | ${AWK} '{print $1}'`
LS=`${WHICH} ls | ${TAIL} -1 | ${AWK} '{print $1}'`
HAS_THE_DBVIP=`/sbin/ip addr show | grep "scope global secondary" | grep -c "${DBVIP}"`
if [ ${HAS_THE_DBVIP} -eq 1 ] ; then exit ; fi
DT=`${DATE} +"%Y%m%d_%H%M%S"`
UNIQUETAG=`${ECHO} ${SSH_CLIENT}_${SSH_CONNECTION}_${DT} | ${SED} 's/\./ /g' | ${SED} 's/ //g'`
cd /root/QueryDigest
OUTFILE=QP_${DT}.txt
HOSTADDR=${DBVIP}
${MKDQ} --processlist h=${HOSTADDR},u=queryprofiler,p=queryprofiler --run-time=${RUNTIME} > ${OUTFILE}
#
# Rotate out Old Copies
#
QPFILES=QPFiles.txt
QPFILES2ZAP=QPFiles2Zap.txt
${LS} QP_[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9][0-9][0-9][0-9][0-9][0-9].txt > ${QPFILES}
LINECOUNT=`${WC} -l < ${QPFILES}`
if [ ${LINECOUNT} -gt ${COPIES_TO_KEEP} ]
then
(( DIFF = LINECOUNT - COPIES_TO_KEEP ))
${HEAD} -${DIFF} < ${QPFILES} > ${QPFILES2ZAP}
for QPFILETOZAP in `${CAT} ${QPFILES2ZAP}`
do
${RM} ${QPFILETOZAP}
done
fi
rm -f ${QPFILES2ZAP}
rm -f ${QPFILES}
rm -f ${RUNFILE}
Đây là người dùng tôi đã thực hiện để kết nối với mysql bằng mk-query-digest
GRANT PROCESS ON *.* TO 'queryprofiler'@'%' IDENTIFIED BY 'queryprofiler';
Đây là crontab tôi đã chạy cứ sau 20 phút (chưa đến 10 giây) giữ 144 bản cuối cùng (tức là 48 giờ hồ sơ)
*/20 * * * * /root/QueryDigest/ExecQueryDigest.sh 1190s 144 10.1.1.8
Phần đáng kinh ngạc: Đầu ra của mk-query-digest
Đây là một hồ sơ chạy 2011-12-28 11:20:00 trong 1190 giây (20 phút ít hơn 10 giây)
22 dòng cuối cùng
# Rank Query ID Response time Calls R/Call Item
# ==== ================== ================ ======= ========== ====
# 1 0x5E994008E9543B29 40.3255 11.2% 101 0.399263 SELECT schedule_occurrence schedule_eventschedule schedule_event schedule_eventtype schedule_event schedule_eventtype schedule_occurrence.start
# 2 0x392F6DA628C7FEBD 33.9181 9.4% 17 1.995184 SELECT mt_entry mt_objecttag
# 3 0x6C6318E56E149036 26.4695 7.3% 102 0.259505 SELECT schedule_occurrence schedule_eventschedule schedule_event schedule_eventtype schedule_event schedule_eventtype schedule_occurrence.start
# 4 0x00F66961DAE6FFB2 25.5472 7.1% 55 0.464495 SELECT mt_entry mt_placement mt_category
# 5 0x99E13015BFF1E75E 22.3618 6.2% 199 0.112371 SELECT mt_entry mt_objecttag
# 6 0x84DD09F0FC444677 22.3516 6.2% 39 0.573118 SELECT mt_entry
# 7 0x440EBDBCEDB88725 21.1817 5.9% 36 0.588380 SELECT mt_entry
# 8 0x8D258C584B858811 17.2402 4.8% 37 0.465951 SELECT mt_entry mt_placement mt_category
# 9 0x4E2CB0F4CAFD1400 16.9768 4.7% 40 0.424419 SELECT mt_entry mt_placement mt_category
# 10 0x377E0D0898266FDD 16.6979 4.6% 150 0.111319 SELECT polls_pollquestion mt_category
# 11 0x3B9686D98BB8E054 16.2089 4.5% 32 0.506529 SELECT mt_entry mt_objecttag mt_tag
# 12 0x97F670B604A85608 15.6158 4.3% 34 0.459287 SELECT mt_entry mt_placement mt_category
# 13 0x3F5557DA231225EB 14.4309 4.0% 36 0.400859 SELECT mt_entry mt_placement mt_category
# 14 0x191D660A10738896 13.1220 3.6% 31 0.423290 SELECT mt_entry mt_placement mt_category
# 15 0xF88F7421DD88036D 12.1261 3.4% 61 0.198788 SELECT mt_entry mt_blog mt_objecttag mt_tag mt_author
# 16 0xA909BF76E7051792 10.3971 2.9% 53 0.196172 SELECT mt_entry mt_objecttag mt_tag
# 17 0x3D42D07A335ED983 9.1424 2.5% 20 0.457121 SELECT mt_entry mt_placement mt_category
# 18 0x59F43B57DD43F2BD 9.0533 2.5% 21 0.431111 SELECT mt_entry mt_placement mt_category
# 19 0x7961BD4C76277EB7 8.5564 2.4% 47 0.182052 INSERT UNION UPDATE UNION mt_session
# 20 0x173EB4903F3B6DAC 8.5394 2.4% 22 0.388153 SELECT mt_entry mt_placement mt_category
Lưu ý rằng đây là danh sách 20 truy vấn có hiệu suất kém nhất dựa trên Thời gian phản hồi truy vấn chia cho Số lần truy vấn được gọi.
Nhìn vào ID truy vấn số 1, nghĩa là 0x5E994008E9543B29
, chúng tôi xác định ID truy vấn đó trong tệp đầu ra và đây là báo cáo cho truy vấn cụ thể đó:
# Query 1: 0.09 QPS, 0.03x concurrency, ID 0x5E994008E9543B29 at byte 0 __
# This item is included in the report because it matches --limit.
# pct total min max avg 95% stddev median
# Count 4 101
# Exec time 7 40s 303ms 1s 399ms 992ms 198ms 293ms
# Lock time 0 0 0 0 0 0 0 0
# Users 1 mt
# Hosts 101 10.64.95.73:33750 (1), 10.64.95.73:34452 (1), 10.64.95.73:38440 (1)... 97 more
# Databases 1 mt1
# Time range 1325089201 to 1325090385
# bytes 0 273.60k 2.71k 2.71k 2.71k 2.62k 0 2.62k
# id 4 765.11M 7.57M 7.58M 7.58M 7.29M 0.12 7.29M
# Query_time distribution
# 1us
# 10us
# 100us
# 1ms
# 10ms
# 100ms ################################################################
# 1s ######
# 10s+
# Tables
# SHOW TABLE STATUS FROM `mt1` LIKE 'schedule_occurrence'\G
# SHOW CREATE TABLE `mt1`.`schedule_occurrence`\G
# SHOW TABLE STATUS FROM `mt1` LIKE 'schedule_eventschedule'\G
# SHOW CREATE TABLE `mt1`.`schedule_eventschedule`\G
# SHOW TABLE STATUS FROM `mt1` LIKE 'schedule_event'\G
# SHOW CREATE TABLE `mt1`.`schedule_event`\G
# SHOW TABLE STATUS FROM `mt1` LIKE 'schedule_eventtype'\G
# SHOW CREATE TABLE `mt1`.`schedule_eventtype`\G
# SHOW TABLE STATUS FROM `schedule_occurrence` LIKE 'start'\G
# SHOW CREATE TABLE `schedule_occurrence`.`start`\G
# EXPLAIN
SELECT `schedule_occurrence`.`id`, `schedule_occurrence`.`schedule_id`, `schedule_occurrence`.`event_id`, `schedule_occurrence`.`start`, `schedule_occurrence`.`end`, `schedule_occurrence`.`cancelled`, `schedule_occurrence`.`original_start`, `schedule_occurrence`.`original_end`, `schedule_occurrence`.`all_day`, `schedule_occurrence`.`ongoing`, `schedule_occurrence`.`featured`, `schedule_eventschedule`.`id`, `schedule_eventschedule`.`event_id`, `schedule_eventschedule`.`start`, `schedule_eventschedule`.`end`, `schedule_eventschedule`.`all_day`, `schedule_eventschedule`.`ongoing`, `schedule_eventschedule`.`min_date_calculated`, `schedule_eventschedule`.`max_date_calculated`, `schedule_eventschedule`.`rule`, `schedule_eventschedule`.`end_recurring_period`, `schedule_eventschedule`.`textual_description`, `schedule_event`.`id`, `schedule_event`.`title`, `schedule_event`.`slug`, `schedule_event`.`description`, `schedule_event`.`host_id`, `schedule_event`.`cost`, `schedule_event`.`age_restrictions`, `schedule_event`.`more_info`, `schedule_event`.`photo_id`, `schedule_event`.`contact_email`, `schedule_event`.`event_type_id`, `schedule_event`.`featured`, `schedule_event`.`staff_pick`, `schedule_event`.`futuremost`, `schedule_event`.`creator_id`, `schedule_event`.`created_on`, `schedule_event`.`allow_comments`, `schedule_event`.`mt_entry`, `schedule_eventtype`.`id`, `schedule_eventtype`.`parent_id`, `schedule_eventtype`.`name`, `schedule_eventtype`.`slug`, `schedule_eventtype`.`lft`, `schedule_eventtype`.`rght`, `schedule_eventtype`.`tree_id`, `schedule_eventtype`.`level`, T5.`id`, T5.`title`, T5.`slug`, T5.`description`, T5.`host_id`, T5.`cost`, T5.`age_restrictions`, T5.`more_info`, T5.`photo_id`, T5.`contact_email`, T5.`event_type_id`, T5.`featured`, T5.`staff_pick`, T5.`futuremost`, T5.`creator_id`, T5.`created_on`, T5.`allow_comments`, T5.`mt_entry`, T6.`id`, T6.`parent_id`, T6.`name`, T6.`slug`, T6.`lft`, T6.`rght`, T6.`tree_id`, T6.`level` FROM `schedule_occurrence` INNER JOIN `schedule_eventschedule` ON (`schedule_occurrence`.`schedule_id` = `schedule_eventschedule`.`id`) INNER JOIN `schedule_event` ON (`schedule_eventschedule`.`event_id` = `schedule_event`.`id`) INNER JOIN `schedule_eventtype` ON (`schedule_event`.`event_type_id` = `schedule_eventtype`.`id`) INNER JOIN `schedule_event` T5 ON (`schedule_occurrence`.`event_id` = T5.`id`) INNER JOIN `schedule_eventtype` T6 ON (T5.`event_type_id` = T6.`id`) WHERE (EXTRACT(MONTH FROM `schedule_occurrence`.`start`) = 8 AND EXTRACT(DAY FROM `schedule_occurrence`.`start`) = 6 AND `schedule_occurrence`.`start` BETWEEN '2011-01-01 00:00:00' and '2011-12-31 23:59:59.99') ORDER BY `schedule_occurrence`.`ongoing` ASC, `schedule_occurrence`.`all_day` DESC, `schedule_occurrence`.`start` ASC\G
Mặc dù biểu đồ dựa trên văn bản, nó cho hình ảnh chính xác về hiệu suất tổng thể của truy vấn, đôi khi chạy trong hơn 1 giây và hầu hết thời gian trong khoảng từ 0,01 đến 0,1 giây. Từ đây, người ta có thể tiến hành điều chỉnh hiệu suất bằng cách cấu trúc lại truy vấn, đặt kết quả truy vấn vào memcached, thêm các chỉ mục bị thiếu hoặc bao phủ, v.v.
PHẦN KẾT LUẬN
IMHO Nếu Percona từng đặt các công cụ trình hồ sơ vào GUI Windows, nó sẽ dễ dàng cạnh tranh với SQL Server Profiler của Microsoft.
Quốc phòng nghỉ ngơi !!!