เดิมทีการทำ SQL tuning เป็นศาสตร์ที่ต้องใช้ความละเอียดรอบคอบ และต้องให้เวลากับมัน ต้องศึกษาจาก best prectice หลายแหล่ง เพื่อให้ได้การตั้งค่าที่เหมาะสมกับการใช้งานในแต่ละรูปแบบ วันนี้เลยจะแนะนำเครื่องมือ ที่ช่วยให้การ tuning นั้นง่ายขึ้น ไม่ต้องทำความเข้าใจทั้งหมดในครั้งเดียว แต่ค่อยๆ ปรับไปได้ ด้วยกระบวนการนี้ทำให้เราลดระยะเวลาในการดำเนินการได้เยอะ
Jump to Contents
รู้จักกับ MySQL Tuning-Primer.sh
เป็น shell script ที่พัฒนาโดย Matthew Montgomery โดย script จะทำงานโดยดูข้อมูลจาก SHOW STATUS LIKE...
และ SHOW VARIABLES LIKE...
เพื่อสร้างคำแนะนำสำหรับตั้งค่า server โดยคำแนะนำจะมีหัวข้อต่อไปนี้
Currently it handles recommendations for the following:
- Slow Query Log
- Max Connections
- Worker Threads
- Key Buffer [MyISAM only]
- Query Cache
- Sort Buffer
- Joins
- Temp Tables
- Table (Open & Definition) Cache
- Table Locking
- Table Scans (
read_buffer
) [MyISAM only] - InnoDB Status
ซึ่งก็เพียงพอแล้วสำหรับการปรับแต่งเบื้องต้นเพื่อให้ทราบถึงการตั้งค่าพื้นฐานนั้นเหมาะสมกับ Server ที่เรากำลังใช้งานหรือไม่ โดยคำแนะนำก็จะมีการแบ่งเป็นสี เขียว, เหลือง และ แดง
ตั้งค่าการใช้งาน
ก่อนอื่นให้เรา SSH เข้าไปยังเครื่อง server linux ที่ติดตั้ง MySQL/MariaDB ไว้ จากนั้นตั้งค่า user, password ของ MySQL โดยการสร้างไฟล์ .my.cnf
ขึ้นมาใน home ของเราเองได้เลย
nano ~/.my.cnf
กรอกข้อมูล user, password สำหรับเชื่อมต่อ MySQL/MariaDB ให้ถูกต้อง ค่า variable นี้ต้องอยู่ใน section ของ [client]
หรือจะใช้ [mysql]
ก็ได้เช่นกัน เสร็จแล้วบันทึก
[client]
user=
password=
การใช้งานแสนง่าย
เข้าไปที่ลิงค์นี้ https://github.com/BMDan/tuning-primer.sh เพื่อดาวน์โหลด แต่หากไม่ต้องการ download ก็สามารถใช้งานผ่าน curl ได้เลย (มันก็ไม่ต่างกันหรอก)
curl -L https://raw.githubusercontent.com/BMDan/tuning-primer.sh/main/tuning-primer.sh | bash
เจ้า script ตัวนี้ก็จะทำการ recommend ออกมาเป็นข้อๆ พร้อมกับสี ที่สังเกตุได้ง่าย อันไหนแดง ก็ต้องไปปรับแก้ไขใหม่ การแก้ไขการกำหนดค่าของ MySQL/MariaDB นั้นให้แก้ที่ไฟล์ /etc/mysql/my.cnf
nano /etc/mysql/my.cnf
ใน section [mysqld]
เราจะกำหนดค่าที่ระบบแนะนำ เช่น
[mysqld]
innodb_buffer_pool_size = 26G
innodb_log_file_size = 26G
innodb_log_buffer_size = 128M
innodb_flush_log_at_trx_commit = 0
innodb_flush_method = O_DSYNC
# Cache only tiny result sets, so we can fit more in the query cache.
query_cache_limit = 128K
query_cache_size = 0
ทั้งนี้การตั้งค่าเอง ผมก็หาวิธีง่ายๆ มาประกอบ โดย วิธีแรก เราไปหา template ของ my.cnf ที่ทำไว้แล้วเอามาลอก ยกตัวอย่างเช่น gist ของท่านนี้ https://gist.github.com/fevangelou/fb72f36bbe333e059b66 โดยการเอา recommend จาก script มาค้นหาดูว่า เขากำหนดค่าอย่างไร และในกลุ่มนั้นต้องกำหนดอะไรเพิ่มเติมบ้าง เราก็สามารถนำมาประยุกค์ใช้ได้เลย
# === Optimized my.cnf configuration for MySQL/MariaDB (on Ubuntu, CentOS, Almalinux etc. servers) ===
#
# by Fotis Evangelou, developer of Engintron (engintron.com)
#
# ~ Updated December 2021 ~
#
#
# The settings provided below are a starting point for a 8-16 GB RAM server with 4-8 CPU cores.
# If you have different resources available you should adjust accordingly to save CPU, RAM & disk I/O usage.
#
# The settings marked with a specific comment or the word "UPD" (after the value)
# should be adjusted for your system by using database diagnostics tools like:
#
# https://github.com/major/MySQLTuner-perl
# or
# https://github.com/BMDan/tuning-primer.sh
#
# Run either of these scripts before optimizing your database, at least 1 hr after the optimization & finally
# at least once a day for 3 days (without restarting the database) to see how your server performs and if you need
# to re-adjust anything. The more MySQL/MariaDB runs without restarting, the more usage data it gathers, so these
# diagnostics scripts will report in mode detail how MySQL/MariaDB performs.
#
#
# IMPORTANT NOTE: If there is NO comment after a setting value, then 99,9% of the times you won't need to adjust it.
#
#
# --- THINGS TO DO AFTER YOU UPDATE MY.CNF - TROUBLESHOOTING ---
#
# If any terminal commands are mentioned, make sure you execute them as "root" user.
#
# If MySQL or MariaDB cannot start (or restart), then perform the following actions.
#
# 1. If the server had the stock database configuration and you added or updated any
# "innodb_log_*" settings (as suggested below), then execute these commands ONLY
# the first time you apply this configuration:
#
# $ rm -rvf /var/lib/mysql/ib_logfile*
# $ chown -R mysql:mysql /var/lib/mysql
# $ service mysql restart
#
# or use the shorthand command:
# $ rm -rvf /var/lib/mysql/ib_logfile*; chown -R mysql:mysql /var/lib/mysql; service mysql restart
#
# 2. If the setting "bind-address" is not commented out, then make sure the file /etc/hosts is
# properly configured. A good example of a "clean" /etc/hosts file is something like this:
#
# 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
# ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# 1.2.3.4 hostname.domain.tld hostname # <-- Replace accordingly!
#
# Finally restart the database service:
#
# $ service mysql restart
#
# 3. If the database service cannot restart even after the first 2 steps, make sure the database data folder
# (common for either MySQL or MariaDB) "/var/lib/mysql" is owned by the "mysql" user AND group.
# Additionally, the folder itself can have 0751 or 0755 file permissions. To fix it, simply do this:
# $ chown -R mysql:mysql /var/lib/mysql
# $ chmod 0755 /var/lib/mysql
#
# Finally restart the database service:
#
# $ service mysql restart
#
#
# ~ FIN ~
[mysql]
port = 3306
socket = /var/run/mysqld/mysqld.sock
[mysqld]
# === Required Settings ===
basedir = /usr
bind_address = 127.0.0.1 # Change to 0.0.0.0 to allow remote connections
datadir = /var/lib/mysql
#default_authentication_plugin = mysql_native_password # Enable in MySQL 8+ or MariaDB 10.6+ for backwards compatibility with common CMSs
max_allowed_packet = 256M
max_connect_errors = 1000000
pid_file = /var/run/mysqld/mysqld.pid
port = 3306
skip_external_locking
skip_name_resolve
socket = /var/run/mysqld/mysqld.sock
tmpdir = /tmp
user = mysql
# === SQL Compatibility Mode ===
# Enable for b/c with databases created in older MySQL/MariaDB versions
# (e.g. when using null dates)
#sql_mode = ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES
# Crappy SQL queries/schema? Go bold!
#sql_mode = ""
# === InnoDB Settings ===
default_storage_engine = InnoDB
innodb_buffer_pool_instances = 4 # Use 1 instance per 1GB of InnoDB pool size - max is 64
innodb_buffer_pool_size = 4G # Use up to 70-80% of RAM
innodb_file_per_table = 1
innodb_flush_log_at_trx_commit = 0
innodb_flush_method = O_DIRECT
innodb_log_buffer_size = 16M
innodb_log_file_size = 1G
innodb_sort_buffer_size = 4M # UPD - Defines how much data is read into memory for sorting operations before writing to disk (default is 1M / max is 64M)
innodb_stats_on_metadata = 0
#innodb_use_fdatasync = 1 # Only (!) for MySQL v8.0.26+
#innodb_temp_data_file_path = ibtmp1:64M:autoextend:max:20G # Control the maximum size for the ibtmp1 file
#innodb_thread_concurrency = 4 # Optional: Set to the number of CPUs on your system (minus 1 or 2) to better
# contain CPU usage. E.g. if your system has 8 CPUs, try 6 or 7 and check
# the overall load produced by MySQL/MariaDB.
innodb_read_io_threads = 64
innodb_write_io_threads = 64
#innodb_io_capacity = 2000 # Depends on the storage tech - use 2000 for SSD, more for NVMe
#innodb_io_capacity_max = 4000 # Usually double the value of innodb_io_capacity
# === MyISAM Settings ===
# The following 3 options are ONLY supported by MariaDB & up to MySQL 5.7
# Do NOT un-comment on MySQL 8.x+
#query_cache_limit = 4M # UPD
#query_cache_size = 64M # UPD
#query_cache_type = 1 # Enabled by default
key_buffer_size = 24M # UPD
low_priority_updates = 1
concurrent_insert = 2
# === Connection Settings ===
max_connections = 100 # UPD - Important: high no. of connections = high RAM consumption
back_log = 512
thread_cache_size = 100
thread_stack = 192K
interactive_timeout = 180
wait_timeout = 180
# For MySQL 5.7+ only (disabled by default)
#max_execution_time = 90000 # Set a timeout limit for SELECT statements (value in milliseconds).
# This option may be useful to address aggressive crawling on large sites,
# but it can also cause issues (e.g. with backups). So use with extreme caution and test!
# More info at: https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_execution_time
# For MariaDB 10.1.1+ only (disabled by default)
#max_statement_time = 90 # The equivalent of "max_execution_time" in MySQL 5.7+ (set above)
# The variable is of type double, thus you can use subsecond timeout.
# For example you can use value 0.01 for 10 milliseconds timeout.
# More info at: https://mariadb.com/kb/en/aborting-statements/
# === Buffer Settings ===
# Handy tip for managing your database's RAM usage:
# The following values should be treated carefully as they are added together and then multiplied by your "max_connections" value.
# Other options will also add up to RAM consumption (e.g. tmp_table_size). So don't go switching your "join_buffer_size" to 1G, it's harmful & inefficient.
# Use one of the database diagnostics tools mentioned at the top of this file to count your database's potential total RAM usage, so you know if you are within
# reasonable limits. Remember that other services will require enough RAM to operate properly (like Apache or PHP-FPM), so set your limits wisely.
join_buffer_size = 4M # UPD
read_buffer_size = 3M # UPD
read_rnd_buffer_size = 4M # UPD
sort_buffer_size = 4M # UPD
# === Table Settings ===
# In systemd managed systems like Ubuntu 16.04+ or CentOS 7+, you need to perform an extra action for table_open_cache & open_files_limit
# to be overriden (also see comment next to open_files_limit).
# E.g. for MySQL 5.7, please check: https://dev.mysql.com/doc/refman/5.7/en/using-systemd.html
# and for MariaDB check: https://mariadb.com/kb/en/library/systemd/
table_definition_cache = 40000 # UPD
table_open_cache = 40000 # UPD
open_files_limit = 60000 # UPD - This can be 2x to 3x the table_open_cache value or match the system's
# open files limit usually set in /etc/sysctl.conf and /etc/security/limits.conf
# In systemd managed systems this limit must also be set in:
# - /etc/systemd/system/mysql.service.d/override.conf (for MySQL 5.7+ in Ubuntu) or
# - /etc/systemd/system/mysqld.service.d/override.conf (for MySQL 5.7+ in CentOS) or
# - /etc/systemd/system/mariadb.service.d/override.conf (for MariaDB)
# otherwise changing open_files_limit will have no effect.
#
# To edit the right file execute:
# $ systemctl edit mysql (or mysqld or mariadb)
# and set "LimitNOFILE=" to something like 100000 or more (depending on your system limits for MySQL)
# or use "LimitNOFILE=infinity" for MariaDB only.
# Finally merge the changes with:
# $ systemctl daemon-reload; systemctl restart mysql (or mysqld or mariadb)
max_heap_table_size = 128M # Increase to 256M or 512M if you have lots of temporary tables because of missing indices in JOINs
tmp_table_size = 128M # Use same value as max_heap_table_size
# === Search Settings ===
ft_min_word_len = 3 # Minimum length of words to be indexed for search results
# === Binary Logging ===
disable_log_bin = 1 # Binary logging disabled by default
#log_bin # To enable binary logging, uncomment this line & only one of the following 2 lines
# that corresponds to your actual MySQL/MariaDB version.
# Remember to comment out the line with "disable_log_bin".
#expire_logs_days = 1 # Keep logs for 1 day - For MySQL 5.x & MariaDB before 10.6 only
#binlog_expire_logs_seconds = 86400 # Keep logs for 1 day (in seconds) - For MySQL 8+ & MariaDB 10.6+ only
# === Error & Slow Query Logging ===
log_error = /var/lib/mysql/mysql_error.log
log_queries_not_using_indexes = 0 # Disabled on production
long_query_time = 5
slow_query_log = 0 # Disabled on production
slow_query_log_file = /var/lib/mysql/mysql_slow.log
[mysqldump]
# Variable reference
# For MySQL 5.7+: https://dev.mysql.com/doc/refman/5.7/en/mysqldump.html
# For MariaDB: https://mariadb.com/kb/en/library/mysqldump/
quick
quote_names
max_allowed_packet = 1024M
ยุคที่ขับเคลื่อนด้วย AI
อีกวิธีที่สะดวกไม่แพ้กัน ก็คือการเอา AI มาช่วย อย่าง ChatGPT มาช่วย โดยเราเอา recomment โยนไปให้ chatGPT สร้าง valiable กลับมาให้ ดังภาพ
ข้อความที่ผมถาม
Current join_buffer_size = 260.00 K You have had 1 queries where a join could not use an index properly You should enable “log-queries-not-using-indexes” Then look for non indexed joins in the slow query log. If you are unable to optimize your queries you may want to increase your join_buffer_size to accommodate larger joins in one pass. Note! This script will still suggest raising the join_buffer_size when ANY joins not using indexes are found.
ข้อความที่ ChatGPT ตอบ
ข้อความบอกว่าคุณมีการใช้งาน join_buffer_size ที่มีขนาดเล็ก (260.00 K) และมีคำแนะนำให้คุณเปิดใช้งาน “log-queries-not-using-indexes” เพื่อให้สามารถตรวจสอบการใช้งานการ join ที่ไม่ได้ใช้ดัชนีได้ ซึ่งสามารถช่วยให้คุณสามารถปรับแต่ง join_buffer_size ได้ตามความเหมาะสม
นี่คือข้อความเตือนที่เสนอให้คุณดำเนินการ:
- เปิดใช้งาน “log-queries-not-using-indexes”: เปิดใช้งานคุณสมบัตินี้จะช่วยให้คุณสามารถตรวจสอบการใช้งาน join ที่ไม่ได้ใช้ดัชนี ใน slow query log ซึ่งเป็นข้อมูลที่มีประโยชน์ในการปรับปรุงความเร็วของคำสั่ง SQL ได้
log-queries-not-using-indexes
- ตรวจสอบ slow query log: เมื่อคุณเปิดใช้งาน “log-queries-not-using-indexes” ในขั้นตอนที่ 1 คำสั่ง join ที่ไม่ได้ใช้ดัชนีจะถูกบันทึกลงใน slow query log คุณสามารถตรวจสอบ slow query log เพื่อหาคำสั่ง SQL ที่ต้องการปรับปรุง
- ปรับแต่ง join_buffer_size: หากคุณพบว่ามีการ join ที่ใช้งาน join_buffer_size เกินขนาดปัจจุบัน คุณสามารถปรับค่า join_buffer_size ให้มีขนาดใหญ่ขึ้นเพื่อรองรับการ join ที่ใหญ่ขึ้นในหนึ่งครั้ง
join_buffer_size=2M
คำสั่งนี้จะเปลี่ยนขนาดของ join_buffer_size เป็น 2 เมกะไบต์ เพื่อให้มีพื้นที่เพียงพอสำหรับการ join ที่ใหญ่ขึ้นในหนึ่งครั้ง. อย่างไรก็ตาม ค่านี้สามารถปรับเปลี่ยนตามความเหมาะสมของการใช้งานและสถานการณ์ของคุณได้. คุณอาจต้องทดสอบและปรับค่าไปเรื่อยๆ เพื่อหาค่าที่เหมาะสมที่สุดสำหรับเซิร์ฟเวอร์ MySQL/MariaDB ของคุณ
เป็นคำตอบที่ครบถ้วน และเข้าใจง่าย ประหยัดเวลาเราได้มาก ไม่ต้องไปนั่งค้นหาจาก Search Engine แล้วตัวมานั่งหาทีละลิงค์อีกต่อไปแล้ว
สรุป
โดยเนื้อหาสำคัญ เราก็ควรมีความเข้าใจด้วยว่าเราจะ tuning ไปเพื่ออะไร ปัญหาที่เราต้องการแก้คืออะไร มันอาจจะใช้การ tuning เพียงอย่างเดียวไม่ได้ อาจต้องใช้กระบวนการอื่นร่วมด้วย เช่นการทำ index อย่างเหมาะสม การเลือก query ข้อมูลที่จำเป็น แทนการใช้ * การ join ที่ระมัดระวัง data type ที่ไม่เหมือนกัน ขนาดไม่เท่ากัน และการ join เท่าที่จำเป็นเท่านั้น การปรับโครงสร้างตารางให้สอดคล้องกับข้อมูล column type การใช้ view หรือ store procedure แทนการใช้คำสั่ง query ก็ช่วยได้