4.3 案例演示
步骤1. 建表
CREATE TABLE `student` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `stuno` INT NOT NULL , `name` VARCHAR(20) DEFAULT NULL, `age` INT(3) DEFAULT NULL, `classId` INT(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
步骤2:设置参数log_bin_trust_function_creators
创建函数,假如报错:This function has none of DETERMINISTIC…
命令开启:允许创建函数设置
set global log_bin_trust_function_creators=1; # 不加global只是当前窗口有效。
步骤3:创建函数
随机产生字符串:(同上一章)
DELIMITER // CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255) #该函数会返回一个字符串 BEGIN DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'; DECLARE return_str VARCHAR(255) DEFAULT ''; DECLARE i INT DEFAULT 0; WHILE i < n DO SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1)); SET i = i + 1; END WHILE; RETURN return_str; END // DELIMITER ; #测试 SELECT rand_string(10); DELIMITER // CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11) BEGIN DECLARE i INT DEFAULT 0; SET i = FLOOR(from_num +RAND()*(to_num - from_num+1)) ; RETURN i; END // DELIMITER ; #测试: SELECT rand_num(10,100); /* +------------------+ | rand_num(10,100) | +------------------+ | 23 | +------------------+ */
步骤4:创建存储过程
DELIMITER // CREATE PROCEDURE insert_stu1( START INT , max_num INT ) BEGIN DECLARE i INT DEFAULT 0; SET autocommit = 0; #设置手动提交事务 REPEAT #循环 SET i = i + 1; #赋值 INSERT INTO student (stuno, NAME ,age ,classId ) VALUES ((START+i),rand_string(6),rand_num(10,100),rand_num(10,1000)); UNTIL i = max_num END REPEAT; COMMIT; #提交事务 END // DELIMITER ;
步骤5:调用存储过程
#调用刚刚写好的函数, 4000000条记录,从100001号开始 CALL insert_stu1(100001,4000000);
mysql> SELECT COUNT(*) FROM student; +----------+ | COUNT(*) | +----------+ | 4000000 | +----------+ 1 row in set (0.70 sec)
4.4 测试及分析
1. 测试
SELECT * FROM student WHERE stuno = 3455655; /* +---------+---------+--------+------+---------+ | id | stuno | name | age | classId | +---------+---------+--------+------+---------+ | 3355654 | 3455655 | IHnqPe | 14 | 943 | +---------+---------+--------+------+---------+ 1 row in set (1.33 sec) */ SELECT * FROM student WHERE name = 'IHnqPe '; /* +---------+---------+--------+------+---------+ | id | stuno | name | age | classId | +---------+---------+--------+------+---------+ | 2264898 | 2364899 | IHnqPe | 14 | 968 | | 2802437 | 2902438 | IHnqPe | 13 | 933 | | 2947412 | 3047413 | IHnqPe | 13 | 929 | | 3355654 | 3455655 | IHnqPe | 14 | 943 | | 3432555 | 3532556 | IhNqpE | 15 | 507 | +---------+---------+--------+------+---------+ 5 rows in set (1.43 sec) */
从上面的结果可以看出来,查询学生编号为“3455655”的学生信息花费时间为1.33 秒。查询学生姓名为“IHnqPe”的学生信息花费时间为1.43 秒。已经达到了秒的数量级,说明目前查询效率是比较低的,下面我们分析一下原因:
2. 分析
show status like 'slow_queries'; /* +---------------+-------+ | Variable_name | Value | +---------------+-------+ | Slow_queries | 2 | +---------------+-------+ */
补充说明:
除了上述变量,控制慢查询日志的还有一个系统变量: min_examined_row_limit。这个变量的意思是,查询扫描过的最少记录数。这个变晕和查询执行时间,共同组成了判别一个查询是否是慢查询的条件。如果查询扫描过的记录数大于等于这个变量的值,并且查询执行时间超过long_query_time的值,那么,这个查询就被记录到慢查询日志中;反之,则不被记录到慢查询日志中。
mysql> show variables like ‘min%’;
/*
±-----------------------±------+
| Variable_name | Value |
±-----------------------±------+
| min_examined_row_limit | 0 |
±-----------------------±------+
1 row in set (0.02 sec)
*/
这个值默认是0。与long_query_time=10合在一起,表示只要查询的执行时间超过10秒钟,哪怕一个记录也没有扫描过,都要被记录到慢查询日志中。也可以根据需要,通过修“my.ini"文件,来修改查询时长,或者通过SET指令,用SQL语句修改“min_examined_row_limit"的值。
4.5 慢查询日志分析工具:mysqldumpslow
在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpslow
查看mysqldumpslow的帮助信息
#注意:下面语句不是在mysql>下执行,而要在根目录下执行 [root@centos7-mysql-1 ~]# mysqldumpslow --help /* Usage: mysqldumpslow [ OPTS... ] [ LOGS... ] Parse and summarize the MySQL slow query log. Options are --verbose verbose --debug debug --help write this text to standard output -v verbose -d debug -s ORDER what to sort by (al, at, ar, c, l, r, t), 'at' is default al: average lock time ar: average rows sent at: average query time c: count l: lock time r: rows sent t: query time -r reverse the sort order (largest last instead of first) -t NUM just show the top n queries -a don't abstract all numbers to N and strings to 'S' -n NUM abstract numbers with at least n digits within names -g PATTERN grep: only consider stmts that include this string -h HOSTNAME hostname of db server for *-slow.log filename (can be wildcard), default is '*', i.e. match all -i NAME name of server instance (if using mysql.server startup script) -l don't subtract lock time from total time */
mysqldumpslow 命令的具体参数如下:
-a: 不将数字抽象成N,字符串抽象成S
-s: 是表示按照何种方式排序:
c: 访问次数
l: 锁定时间
r: 返回记录
t: 查询时间
al:平均锁定时间
ar:平均返回记录数
at:平均查询时间 (默认方式)
ac:平均查询次数
-t :即为返回前面多少条的数据;
-g: 后边搭配一个正则匹配模式,大小写不敏感的
下面演示如何得到慢查询:
首先找到慢查询文件在哪个目录下:
#首先找到慢查询文件在哪个目录下: [root@centos7-mysql-1 ~]# cd /var/lib/mysql [root@centos7-mysql-1 mysql]# ll #运行结构可知目录为LSLNO1-slow.log /* 总用量 902900 drwxr-x---. 2 mysql mysql 67 8月 3 18:23 atguigudb1 -rw-r-----. 1 mysql mysql 56 7月 25 19:53 auto.cnf -rw-r-----. 1 mysql mysql 498 7月 25 20:34 binlog.000001 -rw-r-----. 1 mysql mysql 1400 7月 27 18:19 binlog.000002 -rw-r-----. 1 mysql mysql 7004 7月 28 20:02 binlog.000003 -rw-r-----. 1 mysql mysql 647264629 8月 3 18:33 binlog.000004 -rw-r-----. 1 mysql mysql 64 7月 29 10:00 binlog.index -rw-------. 1 mysql mysql 1680 7月 25 19:53 ca-key.pem -rw-r--r--. 1 mysql mysql 1112 7月 25 19:53 ca.pem -rw-r-----. 1 mysql mysql 901 8月 3 18:43 centos7-mysql-1-slow.log -rw-r--r--. 1 mysql mysql 1112 7月 25 19:53 client-cert.pem -rw-------. 1 mysql mysql 1676 7月 25 19:53 client-key.pem drwxr-x---. 2 mysql mysql 4096 7月 31 11:15 dbtest1 drwxr-x---. 2 mysql mysql 242 8月 3 13:21 dbtest2 -rw-r-----. 1 mysql mysql 196608 8月 3 18:33 #ib_16384_0.dblwr -rw-r-----. 1 mysql mysql 8585216 8月 3 14:18 #ib_16384_1.dblwr -rw-r-----. 1 mysql mysql 3795 7月 28 20:02 ib_buffer_pool -rw-r-----. 1 mysql mysql 12582912 8月 3 18:32 ibdata1 -rw-r-----. 1 mysql mysql 50331648 8月 3 18:33 ib_logfile0 -rw-r-----. 1 mysql mysql 50331648 8月 3 18:33 ib_logfile1 -rw-r-----. 1 mysql mysql 12582912 7月 29 10:00 ibtmp1 drwxr-x---. 2 mysql mysql 187 7月 29 10:00 #innodb_temp drwxr-x---. 2 mysql mysql 143 7月 25 19:53 mysql -rw-r-----. 1 mysql mysql 25165824 8月 3 18:33 mysql.ibd srwxrwxrwx. 1 mysql mysql 0 7月 29 10:00 mysql.sock -rw-------. 1 mysql mysql 5 7月 29 10:00 mysql.sock.lock drwxr-x---. 2 mysql mysql 8192 7月 25 19:53 performance_schema -rw-------. 1 mysql mysql 1680 7月 25 19:53 private_key.pem -rw-r--r--. 1 mysql mysql 452 7月 25 19:53 public_key.pem -rw-r--r--. 1 mysql mysql 1112 7月 25 19:53 server-cert.pem -rw-------. 1 mysql mysql 1676 7月 25 19:53 server-key.pem drwxr-x---. 2 mysql mysql 28 7月 25 19:53 sys -rw-r-----. 1 mysql mysql 83886080 8月 3 18:33 undo_001 -rw-r-----. 1 mysql mysql 33554432 8月 3 18:32 undo_002 */
举例:我们想要按照查询时间排序,查看前五条 SQL 语句,这样写即可:
#查看前五条 SQL 语句 [root@centos7-mysql-1 mysql]# mysqldumpslow -s t -t 5 /var/lib/mysql/centos7-mysql-1-slow.log /*查询结果 Count: 1 Time=251.55s (251s) Lock=0.00s (0s) Rows=0.0 (0), root[root]@[192.168.253.134] CALL insert_stu1(N,N) Count: 1 Time=1.43s (1s) Lock=0.00s (0s) Rows=5.0 (5), root[root]@localhost SELECT * FROM student WHERE name = 'S' Count: 1 Time=1.29s (1s) Lock=0.03s (0s) Rows=1.0 (1), root[root]@localhost SELECT * FROM student WHERE stuno = N Died at /usr/bin/mysqldumpslow line 162, <> chunk 3. */
不想替换S、N 用-a 花费时间降序排列
[root@centos7-mysql-1 mysql]# mysqldumpslow -a -s t -t 5 /var/lib/mysql/centos7-mysql-1-slow.log Reading mysql slow query log from /var/lib/mysql/centos7-mysql-1-slow.log Count: 1 Time=251.55s (251s) Lock=0.00s (0s) Rows=0.0 (0), root[root]@[192.168.253.134] CALL insert_stu1(100001,4000000) Count: 1 Time=1.43s (1s) Lock=0.00s (0s) Rows=5.0 (5), root[root]@localhost SELECT * FROM student WHERE name = 'IHnqPe ' Count: 1 Time=1.29s (1s) Lock=0.03s (0s) Rows=1.0 (1), root[root]@localhost SELECT * FROM student WHERE stuno = 3455655 Died at /usr/bin/mysqldumpslow line 162, <> chunk 3.
工作常用参考:
#得到返回记录集最多的10个SQL mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log #得到访问次数最多的10个SQL mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log #得到按照时间排序的前10条里面含有左连接的查询语句 mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log #另外建议在使用这些命令时结合 | 和more 使用 ,否则有可能出现爆屏情况 mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log | more
4.6 关闭慢查询日志
MySQL服务器停止慢查询日志功能有两种方法:
方式1:永久性方式
[mysqld] slow_query_log=OFF
或者,把slow_query_log一项注释掉 或 删除
[mysqld] #slow_query_log =OFF
重启MySQL服务,执行如下语句查询慢日志功能。
SHOW VARIABLES LIKE ‘%slow%’; #查询慢查询日志所在目录 SHOW VARIABLES LIKE ‘%long_query_time%’; #查询超时时长
可以看到,MySQL系统中的慢查询日志是关闭的。
方式2:临时性方式
使用SET语句来设置。
(1)停止MySQL慢查询日志功能,具体SQL语句如下
SET GLOBAL slow_query_log=off; SHOW VARIABLES LIKE '%slow%'; /* +---------------------------+--------------------------------+ | Variable_name | Value | +---------------------------+--------------------------------+ | log_slow_admin_statements | OFF | | log_slow_extra | OFF | | log_slow_slave_statements | OFF | | slow_launch_time | 2 | | slow_query_log | OFF | | slow_query_log_file | /var/lib/mysql/LSLNO1-slow.log | +---------------------------+--------------------------------+ */ #以及 SHOW VARIABLES LIKE '%long_query_time%'; /* +-----------------+----------+ | Variable_name | Value | +-----------------+----------+ | long_query_time | 1.000000 | +-----------------+----------+ */
(2)重启MySQL服务,使用SHOW语句查询慢查询日志功能信息,具体SQL语句如下
#在根目录下重启服务 [root@centos7-mysql-1 mysql]# systemctl restart mysqld mysql> SHOW VARIABLES LIKE '%long_query_time%'; /* 重启后阈值恢复默认值10 +-----------------+-----------+ | Variable_name | Value | +-----------------+-----------+ | long_query_time | 10.000000 | +-----------------+-----------+ */
4.7 删除慢查询日志
查询并调优完后该日志没有用了,可以将其删除
使用SHOW语句显示慢查询日志信息,具体SQL语句如下
show variables like 'slow_query_log%'; /* +---------------------+-----------------------------------------+ | Variable_name | Value | +---------------------+-----------------------------------------+ | slow_query_log | OFF | | slow_query_log_file | /var/lib/mysql/centos7-mysql-1-slow.log | +---------------------+-----------------------------------------+ 2 rows in set (0.00 sec) */
从执行结果可以看出,慢查询日志的目录默认为MySQL的数据目录,在该目录下手动删除慢查询日志文件即可
[root@LSLNO1 mysql]# rm centos7-mysql-1-slow.log rm:是否删除普通文件 "LSLNO1-slow.log"?y
使用命令mysqladmin flush-logs
来重新生成查询日志文件,具体命令如下,执行完毕会在数据目录下重新生成慢查询日志文件
mysqladmin -uroot -p flush-logs slow
# 在打开的条件下,执行上面的语句,才可以看见重置的日志文件 SET GLOBAL slow_query_log=on;
提示:
慢查询日志都是使用mysqladmin flush-logs命令来删除重建的。使用时一定要注意,一旦执行了这个命令,慢查询日志都只存在新的日志文件中,如果需要旧的查询日志,就必须享先备份。
5. 查看 SQL 执行成本:SHOW PROFILE
show profile在《逻辑架构》章节中讲过,这里作为复习。
Show Profile是MySQL提供的可以用来分析当前会话中SQL都做了什么、执行的资源消耗情况的工具,可用于sql调优的测量。默认情况下处于关闭状态
,并保存最近15次的运行结果。
可以在会话级别开启这个功能
show variables like 'profiling'; /* +---------------+-------+ | Variable_name | Value | +---------------+-------+ | profiling | OFF | +---------------+-------+ */
通过设profiling='ON’来开启show profile :
set profiling = 'ON';
show profile使用演示:
use atguigudb; select * from student where stuno =343455; -- select * from student where name = 'vyituS'; -- show profiles; /* +----------+------------+---------------------------------------------+ | Query_ID | Duration | Query | +----------+------------+---------------------------------------------+ | 1 | 0.00174700 | show variables like 'profiling' | | 2 | 1.52700950 | select * from student where stuno =343455 | | 3 | 1.20279475 | select * from student where name = 'vyituS' | +----------+------------+---------------------------------------------+ 3 rows in set, 1 warning (0.00 sec) */ show profile; /* +--------------------------------+----------+ | Status | Duration | +--------------------------------+----------+ | starting | 0.000083 | | Executing hook on transaction | 0.000004 | | starting | 0.000009 | | checking permissions | 0.000006 | | Opening tables | 0.000044 | | init | 0.000004 | | System lock | 0.000008 | | optimizing | 0.000008 | | statistics | 0.000019 | | preparing | 0.000018 | | executing | 1.202507 | | end | 0.000024 | | query end | 0.000005 | | waiting for handler commit | 0.000010 | | closing tables | 0.000012 | | freeing items | 0.000023 | | cleaning up | 0.000013 | +--------------------------------+----------+ 17 rows in set, 1 warning (0.00 sec) */
我们也可以查看指定的Query lD的开销,比如show profile for query 2
查询结果是一样的。在SHOWPROFILE中我们可以查看不同部分的开销,比如cpu、block.io等:
mysql> show profile cpu, block io for query 2; +--------------------------------+----------+----------+------------+--------------+---------------+ | Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out | +--------------------------------+----------+----------+------------+--------------+---------------+ | starting | 0.000111 | 0.000066 | 0.000039 | 0 | 0 | | Executing hook on transaction | 0.000006 | 0.000002 | 0.000001 | 0 | 0 | | starting | 0.000009 | 0.000005 | 0.000003 | 0 | 0 | | checking permissions | 0.000006 | 0.000004 | 0.000003 | 0 | 0 | | Opening tables | 0.000044 | 0.000028 | 0.000016 | 0 | 0 | | init | 0.000004 | 0.000002 | 0.000002 | 0 | 0 | | System lock | 0.000008 | 0.000005 | 0.000002 | 0 | 0 | | optimizing | 0.000009 | 0.000006 | 0.000004 | 0 | 0 | | statistics | 0.000057 | 0.000036 | 0.000022 | 0 | 0 | | preparing | 0.000023 | 0.000013 | 0.000008 | 0 | 0 | | executing | 1.526632 | 1.208456 | 0.519889 | 361248 | 0 | | end | 0.000021 | 0.000008 | 0.000005 | 0 | 0 | | query end | 0.000005 | 0.000003 | 0.000001 | 0 | 0 | | waiting for handler commit | 0.000012 | 0.000008 | 0.000005 | 0 | 0 | | closing tables | 0.000013 | 0.000008 | 0.000004 | 0 | 0 | | freeing items | 0.000037 | 0.000024 | 0.000014 | 0 | 0 | | cleaning up | 0.000016 | 0.000009 | 0.000006 | 0 | 0 | +--------------------------------+----------+----------+------------+--------------+---------------+ 17 rows in set, 1 warning (0.00 sec)
show profile的常用查询参数:
- ALL:显示所有的开销信息。
- BLOCK IO:显示块IO开销。
- CONTEXT SWITCHES:上下文切换开销。
- CPU:显示CPU开销信息。
- IPC:显示发送和接收开销信息
- MEMORY:显示内存开销信息。
- PAGE FAULTS:显示页面错误开销信息。
- SOURCE:显示和Source_function,Source_file,Source_line相关的开销信息。
- SWAPS:显示交换次数开销信息。
日常开发需注意的结论:
- converting HEAP to MyISAM:查询结果太大,内存不够,数据往磁盘上搬了。
- Creating tmp.table:创建临时表。先拷贝数据到临时表,用完后再删除临时表
- Copying to tmp table on disk:把内存中临时表复制到磁盘上,警惕!
- locked 。
- 如果在show profile诊断结果中出现了以上4条结果中的任何一条,则sql语句需要优化。
注意:
不过 SHOW PROFILE命令将被弃用,可以从 information_schema中的profiling 数据表进行查看
6. 分析查询语句:EXPLAIN
6.1概述
定位了查询慢的SQL之后,就可以使用EXPLAIN或DESCRIBE工具做针对性的分析查询语句。DESCRIBE语句的使用方法与EXPLAIN语句是一样的,并且分析结果也是一样的。
MySQL中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供它认为最优的执行计划(他认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间)
这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来具体执行查询等等。
MySQL为我们提供了EXPLAIN语句来帮助我们查看某个查询语句的具体执行计划,大家看懂EXPLAIN语句的各个输出项,可以有针对性的提升查询语句的性能。
1.能做什么?
- 表的读取顺序
- 数据读取操作的操作类型。
- 哪些索引可以使用
- 哪些索引被实际使用
- 表之间的引用
- 每张表有多少行被优化器查询
2.官网介绍
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
https://dev.mysql.com/doc/refman/8.0/en/explain-output.html
3.版本情况
MySQL 5.6.3以前只能 EXPLAIN SELECT ;MYSQL 5.6.3以后就可以 EXPLAIN,SELECT,UPDATE,DELETE
在5.7以前的版本中,想要显示 partitions 需要使用 explain partitions 命令;想要显示filtered需要使用 explain extended命令。在5.7版本后,默认explain直接显示partitions和filtered中的信息。
EXPLAIN SELECT * FROM student_info; SELECT * FROM student_info LIMIT 10; DESCRIBE DELETE FROM student_info WHERE id=2;
6.2 基本语法
EXPLAIN 或 DESCRIBE语句的语法形式如下:
EXPLAIN SELECT select_options #或者 DESCRIBE SELECT select_options
如果我们想看看某个查询的执行计划的话,可以在具体的查询语句前边加一个 EXPLAIN ,就像这样:
EXPLAIN SELECT 1; /* +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ */
输出的上述信息就是所谓的执行计划。在这个执行计划的辅助下,我们需要知道应该怎样改进自己的查询语句以使查询执行起来更高效。其实除了以SELECT开头的查询语句,其余的DELETE、INSERT、REPLACE以及UPDATE语句等都可以加上EXPLAIN,用来查看这些语句的执行计划,只是平时我们对SELECT语句更感兴趣
注意: 执行EXPLAIN时并没有真正的执行该后面的语句,因此可以安全的查看执行计划。
EXPLAIN语句输出的各个列的作用如下:
列名 | 描述 |
id |
在一个大的查询语句中每个SELECT关键字都对应一个 唯一的id |
select_type |
SELECT关键字对应的那个查询的类型 |
table |
表名 |
partitions |
匹配的分区信息 |
type |
针对单表的访问方法 |
possible_keys |
可能用到的索引 |
key |
实际上使用的索引 |
key_len |
实际使用到的索引长度(单位:字节) |
ref |
当使用索引列等值查询时,与索引列进行等值匹配的对象信息 |
rows |
预估的需要读取的记录条数 |
filtered |
某个表经过搜索条件过滤后剩余记录条数的百分比 |
Extra |
一些额外的信息 |
在这里把它们都列出来只是为了描述—个轮廓,让大家有一个大致的印象。
6.3 数据准备
.1. 建表
#表一 CREATE TABLE s1 ( id INT AUTO_INCREMENT, key1 VARCHAR(100), key2 INT, key3 VARCHAR(100), key_part1 VARCHAR(100), key_part2 VARCHAR(100), key_part3 VARCHAR(100), common_field VARCHAR(100), PRIMARY KEY (id), INDEX idx_key1 (key1), UNIQUE INDEX idx_key2 (key2), INDEX idx_key3 (key3), INDEX idx_key_part(key_part1, key_part2, key_part3) ) ENGINE=INNODB CHARSET=utf8;
#表二 CREATE TABLE s2 ( id INT AUTO_INCREMENT, key1 VARCHAR(100), key2 INT, key3 VARCHAR(100), key_part1 VARCHAR(100), key_part2 VARCHAR(100), key_part3 VARCHAR(100), common_field VARCHAR(100), PRIMARY KEY (id), INDEX idx_key1 (key1), UNIQUE INDEX idx_key2 (key2), INDEX idx_key3 (key3), INDEX idx_key_part(key_part1, key_part2, key_part3) ) ENGINE=INNODB CHARSET=utf8;