Showing posts with label Optimize. Show all posts
Showing posts with label Optimize. Show all posts

Monday, May 24, 2010

MySQL: Many tables or many databases?

Question:
For a project we having a bunch of data that always have the same structure and is not linked together. There are two approaches to save the data:
* Creating a new database for every pool (about 15-25 tables)
* Creating all the tables in one database and differ the pools by table names.
Which one is easier and faster to handle for MySQL?

Answer:
There should be no significant performance difference between multiple tables in a single database versus multiple tables in separate databases.

In MySQL, databases (standard SQL uses the term "schema" for this) serve chiefly as a namespace for tables. A database has only a few attributes, e.g. the default character set and collation. And that usage of GRANT makes it convenient to control access privileges per database, but that has nothing to do with performance.

You can access tables in any database from a single connection (provided they are managed by the same instance of MySQL Server). You just have to qualify the table name:

SELECT * FROM database17.accounts_table;

This is purely a syntactical difference. It should have no effect on performance.

Regarding storage, you can't organize tables into a file-per-database as @Chris speculates. With the MyISAM storage engine, you always have a file per table. With the InnoDB storage engine, you either have a single set of storage files that amalgamate all tables, or else you have a file per table (this is configured for the whole MySQL server, not per database). In either case, there's no performance advantage or disadvantage to creating the tables in a single database versus many databases.

There aren't many MySQL configuration parameters that work per database. Most parameters that affect server performance are server-wide in scope.

Regarding backups, you can specify a subset of tables as arguments to the mysqldump command. It may be more convenient to back up logical sets of tables per database, without having to name all the tables on the command-line. But it should make no difference to performance, only convenience for you as you enter the backup command.

-- Bill Karwin (the author of SQL Antipatterns from Pragmatic Bookshelf)

Monday, November 09, 2009

Mysql Server System Variables

mysql 数据库中几个系统变量的分析说明

connect_timeout

Command Line Format --connect_timeout=#
Config File Format connect_timeout
Option Sets Variable Yes, connect_timeout
Variable Name connect_timeout
Variable Scope Global
Dynamic Variable Yes
   Permitted Values (<= 5.1.22)
Type numeric
Default 5
   Permitted Values (>= 5.1.23)
Type numeric
Default 10

The number of seconds that the mysqld server waits for a connect packet before responding with Bad handshake. The default value is 10 seconds as of MySQL 5.1.23 and 5 seconds before that.
Increasing the connect_timeout value might help if clients frequently encounter errors of the form Lost connection to MySQL server at 'XXX', system error: errno.
增加此值可以解决此问题:Lost connection to MySQL server at 'XXX', system error: errno.



delay_key_write

Command Line Format --delay-key-write[=name]
Config File Format delay-key-write
Option Sets Variable Yes, delay_key_write
Variable Name delay-key-write
Variable Scope Global
Dynamic Variable Yes
   Permitted Values
Type enumeration
Default ON
Valid Values ON, OFF, ALL

This option applies only to MyISAM tables.
此参数只针对MyISAM表。
It can have one of the following values to affect handling of the DELAY_KEY_WRITE table option that can be used in CREATE TABLE statements.
Option Description
OFF DELAY_KEY_WRITE is ignored.
ON MySQL honors any DELAY_KEY_WRITE option specified in CREATE TABLE statements. This is the default value.
ALL All new opened tables are treated as if they were created with the DELAY_KEY_WRITE option enabled.

If DELAY_KEY_WRITE is enabled for a table, the key buffer is not flushed for the table on every index update, but only when the table is closed. This speeds up writes on keys a lot, but if you use this feature, you should add automatic checking of all MyISAM tables by starting the server with the --myisam-recover option (for example, --myisam-recover=BACKUP,FORCE). See Section 5.1.2, “Server Command Options”, and Section 13.5.1, “MyISAM Startup Options”.
Warning
如果启用了DELAY_KEY_WRITE,说明使用该项的表的键缓冲区在每次更新索引时不被清空,只有关闭表时才清空。遮掩盖可以大大加快键的写操作,但如果你使用该特性,你应用--myisam-recover选项启动服务器,为所有MyISAM表添加自动检查(例如,--myisam-recover=BACKUP,FORCE)。
If you enable external locking with --external-locking, there is no protection against index corruption for tables that use delayed key writes.



expire_logs_days

Command Line Format --expire_logs_days=#
Config File Format expire_logs_days
Option Sets Variable Yes, expire_logs_days
Variable Name expire_logs_days
Variable Scope Global
Dynamic Variable Yes
   Permitted Values
Type numeric
Default 0
Range 0-99

The number of days for automatic binary log removal. The default is 0, which means “no automatic removal.” Possible removals happen at startup and when the binary log is flushed.
二进制日志自动删除的天数,可设置值的范围为0至99。默认值为0,表示“没有自动删除”。启动时和二进制日志循环时可能删除。

init_connect
Command Line Format --init-connect=name
Config File Format init_connect
Option Sets Variable Yes, init_connect
Variable Name init_connect
Variable Scope Global
Dynamic Variable Yes
   Permitted Values
Type string

A string to be executed by the server for each client that connects. The string consists of one or more SQL statements. To specify multiple statements, separate them by semicolon characters. For example, each client begins by default with autocommit mode enabled. There is no global system variable to specify that autocommit should be disabled by default, but init_connect can be used to achieve the same effect:

SET GLOBAL init_connect='SET autocommit=0';

This variable can also be set on the command line or in an option file. To set the variable as just shown using an option file, include these lines:

[mysqld]
init_connect='SET autocommit=0'

Note that the content of init_connect is not executed for users that have the SUPER privilege. This is done so that an erroneous value for init_connect does not prevent all clients from connecting. For example, the value might contain a statement that has a syntax error, thus causing client connections to fail. Not executing init_connect for users that have the SUPER privilege enables them to open a connection and fix the init_connect value.
在这里比较常用的初始化语句是设置一个字符集,当然也可以直接修改my.cnf中的配置参数(character_set_connection/character_set_client等):
[mysqld]
init_connect='SET names utf8'




interactive_timeout

Command Line Format --interactive_timeout=#
Config File Format interactive_timeout
Option Sets Variable Yes, interactive_timeout
Variable Name interactive_timeout
Variable Scope Both
Dynamic Variable Yes
   Permitted Values
Type numeric
Default 28800
Min Value 1

The number of seconds the server waits for activity on an interactive connection before closing it. An interactive client is defined as a client that uses the CLIENT_INTERACTIVE option to mysql_real_connect(). See also wait_timeout.
一个连接如果长时间空闲会被mysql服务端强行关闭掉,其默认的时间是8小时,在用JDBC连接到mysql中,如果池里的连接超过8小时没有使用,就会产生连接异常。

wait_timeout
Command Line Format --wait_timeout=#
Config File Format wait_timeout
Option Sets Variable Yes, wait_timeout
Variable Name wait_timeout
Variable Scope Both
Dynamic Variable Yes
   Permitted Values
Type numeric
Default 28800
Range 1-31536000
   Permitted Values
Type (windows) numeric
Default 28800
Range 1-2147483

The number of seconds the server waits for activity on a noninteractive connection before closing it. This timeout applies only to TCP/IP and Unix socket file connections, not to connections made via named pipes, or shared memory.

On thread startup, the session wait_timeout value is initialized from the global wait_timeout value or from the global interactive_timeout value, depending on the type of client (as defined by the CLIENT_INTERACTIVE connect option to mysql_real_connect()).
这个值一般不需要去设置,只要设置interactive_timeout。



join_buffer_size
Command Line Format --join_buffer_size=#
Config File Format join_buffer_size
Option Sets Variable Yes, join_buffer_size
Variable Name join_buffer_size
Variable Scope Both
Dynamic Variable Yes
   Permitted Values
Platform Bit Size 64
Type numeric
Default 131072
Range 8200-18446744073709547520

The size of the buffer that is used for plain index scans, range index scans, and joins that do not use indexes and thus perform full table scans. Normally, the best way to get fast joins is to add indexes. Increase the value of join_buffer_size to get a faster full join when adding indexes is not possible. One join buffer is allocated for each full join between two tables. For a complex join between several tables for which indexes are not used, multiple join buffers might be necessary.

The maximum allowable setting for join_buffer_size is 4GB. As of MySQL 5.1.23, values larger than 4GB are allowed for 64-bit platforms (except 64-bit Windows, for which large values are truncated to 4GB with a warning).




key_buffer_size

Command Line Format --key_buffer_size=#
Config File Format key_buffer_size
Option Sets Variable Yes, key_buffer_size
Variable Name key_buffer_size
Variable Scope Global
Dynamic Variable Yes
   Permitted Values
Type numeric
Default 8388608
Range 8-4294967295

此参数仅用于MyISAM表中,一般此值可设置为物理内存的25-50%,尽可能的使用 Key_reads/Key_read_requests 的比率小于0.01,最重要的mysql性能调优参数。

Index blocks for MyISAM tables are buffered and are shared by all threads. key_buffer_size is the size of the buffer used for index blocks. The key buffer is also known as the key cache.
MyISAM表的索引块分配了缓冲区,由所有线程共享。key_buffer_size是索引块缓冲区的大小。键值缓冲区即为键值缓存。
The maximum allowable setting for key_buffer_size is 4GB on 32-bit platforms. As of MySQL 5.1.23, values larger than 4GB are allowed for 64-bit platforms, except 64-bit Windows prior to MySQL 5.1.31, for which large values are truncated to 4GB with a warning. As of MySQL 5.1.31, values larger than 4GB are also allowed for 64-bit Windows. The effective maximum size might be less, depending on your available physical RAM and per-process RAM limits imposed by your operating system or hardware platform. The value of this variable indicates the amount of memory requested. Internally, the server allocates as much memory as possible up to this amount, but the actual allocation might be less.
key_buffer_size的最大允许设定值为4GB。有效最大值可以更小,取决于可用物理RAM和操作系统或硬件平台强加的每个进程的RAM限制。
You can increase the value to get better index handling for all reads and multiple writes; on a system whose primary function is to run MySQL using the MyISAM storage engine, 25% of the machine's total memory is an acceptable value for this variable. However, you should be aware that, if you make the value too large (for example, more than 50% of the machine's total memory), your system might start to page and become extremely slow. This is because MySQL relies on the operating system to perform file system caching for data reads, so you must leave some room for the file system cache. You should also consider the memory requirements of any other storage engines that you may be using in addition to MyISAM.
增加该值,达到你可以提供的更好的索引处理(所有读和多个写操作)。通常为主要运行MySQL的机器内存的25%。但是,如果你将该值设得过大(例如,大于总内存的50%),系统将转换为页并变得极慢。MySQL依赖操作系统来执行数据读取时的文件系统缓存,因此你必须为文件系统缓存留一些空间。
For even more speed when writing many rows at the same time, use LOCK TABLES. See Section 7.2.21, “Speed of INSERT Statements”.
同时写多行时要想速度更快,应使用LOCK TABLES。
You can check the performance of the key buffer by issuing a SHOW STATUS statement and examining the Key_read_requests, Key_reads, Key_write_requests, and Key_writes status variables. (See Section 12.5.5, “SHOW Syntax”.) The Key_reads/Key_read_requests ratio should normally be less than 0.01. The Key_writes/Key_write_requests ratio is usually near 1 if you are using mostly updates and deletes, but might be much smaller if you tend to do updates that affect many rows at the same time or if you are using the DELAY_KEY_WRITE table option.

The fraction of the key buffer in use can be determined using key_buffer_size in conjunction with the Key_blocks_unused status variable and the buffer block size, which is available from the key_cache_block_size system variable:

   ((Key_blocks_unused × key_cache_block_size) / key_buffer_size)
  
用key_buffer_size结合Key_blocks_unused状态变量和缓冲区块大小,可以确定使用的键值缓冲区的比例。从key_cache_block_size服务器变量可以获得缓冲区块大小。使用的缓冲区的比例为:
   ((Key_blocks_unused * key_cache_block_size) / key_buffer_size)
该值为约数,因为键值缓冲区的部分空间被分配用作内部管理结构。

可以创建多个MyISAM键值缓存。4GB限制可以适合每个缓存,而不是一个组。
This value is an approximation because some space in the key buffer may be allocated internally for administrative structures.

It is possible to create multiple MyISAM key caches. The size limit of 4GB applies to each cache individually, not as a group. See Section 7.4.5, “The MyISAM Key Cache”.



log_slave_updates

Whether updates received by a slave server from a master server should be logged to the slave's own binary log. Binary logging must be enabled on the slave for this variable to have any effect.
假如mysql slave还用作为master server时,需要设置此参数,记录所有SQL操作,提供其他slave复制。



log_slow_queries
Command Line Format --log-slow-queries[=name]
Config File Format log-slow-queries
Option Sets Variable Yes, log_slow_queries
Variable Name log_slow_queries
Variable Scope Global
Dynamic Variable Yes
   Permitted Values
Type boolean

Whether slow queries should be logged. “Slow” is determined by the value of the long_query_time variable.
记录查询非常慢的SQL到一个独立的日志文件中,可用于SQL分析和优化。




lower_case_file_system
Command Line Format --lower_case_file_system[=#]
Config File Format lower_case_file_system
Option Sets Variable Yes, lower_case_file_system
Variable Name lower_case_file_system
Variable Scope Global
Dynamic Variable No
   Permitted Values
Type boolean

This variable describes the case sensitivity of file names on the file system where the data directory is located. OFF means file names are case sensitive, ON means they are not case sensitive.

lower_case_table_names
Command Line Format --lower_case_table_names[=#]
Config File Format lower_case_table_names
Option Sets Variable Yes, lower_case_table_names
Variable Name lower_case_table_names
Variable Scope Global
Dynamic Variable No
   Permitted Values
Type numeric
Default 0
Range 0-2

If set to 1, table names are stored in lowercase on disk and table name comparisons are not case sensitive. If set to 2 table names are stored as given but compared in lowercase. This option also applies to database names and table aliases. See Section 8.2.2, “Identifier Case Sensitivity”.

这个参数在linux服务器上需要注意,一般是将此值设置为1,使表名全部以小写方式写入硬盘,避免表名的大小写问题。

If you are using InnoDB tables, you should set this variable to 1 on all platforms to force names to be converted to lowercase.

You should not set this variable to 0 if you are running MySQL on a system that does not have case-sensitive file names (such as Windows or Mac OS X). If this variable is not set at startup and the file system on which the data directory is located does not have case-sensitive file names, MySQL automatically sets lower_case_table_names to 2.


max_allowed_packet
Command Line Format --max_allowed_packet=#
Config File Format max_allowed_packet
Option Sets Variable Yes, max_allowed_packet
Variable Name max_allowed_packet
Variable Scope Both
Dynamic Variable Yes
   Permitted Values
Type numeric
Default 1048576
Range 1024-1073741824

The maximum size of one packet or any generated/intermediate string.

The packet message buffer is initialized to net_buffer_length bytes, but can grow up to max_allowed_packet bytes when needed. This value by default is small, to catch large (possibly incorrect) packets.

此值需要是1024的一个倍数值,如果操作的数据有使用很长的字符串和大的BLOB字段,如图片,需要增加此值。
You must increase this value if you are using large BLOB columns or long strings. It should be as big as the largest BLOB you want to use. The protocol limit for max_allowed_packet is 1GB. The value should be a multiple of 1024; nonmultiples are rounded down to the nearest multiple.

When you change the message buffer size by changing the value of the max_allowed_packet variable, you should also change the buffer size on the client side if your client program allows it. On the client side, max_allowed_packet has a default of 1GB. Some programs such as mysql and mysqldump enable you to change the client-side value by setting max_allowed_packet on the command line or in an option file.




max_connect_errors
Command Line Format --max_connect_errors=#
Config File Format max_connect_errors
Option Sets Variable Yes, max_connect_errors
Variable Name max_connect_errors
Variable Scope Global
Dynamic Variable Yes
   Permitted Values
Platform Bit Size 32
Type numeric
Default 10
Range 1-4294967295
   Permitted Values
Platform Bit Size 64
Type numeric
Default 10
Range 1-18446744073709547520

If there are more than this number of interrupted connections from a host, that host is blocked from further connections. You can unblock blocked hosts with the FLUSH HOSTS statement.
如果从某台服务器的连接错误过多,会被mysql服务器阻挡连接。




max_connections
Command Line Format --max_connections=#
Config File Format max_connections
Option Sets Variable Yes, max_connections
Variable Name max_connections
Variable Scope Global
Dynamic Variable Yes
   Permitted Values (<= 5.1.14)
Type numeric
Default 100
   Permitted Values (>= 5.1.15)
Type numeric
Default 151
Range 1-16384
   Permitted Values (>= 5.1.17)
Type numeric
Default 151
Range 1-100000

The number of simultaneous client connections allowed. By default, this is 151, beginning with MySQL 5.1.15. (Previously, the default was 100.)
这个值一般都会调整得大一些,如200或者是500,用于处理并发连接数。如果看到“Too many connections”这样的错误提示就是表示mysql服务器的连接数已经被用完。



max_relay_log_size
Command Line Format --max_relay_log_size=#
Config File Format max_relay_log_size
Option Sets Variable Yes, max_relay_log_size
Variable Name max_relay_log_size
Variable Scope Global
Dynamic Variable Yes
   Permitted Values
Type numeric
Default 0
Range 0-1073741824

If a write by a replication slave to its relay log causes the current log file size to exceed the value of this variable, the slave rotates the relay logs (closes the current file and opens the next one). If max_relay_log_size is 0, the server uses max_binlog_size for both the binary log and the relay log. If max_relay_log_size is greater than 0, it constrains the size of the relay log, which enables you to have different sizes for the two logs. You must set max_relay_log_size to between 4096 bytes and 1GB (inclusive), or to 0. The default value is 0.



net_read_timeout
Command Line Format --net_read_timeout=#
Config File Format net_read_timeout
Option Sets Variable Yes, net_read_timeout
Variable Name net_read_timeout
Variable Scope Both
Dynamic Variable Yes
   Permitted Values
Type numeric
Default 30
Min Value 1

The number of seconds to wait for more data from a connection before aborting the read. This timeout applies only to TCP/IP connections, not to connections made via Unix socket files, named pipes, or shared memory. When the server is reading from the client, net_read_timeout is the timeout value controlling when to abort. When the server is writing to the client, net_write_timeout is the timeout value controlling when to abort.
此参数只针对TCP/IP的mysql连接,当超过此值的秒数后,服务器端会放弃从客户端读取数据。



old_passwords
Command Line Format --old_passwords
Config File Format old-passwords
Option Sets Variable Yes, old_passwords
Variable Name old_passwords
Variable Scope Both
Dynamic Variable Yes
   Permitted Values
Type boolean
Default FALSE

Whether the server should use pre-4.1-style passwords for MySQL user accounts.
如果客户端收到的错误消息为:“Client does not support authentication protocol”,说明服务器使用的是旧的密码格式,需要为用户按旧的格式重设密码。
mysql>SET PASSWORD 'some_user'@'some_host' = OLD_PASSWORD('newpwd');




read_only
Command Line Format --read_only
Config File Format read_only
Option Sets Variable Yes, read_only
Variable Name read_only
Variable Scope Global
Dynamic Variable Yes
   Permitted Values
Type numeric
Default 0

This variable is off by default. When it is enabled, the server allows no updates except from users that have the SUPER privilege or (on a slave server) from updates performed by slave threads. On a slave server, this can be useful to ensure that the slave accepts updates only from its master server and not from clients. This variable does not apply to TEMPORARY tables, nor does it prevent the server from inserting rows into the log tables (see Section 5.2.1, “Selecting General Query and Slow Query Log Output Destinations”).

read_only exists only as a GLOBAL variable, so changes to its value require the SUPER privilege. Changes to read_only on a master server are not replicated to slave servers. The value can be set on a slave server independent of the setting on the master.

As of MySQL 5.1.15, the following conditions apply:

    * If you attempt to enable read_only while you have any explicit locks (acquired with LOCK TABLES) or have a pending transaction, an error occurs.
    * If you attempt to enable read_only while other clients hold explicit table locks or have pending transactions, the attempt blocks until the locks are released and the transactions end. While the attempt to enable read_only is pending, requests by other clients for table locks or to begin transactions also block until read_only has been set.
    * read_only can be enabled while you hold a global read lock (acquired with FLUSH TABLES WITH READ LOCK) because that does not involve table locks.
这个参数用于slave服务器上,可以控制避免同步复制发生问题。在master上设置此值与slave是无关的,二都互相独立。




server_id
Command Line Format --server-id=#
Config File Format server-id
Option Sets Variable Yes, server_id
Variable Name server_id
Variable Scope Global
Dynamic Variable Yes
   Permitted Values
Type numeric
Default 0
Range 0-4294967295

The server ID, used in replication to give each master and slave a unique identity. This variable is set by the --server-id option. For each server participating in replication, you should pick a positive integer in the range from 1 to 232 – 1 to act as that server's ID.
这个参数用在同步复制时,分配给每个mysql server一个独立唯一的ID标识。




skip_networking

This is ON if the server allows only local (non-TCP/IP) connections. On Unix, local connections use a Unix socket file. On Windows, local connections use a named pipe or shared memory. On NetWare, only TCP/IP connections are supported, so do not set this variable to ON. This variable can be set to ON with the --skip-networking option.
这个参数在许多linux发行版中是被打开的,这样如果是通过TCP/IP进行连接的话,是无法连接成功的,需要注释掉这一行设置才可以,或者使用socket进行连接。





slow_query_log

Whether the slow query log is enabled. The value can be 0 (or OFF) to disable the log or 1 (or ON) to enable the log. The default value depends on whether the --slow_query_log option is given (--log-slow-queries before MySQL 5.1.29). The destination for log output is controlled by the log_output system variable; if that value is NONE, no log entries are written even if the log is enabled. The slow_query_log variable was added in MySQL 5.1.12.
用于分析查询效率低下的SQL


slow_query_log_file
Version Introduced 5.1.12
Command Line Format --slow-query-log-file=file_name
Config File Format slow_query_log_file
Option Sets Variable Yes, slow_query_log_file
Variable Name slow_query_log_file
Variable Scope Global
Dynamic Variable Yes
   Permitted Values
Type filename

The name of the slow query log file. The default value is host_name-slow.log, but the initial value can be changed with the --slow_query_log_file option (--log-slow-queries before MySQL 5.1.29). This variable was added in MySQL 5.1.12.


  
sort_buffer_size
Command Line Format --sort_buffer_size=#
Config File Format sort_buffer_size
Option Sets Variable Yes, sort_buffer_size
Variable Name sort_buffer_size
Variable Scope Both
Dynamic Variable Yes
   Permitted Values
Platform Bit Size 32
Type numeric
Default 2097144
Max Value 4294967295
   Permitted Values
Platform Bit Size 64
Type numeric
Default 2097144
Max Value 18446744073709547520

Each thread that needs to do a sort allocates a buffer of this size. Increase this value for faster ORDER BY or GROUP BY operations. See Section B.5.4.4, “Where MySQL Stores Temporary Files”.

The maximum allowable setting for sort_buffer_size is 4GB. As of MySQL 5.1.23, values larger than 4GB are allowed for 64-bit platforms (except 64-bit Windows, for which large values are truncated to 4GB with a warning).
对于SQL中用到order by和group by子句的,提高此值可以增加查询的速度。



table_cache
Version Removed 5.1.3
Version Deprecated 5.1.3
Command Line Format --table_cache=#
Config File Format table_cache
Option Sets Variable Yes, table_cache
Variable Name table_cache
Variable Scope Global
Dynamic Variable Yes
Deprecated 5.1.3, by table_open_cache
   Permitted Values
Type numeric
Default 64
Range 1-524288

This is the old name of table_open_cache before MySQL 5.1.3. From 5.1.3 on, use table_open_cache instead.



table_open_cache
Version Introduced 5.1.3
Command Line Format --table-open-cache=#
Config File Format table_open_cache
Variable Name table_open_cache
Variable Scope Global
Dynamic Variable Yes
   Permitted Values
Type numeric
Default 64
Range 64-524288

The number of open tables for all threads. Increasing this value increases the number of file descriptors that mysqld requires. You can check whether you need to increase the table cache by checking the Opened_tables status variable. See Section 5.1.7, “Server Status Variables”. If the value of Opened_tables is large and you don't do FLUSH TABLES often (which just forces all tables to be closed and reopened), then you should increase the value of the table_open_cache variable. For more information about the table cache, see Section 7.4.7, “How MySQL Opens and Closes Tables”. Before MySQL 5.1.3, this variable is called table_cache.
  


thread_concurrency
Command Line Format --thread_concurrency=#
Config File Format thread_concurrency
Option Sets Variable Yes, thread_concurrency
Variable Name thread_concurrency
Variable Scope Global
Dynamic Variable No
   Permitted Values
Type numeric
Default 10
Range 1-512

This variable is specific to Solaris systems, for which mysqld invokes the thr_setconcurrency() with the variable value. This function enables applications to give the threads system a hint about the desired number of threads that should be run at the same time.

Tuesday, May 12, 2009

Premature optimization

Knuth: The premature optimization is the root of all evil.

Crockford: Make it right before you make it fast.

Monday, September 17, 2007

Mysql Query Optimization

MySQL查询优化讲座之查询优化器原文

  你提交一个查询的时候,MySQL会分析它,看是否可以做一些优化使处理该查询的速度更快。这一部分将介绍查询优化器是如何工作的。如果你想知道MySQL采用的优化手段,可以查看MySQL参考手册。

  当然,MySQL查询优化器也利用了索引,但是它也使用了其它一些信息。例如,如果你提交如下所示的查询,那么无论数据表有多大,MySQL执行它的速度都会非常快:

SELECT * FROM tbl_name WHERE 0;

   在这个例子中,MySQL查看WHERE子句,认识到没有符合查询条件的数据行,因此根本就不考虑搜索数据表。你可以通过提供一个EXPLAIN语句看 到这种情况,这个语句让MySQL显示自己执行的但实际上没有真正地执行的SELECT查询的一些信息。如果要使用EXPLAIN,只需要在 EXPLAIN单词放在SELECT语句的前面:

mysql> EXPLAIN SELECT * FROM tbl_name WHERE 0\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: NULL
type: NULL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: NULL
Extra: Impossible WHERE

  通常情况下,EXPLAIN返回的信息比上面的信息要多一些,还包括用于扫描数据表的索引、使用的联结类型、每张数据表中估计需要检查的数据行数量等非空(NULL)信息。

  优化器是如何工作的

   MySQL查询优化器有几个目标,但是其中最主要的目标是尽可能地使用索引,并且使用最严格的索引来消除尽可能多的数据行。你的最终目标是提交 SELECT语句查找数据行,而不是排除数据行。优化器试图排除数据行的原因在于它排除数据行的速度越快,那么找到与条件匹配的数据行也就越快。如果能够 首先进行最严格的测试,查询就可以执行地更快。假设你的查询检验了两个数据列,每个列上都有索引:

SELECT col3 FROM mytable
WHERE col1 = ’some value’ AND col2 = ’some other value’;

  假设col1上的测试匹配了900个数据行,col2上的测试匹配了300个数据行,而 同时进行的测试只得到了30个数据行。先测试Col1会有900个数据行,需要检查它们找到其中的30个与col2中的值匹配记录,其中就有870次是失 败了。先测试col2会有300个数据行,需要检查它们找到其中的30个与col1中的值匹配的记录,只有270次是失败的,因此需要的计算和磁盘I/O 更少。其结果是,优化器会先测试col2,因为这样做开销更小。

 你可以通过下面一个指导帮助优化器更好地利用索引:

  尽量比较数据类型相同的数据列。当你在比较操作中使用索引数据列的时候,请使用 数据类型相同的列。相同的数据类型比不同类型的性能要高一些。例如,INT与BIGINT是不同的。CHAR(10)被认为是CHAR(10)或 VARCHAR(10),但是与CHAR(12)或VARCHAR(12)不同。如果你所比较的数据列的类型不同,那么可以使用ALTER TABLE来修改其中一个,使它们的类型相匹配。

  尽可能地让索引列在比较表达式中独立。如果你在函数调用或者更复杂的算术表达式条件中使用了某个数据列,MySQL就不会使用索引,因为它必须计算出每个数据行的表达式值。有时候这种情况无法避免,但是很多情况下你可以重新编写一个查询让索引列独立地出现。

  下面的WHERE子句显示了这种情况。它们的功能相同,但是对于优化目标来说就有很大差异了:


WHERE mycol < 4 / 2
WHERE mycol * 2 < 4

   对于第一行,优化器把表达式4/2简化为2,接着使用mycol上的索引来快速地查找小于2的值。对于第二个表达式,MySQL必须检索出每个数据行的 mycol值,乘以2,接着把结果与4进行比较。在这种情况下,不会使用索引。数据列中的每个值都必须被检索到,这样才能计算出比较表达式左边的值。

  我们看另外一个例子。假设你对date_col列进行了索引。如果你提交一条如下所示的查询,就不会使用这个索引:

SELECT * FROM mytbl WHERE YEAR(date_col) < 1990;

   这个表达式不会把1990与索引列进行比较;它会把1990与该数据列计算出来的值比较,而每个数据行都必须计算出这个值。其结果是,没有使用 date_col上的索引,因为执行这样的查询需要全表扫描。怎么解决这个问题呢?只需要使用文本日期,接着就可以使用date_col上的索引来查找列 中匹配的值了:

WHERE date_col < ’1990-01-01’

  但是,假设你没有特定的日期。你可能希望找到一些与今天相隔固定的几天的日期的记录。表达这种类型的比较有很多种方法--它们的效率并不同。下面就有三种:

WHERE TO_DAYS(date_col) - TO_DAYS(CURDATE()) < cutoff
WHERE TO_DAYS(date_col) < cutoff + TO_DAYS(CURDATE())
WHERE date_col < DATE_ADD(CURDATE(), INTERVAL cutoff DAY)

   对于第一行,不会用到索引,因为每个数据行都必须检索以计算出TO_DAYS(date_col)的值。第二行要好一些。Cutoff和TO_DAYS (CURDATE())都是常量,因此在处理查询之前,比较表达式的右边可以被优化器一次性计算出来,而不需要每个数据行都计算一次。但是 date_col列仍然出现在函数调用中,它阻止了索引的使用。第三行是这几个中最好的。同样,在执行查询之前,比较表达式的右边可以作为常量一次性计算 出来,但是现在它的值是一个日期。这个值可以直接与date_col值进行比较,再也不需要转换成天数了。在这种情况下,会使用索引。

  在LIKE模式的开头不要使用通配符。有些字符串搜索使用如下所示的WHERE子句:

WHERE col_name LIKE ’%string%’

   如果你希望找到那些出现在数据列的任何位置的字符串,这个语句就是对的。但是不要因为习惯而简单地把"%"放在字符串的两边。如果你在查找出现在数据列 开头的字符串,就删掉前面的"%"。假设你要查找那些类似MacGregor或MacDougall等以"Mac"开头的名字。在这种情况下,WHERE 子句如下所示:

WHERE last_name LIKE ’Mac%’

  优化器查看该模式中词首的文本,并使用索引找到那些与下面的表达式匹配的数据行。下面的表达式是使用last_name索引的另一种形式:

WHERE last_name >= ’Mac’ AND last_name < ’Mad’

  这种优化不能应用于使用了REGEXP操作符的模式匹配。REGEXP表达式永远不会被优化。

帮助优化器更好的判断索引的效率。在默认情况下,当你把索引列的值与常量进行比较的时候,优化器会假 设键值在索引内部是均匀分布的。在决定进行常量比较是否使用索引的时候,优化器会快速地检查索引,估计出会用到多少个实体(entry)。对应 MyISAM、InnoDB和BDB数据表来说,你可以使用ANALYZE TABLE让服务器执行对键值的分析。它会为优化器提供更好的信息。

  使用EXPLAIN验证优化器的操作。EXPLAIN语句可以告诉你是否使用了索引。当你试图用另外的方式编写语句或检查添加索引是否会提高查询执行效率的时候,这些信息对你是有帮助的。

   在必要的时候给优化器一些提示。正常情况下,MySQL优化器自由地决定扫描数据表的次序来最快地检索数据行。在有些场合中优化器没有作出最佳选择。如 果你察觉这种现象发生了,就可以使用STRAIGHT_JOIN关键字来重载优化器的选择。带有STRAIGHT_JOIN的联结类似于交叉联结,但是强 迫数据表按照FROM子句中指定的次序来联结。

  在SELECT语句中有两个地方可以指定STRAIGHT_JOIN。你可以在SELECT关键字和选择列表之间的位置指定,这样会对语句中所有的交叉联结产生影响;你也可以在FROM子句中指定。下面的两个语句功能相同:

SELECT STRAIGHT_JOIN ... FROM t1, t2, t3 ... ;
SELECT ... FROM t1 STRAIGHT_JOIN t2 STRAIGHT_JOIN t3 ... ;

  分别在带有STRAIGHT_JOIN和不带STRAIGHT_JOIN的情况下运行这个查询;MySQL可能因为什么原因没有按照你认为最好的次序使用索引(你可以使用EXPLAIN来检查MySQL处理每个语句的执行计划)。

  你还可以使用FORCE INDEX、USE INDEX或IGNORE INDEX来指导服务器如何使用索引。

   利用优化器更加完善的区域。MySQL可以执行联结和子查询,但是子查询是最近才支持的,是在MySQL 4.1中添加的。因而在很多情况下,优化器对联结操作的调整比对子查询的调整要好一些。当你的子查询执行地很慢的时候,这就是一条实际的提示。有一些子查 询可以使用逻辑上相等的联结来重新表达。在可行的情况下,你可以把子查询重新改写为联结,看是否执行地快一些。

  测试查询的备用形式, 多次运行。当你测试查询的备用形式的时候(例如,子查询与等同的联结操作对比),每种方式都应该多次运行。如果两种形式都只运行了一次,那么你通常会发现 第二个查询比第一个快,这是因为第一个查询得到的信息仍然保留在缓存中,以至于第二个查询没有真正地从磁盘上读取数据。你还应该在系统负载相对平稳的时候 运行查询,以避免系统中其它的事务影响结果。

  避免过度地使用MySQL自动类型转换。MySQL会执行自动的类型转换,但是如果你能够避免这种转换操作,你得到的性能就更好了。例如,如果num_col是整型数据列,那么下面这些查询将返回相同的结果:

SELECT * FROM mytbl WHERE num_col = 4;
SELECT * FROM mytbl WHERE num_col = ’4’;

  但是第二个查询涉及到了类型转换。转换操作本身为了把整型和字符串型转换为双精度型进行比较,使性能恶化了。更严重的情况是,如果num_col是索引的,那么涉及到类型转换的比较操作不会使用索引。

  相反类型的比较操作(把字符串列与数值比较)也会阻止索引的使用。假设你编写了如下所示的查询:

SELECT * FROM mytbl WHERE str_col = 4;

  在这个例子中,不会使用str_col上的索引,因为在把str_col中的字符串值转换成数值的时候,可能有很多值等于4(例如’4’、’4.0’和’4th’)。分辨哪些值符合要求的唯一办法是读取每个数据行并执行比较操作。

使用EXPLAIN来检查优化器的操作

  EXPLAIN对于了解优化器生成的、用于处理语句的执行计划的内部信息是很有帮助的。在这一部分中,我们将解释EXPLAIN的两种用途:

  · 查看采用不同的方式编写的查询是否影响了索引的使用。

  · 查看向数据表添加索引对优化器生成高效率执行计划的能力的影响。

  这一部分只讨论与示例相关的EXPLAIN输入字段。

  前面,在"优化器是如何工作的"部分中我们得出的观点是,你编写表达式的方式将决定优化器是否能使用可用的索引。特别是上面的讨论使用了下面三个逻辑相等的WHERE子句的例子,只有第三个允许使用索引:


WHERE TO_DAYS(date_col) - TO_DAYS(CURDATE()) < cutoff
WHERE TO_DAYS(date_col) < cutoff + TO_DAYS(CURDATE())
WHERE date_col < DATE_ADD(CURDATE(), INTERVAL cutoff DAY)

   EXPLAIN允许你查看编写表达式的某种方式是否比另外的方式好一些。为了看到结果,让我们分别用这三个WHERE子句搜索成员表中过期的数据列值, 把cutoff值设为30天。为了看到索引的使用和表达式编写方式之间的关系,我们首先对expiration列进行索引:

mysql> ALTER TABLE member ADD INDEX (expiration);

  在每个表达式形式上使用EXPLAIN,看优化器生成了什么样的执行计划:

mysql> EXPLAIN SELECT * FROM MEMBER
-> WHERE TO_DAYS(expiration) - TO_DAYS(CURDATE()) < 30\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: MEMBER
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 102
Extra: Using where
mysql> EXPLAIN SELECT * FROM MEMBER
-> WHERE TO_DAYS(expiration) < 30 + TO_DAYS(CURDATE())\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: MEMBER
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 102
Extra: Using where
mysql> EXPLAIN SELECT * FROM MEMBER
-> WHERE expiration < DATE_ADD(CURDATE(), INTERVAL 30 DAY)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: MEMBER
type: range
possible_keys: expiration
key: expiration
key_len: 4
ref: NULL
rows: 6
Extra: Using where

  上面的结果显示,前面两个语句没有使用索引。类型(type)值表明了将如何从数据表中读取信息。ALL意味着"将检查所有的记录"。也就是说,它会执行全表扫描,没有利用索引。每个与键相关的列都是NULL也表明没有使用索引。

  与此形成对比的是,第三个语句的结果显示,采用这种方式编写的WHERE子句,优化器可以使用expiration列上的索引:

  · 类型(type)值表明它可以使用索引来搜索特定范围的值(小于右边表达式给定的值)。

  · 可能键(possible_keys)和键(key)值显示expiration上的索引已经被考虑作为备选索引,并且它也是真正使用的索引。

  · 行数(rows)值显示优化器估计自己需要检查6个数据行来处理该查询。这比前面两个执行计划的102小很多。

  EXPLAIN的第二种用途是查看添加索引是否能帮助优化器更高效率地执行语句。我将使用两个未被索引的数据表。它足够显示建立索引的效率。相同的规则可以应用于涉及多表的更加复杂的联结操作。

  假设我们有两个数据表t1和t2,每个有1000行,包含的值从1到1000。下面的查询查找出两个表中值相同的数据行:

mysql> SELECT t1.i1, t2.i2 FROM t1, t2 WHERE t1.i1 = t2.i2;
+------+------+
| i1 | i2 |
+------+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
...

  两个表都没有索引的时候,EXPLAIN产生下面的结果:

mysql> EXPLAIN SELECT t1.i1, t2.i2 FROM t1, t2 WHERE t1.i1 = t2.i2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1000
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: t2
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1000
Extra: Using where

  类型列中的ALL表明要进行检查所有数据行的全表扫描。可能键列中的NULL表明没有找到用于提高查询速度的备选索引(键、键长度和参考列都是NULL也是因为缺少合适的索引)。Using where表明使用WHERE子句中的信息来识别合格的数据行。

  这段信息告诉我们,优化器没有为提高执行查询的效率找到任何有用的信息:

  · 它将对t1表进行全表扫描。

  · 对于t1中的每一行,它将执行t2的全表扫描,使用WHERE子句中的信息识别出合格的行。

   行数值显示了优化器估计的每个阶段查询需要检查的行数。T1的估计值是1000,因为1000可以完成全表扫描。相似地,t2的估计值也是1000,但 是这个值是对于t1的每一行的。换句话说,优化器所估计的处理该查询所需要检查的数据行组合的数量是1000×1000,也就是一百万。这会造成很大的浪 费,因为实际上只有1000个组合符合WHERE子句的条件。
为了使这个查询的效率更高,给其中一个联结列添加索引并重新执行EXPLAIN语句:


mysql> ALTER TABLE t2 ADD INDEX (i2);
mysql> EXPLAIN SELECT t1.i1, t2.i2 FROM t1, t2 WHERE t1.i1 = t2.i2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1000
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: t2
type: ref
possible_keys: i2
key: i2
key_len: 5
ref: sampdb.t1.i1
rows: 10
Extra: Using where; Using index

  我们可以看到性能提高了。T1的输出没有改变(表明还是需要进行全表扫描),但是优化器处理t2的方式就有所不同了:

  · 类型从ALL改变为ref,意味着可以使用参考值(来自t1的值)来执行索引查找,定位t2中合格的数据行。

  · 参考值在参考(ref)字段中给出了:sampdb.t1.i1。

   · 行数值从1000降低到了10,显示出优化器相信对于t1中的每一行,它只需要检查t2中的10行(这是一个悲观的估计值。实际上,在t2中只有一行与 t1中数据行匹配。我们在后面会看到如何帮助优化器改善这个估计值)。数据行组合的全部估计值使1000×10=10000。它比前面的没有索引的时候估 计出来的一百万好多了。

  对t1进行索引有价值吗?实际上,对于这个特定的联结操作,扫描一张表是必要的,因此没有必要对t1建立索引。如果你想看到效果,可以索引t1.i1并再次运行EXPLAIN:

mysql> ALTER TABLE t1 ADD INDEX (i1);
mysql> EXPLAIN SELECT t1.i1, t2.i2 FROM t1, t2 WHERE t1.i1 = t2.i2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: index
possible_keys: i1
key: i1
key_len: 5
ref: NULL
rows: 1000
Extra: Using index
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: t2
type: ref
possible_keys: i2
key: i2
key_len: 5
ref: sampdb.t1.i1
rows: 10
Extra: Using where; Using index

   上面的输出与前面的EXPLAIN的输出相似,但是添加索引对t1的输出有一些改变。类型从NULL改成了index,附加(Extra)从空的改成了 Using index。这些改变表明,尽管对索引的值仍然需要执行全表扫描,但是优化器还是可以直接从索引文件中读取值,根据不需要使用数据文件。你可以从 MyISAM表中看到这类结果,在这种情况下,优化器知道自己只询问索引文件就能够得到所有需要的信息。对于InnoDB 和BDB表也有这样的结果,在这种情况下优化器可以单独使用索引中的信息而不用搜索数据行。

  我们可以运行ANALYZE TABLE使优化器进一步优化估计值。这会引起服务器生成键值的静态分布。分析上面的表并再次运行EXPLAIN得到了更好的估计值:

mysql> ANALYZE TABLE t1, t2;
mysql> EXPLAIN SELECT t1.i1, t2.i2 FROM t1, t2 WHERE t1.i1 = t2.i2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: index
possible_keys: i1
key: i1
key_len: 5
ref: NULL
rows: 1000
Extra: Using index
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: t2
type: ref
possible_keys: i2
key: i2
key_len: 5
ref: sampdb.t1.i1
rows: 1
Extra: Using where; Using index

  在这种情况下,优化器估计在t2中与t1的每个值匹配的数据行只有一个。
重载优化过程

  这个过程听起来多余,但是有时候你还是希望去掉某些MySQL优化行为的:

   重载优化器的表联结次序。使用STRAIGHT_JOIN强迫优化器按照特定的次序使用数据表。在这样操作的时候,你必须对数据表进行排序,这样才能保 证第一张表是被选择的行数最少的表。如果你不能确定被选择行数最少的是哪一张表,那么就把行数最多的放到第一的位置。换句话说,试着对表进行排序,使最有 约束力的选择出现在最前面。你对可能的备选数据行缩小地越早,执行查询的性能就越好。请确保在带有STRAIGHT_JOIN和不带 STRAIGHT_JOIN的时候分别执行该查询。有时候由于某些原因的存在,优化器没有按照你认定的方式联结数据表,STRAIGHT_JOIN也可能 没有实际的帮助作用。

  另一个可能性是在联结的数据表列表中的某个表的后面使用FORCE INDEX、USE INDEX和IGNORE INDEX调节符来告诉MySQL如何使用索引。这在优化器没有做出正确选择的时候是有用处的。

  以最小的代价清空一张表。当需要完全地清空一张MyISAM数据表的时候,最快的方法是删除它并利用它的.frm文件中存储的脚本来重新建立它。使用TRUNCATE TABLE语句实现:


TRUNCATE TABLE tbl_name;

  通过重新建立MyISAM数据表来清空它的这种服务器优化措施使该操作非常快,因为不需要单独地逐行删除。

  但是TRUNCATE TABLE也带来了一些副作用,在某些环境中是不符合要求的:

  · TRUNCATE TABLE不一定能够计算出被删除的数据列的精确数量。如果你需要这个数值,请使用不带WHERE子句的DELETE语句:

DELETE FROM tbl_name;

  · 但是,通过重新建立来清空数据表,它可能会把序号的起始值设置为1。为了避免这种情况,请使用"不优化的"全表DELETE语句,它带有一个恒为真的WHERE子句:

DELETE FROM tbl_name WHERE 1;

  添加WHERE子句会强迫MySQL进行逐行删除,因为它必须计算出每一行的值来判断是否能够删除它。这个语句执行的速度很慢,但是它却保留了当前的AUTO_INCREMENT序号。

Optimizing your MySQL Application

Optimizing your MySQL Application



By Mike Sullivan


May 21st 2001


Reader Rating: 9



So you’ve finished reading Kevin Yank’s article Building a Database-Driven Web Site Using PHP and MySQL [1], and you’re happily databasing your site, when it starts to slow down. You need to get your site zipping along again before your host threatens to kick you off for almost killing their server. How do you do this? Enter: MySQL’s internal turbo-charger, indexes.


Disclaimer


I've attempted to keep my queries as simple as possible, but I assume that you have a basic understanding of databases and the SQL language (more specifically, MySQL’s implementation of it). I also assume you have MySQL 3.23, as a few of these queries may not work on 3.22. If you don’t have MySQL 3.23 yet, I highly recommend you install it if possible, as some of the performance increases are significant.


What are Indexes?

Indexes are organized versions of specific columns in your tables. MySQL uses indexes to facilitate quick retrieval of records. With indexes, MySQL can jump directly to the records you want. Without any indexes, MySQL has to read the entire data file to find the correct record(s). Here’s an example.


Suppose we created a table called "people":



CREATE TABLE people (
 peopleid SMALLINT NOT NULL,
 name CHAR(50) NOT NULL
);


Then we insert 1000 different names into the table in a completely random, non-alphabetic order. A small portion of the data file may be represented like this:


402table1


As you can see, there’s no recognizable order to the “name” column whatsoever. If we create an index on the “name” column, MySQL will automatically order this index alphabetically:


402table2



For each entry in the index, MySQL also internally maintains a “pointer” to the correct row in the actual data file. So if I want to get the value of peopleid when the name is Mike (SELECT peopleid FROM people WHERE name='Mike';), MySQL can look in the name index for Mike, jump directly to the correct row in the data file, and return the correct value of peopleid (999). MySQL only has to look at one row to get the result. Without an index on “name”, MySQL would’ve scanned all 1000 rows in the data file! In general, the less rows MySQL has to evaluate, the quicker it can do its job.


Types of Indexes

There are several types of indexes to choose from in MySQL:


[Note: Full query lists and examples can be found at the end of this article.]



"Normal" Indexes – "Normal" indexes are the most basic indexes, and have no restraints such as uniqueness. These can be added by creating an index (CREATE INDEX name_of_index ON tablename (columns_to_index);), altering the table (ALTER TABLE tablename ADD INDEX [name_of_index] (columns_to_index);), or when creating the table (CREATE TABLE tablename ( [...], INDEX [name_of_index] (columns_to_index) );).


Unique Indexes – Unique indexes are the same as "Normal" indexes with one difference: all values of the indexed column(s) must only occur once. These can be added by creating an index (CREATE UNIQUE INDEX name_of_index ON tablename (columns_to_index);), altering the table (ALTER TABLE tablename ADD UNIQUE [name_of_index] (columns_to_index);) or when creating the table (CREATE TABLE tablename ( [...], UNIQUE [name_of_index] (columns_to_index) );).


Primary keys – Primary keys are unique indexes that must be named “PRIMARY”. If you have used AUTO_INCREMENT columns, you’re probably familiar with these. These indexes are almost always added when creating the table (CREATE TABLE tablename ( [...], PRIMARY KEY (columns_to_index) );), but may also be added by altering the table (ALTER TABLE tablename ADD PRIMARY KEY (columns_to_index);). Note that you may only have one primary key per table.


Full-text indexes – Full-text indexes are used by MySQL in full-text searches. Because full-text search is so new and would add unnecessary complexity to this article, I won't explain it here. Should you want more information, visit the MySQL documentation [2].


Single- vs. Multi-column Indexes

You’ve probably noticed that in the CREATE INDEX, ALTER TABLE and CREATE TABLE queries above, I made references to columns (plural). Again, this can be best explained by an example.


Here’s a more complex version of the people table:


CREATE TABLE people (
 peopleid SMALLINT NOT NULL AUTO_INCREMENT,
 firstname CHAR(50) NOT NULL,
 lastname CHAR(50) NOT NULL,
 age SMALLINT NOT NULL,
 townid SMALLINT NOT NULL,
 PRIMARY KEY (peopleid)
);



(Note that because “peopleid” is an AUTO_INCREMENT field, it must be declared the primary key)



A small snippet of the data we insert may look like this (ignore townid for now):



402table3


From this snippet, we have four Mikes (two Sullivans, two McConnells), two 17 year olds, and an unrelated odd ball (Joe Smith).


My intended use for this table is to get the peopleid for users with a specific first name, last name, and age. For example, I want to find the peopleid for Mike Sullivan, aged 17 (SELECT peopleid FROM people WHERE firstname='Mike' AND lastname='Sullivan' AND age=17;). Since I don’t want to have MySQL do a full table scan, I need to look into some indexing.


My first option is to create an index on a single column, firstname, lastname, or age. If I put the index on firstname (ALTER TABLE people ADD INDEX firstname (firstname);), MySQL will use the index to limit the records to those where firstname=’Mike’. Using this “temporary result set,” MySQL will apply each additional condition individually. First it eliminates those whose last name isn’t Sullivan. Then it eliminates those who aren’t 17. MySQL has now applied all conditions and can return the results.


This is more efficient than forcing MySQL to do a full table scan, but we’re still forcing MySQL to scan significantly more rows than it needs to. We could drop the index on firstname and add an index on lastname or age, but the results would be very similar.


Here's where multi-column indexes come into play. If we add a single index on three columns, we can get the correct set in a single pass! Here is the code I use to add this index:


ALTER TABLE people ADD INDEX fname_lname_age (firstname,lastname,age);


Since the index file is organized, MySQL can jump directly to the correct first name, then move to the correct last name, and finally go directly to the correct age. MySQL has found the correct rows without having to scan a single row of the data file!


Now, you’re probably wondering if creating three single-column indexes on (firstname), (lastname), and (age) is the same as one multi-column index on (firstname,lastname,age). No: it's completely different. When running a query, MySQL can only use one index. If you have three single-column indexes, MySQL will attempt to pick the most restrictive one, but the most restrictive single-column index will be significantly less restrictive than our multi-column index on (firstname,lastname,age).


Leftmost Prefixing

Multi-column indexes provide an additional benefit through what is known as leftmost prefixing. To continue our previous example, we have a three-column index on (firstname,lastname,age), which I have nicknamed “fname_lname_age” (I’ll explain more about that later). This index will be used when searching the following combination of columns:




  • firstname,lastname,age

  • firstname,lastname

  • firstname



To put it another way, we have basically created indexes on (firstname,lastname,age), (firstname,lastname), and just (firstname). The following queries can use the index:



SELECT peopleid FROM people WHERE firstname=’Mike’  
  AND lastname=’Sullivan’ AND age=’17’;
SELECT peopleid FROM people WHERE firstname=’Mike’
  AND lastname=’Sullivan’;
SELECT peopleid FROM people WHERE firstname=’Mike’;


The following queries cannot use the index at all:



SELECT peopleid FROM people WHERE lastname=’Sullivan’;
SELECT peopleid FROM people WHERE age=’17’;
SELECT peopleid FROM people WHERE lastname=’Sullivan’
  AND age=’17’;


How to Pick Columns to Index

One of the most important steps in optimizing is selecting which columns to index. There are two major places you want to consider indexing: columns you reference in the WHERE clause and columns used in join clauses. Look at the following query:



SELECT
     age          ## no use indexing
FROM
     people
WHERE
     firstname='Mike'        ## consider indexing
    AND
     lastname='Sullivan'      ## consider indexing


This query is a little different from the past ones, but it’s still quite simple. Since “age” is referenced in the SELECT portion, MySQL will not use it to limit the chosen rows. Hence, there is no great need to index it. Here’s a more complex example:


SELECT
     people.age,        ## no use indexing
     town.name        ## no use indexing
FROM
     people
LEFT JOIN
     town
   ON
     people.townid=town.townid      ## consider indexing
           ##       town.townid
WHERE
     firstname='Mike'        ## consider indexing
    AND
     lastname='Sullivan'      ## consider indexing


The possibility of indexing firstname and lastname carries over as they are again located in the WHERE clause. An additional field you'll want to consider indexing is the townid field from town table (please note that I’m only using the town table as an example of a join) because it is in a join clause.


“So I simply consider indexing every field in the WHERE clause or a join clause?” Almost, but not quite. Next, you need to consider the type of comparisons your doing on the fields. MySQL will only use indexes for '<', '<=', '=', '>', '>=', BETWEEN, IN, and some LIKE operations. These specific LIKE operations are times where the first character is not a wildcard (% or _). SELECT peopleid FROM people WHERE firstname LIKE 'Mich%'; would use an index, but SELECT peopleid FROM people WHERE firstname LIKE '%ike'; wouldn’t.


Analyzing Index Efficiency

You have some ideas on which indexes to use, but you’re not sure which is the most efficient. Well, you’re in luck, because MySQL has a built-in SQL statement to do this, known as EXPLAIN. The general syntax for this is EXPLAIN select statement;. You can find more information in the MySQL documentation [3]. Here’s an example:



EXPLAIN SELECT peopleid FROM people WHERE firstname='Mike'  
  AND lastname='Sullivan' AND age='17';


This will return a somewhat cryptic result that will look usually look similar to this:



[Note: table split across two rows for readability]

+--------+------+-----------------+-----------------+
| table  | type | possible_keys   | key             |
+--------+------+-----------------+-----------------+  ...
| people | ref  | fname_lname_age | fname_lname_age |
+--------+------+-----------------+-----------------+

   +---------+-------------------+------+------------+
   | key_len | ref               | rows | Extra      |
... +---------+-------------------+------+------------+
   | 102     | const,const,const | 1    | Where used |
   +---------+-------------------+------+------------+


Let's break this down column by column.



table - This is the name of the table. This will become important when you have large joins, as each table will get a row.
type - The type of the join. Here's what the MySQL documentation has to say about the ref type:
All rows with matching index values will be read from this table for each combination of rows from the previous tables. ref is used if the join uses only a leftmost prefix of the key, or if the key is not UNIQUE or a PRIMARY KEY (in other words, if the join cannot select a single row based on the key value). If the key that is used matches only a few rows, this join type is good.
In this case, since our index isn’t UNIQUE, this is the best join type we can get.
In summary, if the join type is listed as “ALL” and you aren’t trying to select most of the rows in the table, then MySQL is doing a full table scan which is usually very bad. You can fix this by adding more indexes. If you want more information, the MySQL manual covers this value with much more depth.
possible_keys - The name of the indexes that could possibly be used. This is where nicknaming your index helps. If you leave the name field blank, the name defaults to the name of the first column in the index (in this case, it would be “firstname”), which isn’t very descriptive.
key - This shows the name of the index that MySQL actually uses. If this is empty (or NULL), then MySQL isn’t using an index.
key_len - The length, in bytes, of the parts of the index being used. In this case, it’s 102 because firstname takes 50 bytes, lastname takes 50, and age takes 2. If MySQL were only using the firstname part of the index, this would be 50.
ref - This shows the name of the columns (or the word “const”) that MySQL will use to select the rows. Here, MySQL references three constants to find the rows.
rows - The number of rows MySQL thinks it has to go through before knowing it has the correct rows. Obviously, one is the best you can get.
Extra - There are many different options here, most of which will have an adverse effect on the query. In this case, MySQL is simply reminding us that it used the WHERE clause to limit the results.


Disadvantages of Indexing

So far, I’ve only discussed why indexes are great. However, they do have several disadvantages.


First, they take up disk space. Usually this isn’t significant, but if you decided to index every column in every possible combination, your index file would grow much more quickly than the data file. If you have a large table, the index file could reach your operating system’s maximum file size.


Second, they slow down the speed of writing queries, such as DELETE, UPDATE, and INSERT. This is because not only does MySQL have to write to the data file, it has to write everything to the index file as well. However, you may be able to write your queries in such a way that the performance degradation is not very noticeable.


Conclusion

Indexes are one of the keys to speed in large databases. No matter how simple your table, a 500,000-row table scan will never be fast. If you have a site with a 500,000-row table, you should really spend time analyzing possible indexes and possibly consider rewriting queries to optimize your application.


As always, there is more to indexing than I covered in this article. More information can be found in the official MySQL manual [4], or in Paul DuBois’ great book, MySQL [5].


Query Reference


Adding a “normal” index via CREATE INDEX:
CREATE INDEX [index_name] ON tablename (index_columns); Example: CREATE INDEX fname_lname_age ON people (firstname,lastname,age);



Adding a unique index via CREATE INDEX:
CREATE UNIQUE INDEX [index_name] ON tablename (index_columns); Example: CREATE UNIQUE INDEX fname_lname_age ON people (firstname,lastname,age);



Adding a “normal” index via ALTER TABLE:
ALTER TABLE tablename ADD INDEX [index_name] (index_columns); Example: ALTER TABLE people ADD INDEX fname_lname_age (firstname,lastname,age);



Adding a unique index via ALTER TABLE:
ALTER TABLE tablename ADD UNIQUE [index_name] (index_columns); Example: ALTER TABLE people ADD UNIQUE fname_lname_age (firstname,lastname,age);



Adding a primary key via ALTER TABLE:
ALTER TABLE tablename ADD PRIMARY KEY (index_columns); Example: ALTER TABLE people ADD PRIMARY KEY (peopleid);



Adding a “normal” index via CREATE TABLE:
CREATE TABLE tablename (
 rest of columns,
 INDEX [index_name] (index_columns)
 [other indexes]
);
Example:
CREATE TABLE people (
 peopleid SMALLINT UNSIGNED NOT NULL,
 firstname CHAR(50) NOT NULL,
 lastname CHAR(50) NOT NULL,
 age SMALLINT NOT NULL,
 townid SMALLINT NOT NULL,
 INDEX fname_lname_age (firstname,lastname,age)
);



Adding a unique index via CREATE TABLE:
CREATE TABLE tablename (
 rest of columns,
 UNIQUE [index_name] (index_columns)
 [other indexes]
);
Example:
CREATE TABLE people (
 peopleid SMALLINT UNSIGNED NOT NULL,
 firstname CHAR(50) NOT NULL,
 lastname CHAR(50) NOT NULL,
 age SMALLINT NOT NULL,
 townid SMALLINT NOT NULL,
 UNIQUE fname_lname_age (firstname,lastname,age)
);



Adding a primary key via CREATE TABLE:
CREATE TABLE tablename (
 rest of columns,
 INDEX [index_name] (index_columns)
 [other indexes]
);
Example:
CREATE TABLE people (
 peopleid SMALLINT NOT NULL AUTO_INCREMENT,
 firstname CHAR(50) NOT NULL,
 lastname CHAR(50) NOT NULL,
 age SMALLINT NOT NULL,
 townid SMALLINT NOT NULL,
 PRIMARY KEY (peopleid)
);



Dropping (removing) a “normal” or unique index via ALTER TABLE:
ALTER TABLE tablename DROP INDEX index_name; Example: ALTER TABLE people DROP INDEX fname_lname_age;



Dropping (removing) a primary key via ALTER TABLE:
ALTER TABLE tablename DROP PRIMARY KEY; Example: ALTER TABLE people DROP PRIMARY KEY;



[1] http://www.webmasterbase.com/article.php/228
[2] http://www.mysql.com/doc/F/u/Fulltext_Search.html
[3] http://www.mysql.com/doc/E/X/EXPLAIN.html
[4] http://www.mysql.com/doc/
[5] http://www.webmasterbase.com/article.php/225


source article