2011年12月7日星期三

设置Heartbeat + DRBD + MySQL

有两台虚拟机, DB1:192.168.56.101, DB2:192.168.56.102, 拟设虚拟IP为192.168.56.100. 使用/dev/sdb作为DRBD盘
DB1 & DB2: /etc/hosts
127.0.0.1               localhost.localdomain localhost
::1             localhost6.localdomain6 localhost6
192.168.56.101  db1
192.168.56.102  db2
DB1:/etc/sysconfig/network
NETWORKING=yes
NETWORKING_IPV6=no
HOSTNAME=db1
DB2:/etc/sysconfig/network
NETWORKING=yes
NETWORKING_IPV6=no
HOSTNAME=db2
DB1:/etc/sysconfig/network-scripts/ifcfg-eth1
DEVICE=eth1
BOOTPROTO=static
ONBOOT=yes
HWADDR=08:00:27:bb:7d:71
IPADDR=192.168.56.101
NETMASK=255.255.255.0
GATEWAY=192.168.56.1
DB2:/etc/sysconfig/network-scripts/ifcfg-eth1
DEVICE=eth1
BOOTPROTO=static
ONBOOT=yes
HWADDR=08:00:27:bb:7d:71
IPADDR=192.168.56.102
NETMASK=255.255.255.0
GATEWAY=192.168.56.1

安装DRBD以及HEARTBEAT
DB1 & DB2:
yum install -y drbd83 kmod-drbd83
yum install -y heartbeat heartbeat-ldirectord heartbeat-pils heartbeat-stonith

配置DRBD
DB1 & DB2: 修改/etc/drbd.conf
主要是配置两个node
on db1{
    device     /dev/drbd0;
    disk       /dev/sdb1;
    address    192.168.56.101:7788;
    meta-disk  internal;
  }
  on db2{
    device    /dev/drbd0;
    disk      /dev/sdb1;
    address   192.168.56.102:7788;
    meta-disk internal;
  }
DB1 & DB2:
在/dev/sdb上建立分区 /dev/sdb1/, 并创建文件系统
/etc/init.d/drbd restart
drbdadm create-md all
/etc/init.d/drbd restart

DB1:
drbdsetup /dev/drbd0 primary -o
mkfs.ext3 /dev/drbd0
mkdir /data
mount /dev/drbd0 /data/
drbdadm primary all

DB2:
mkdir /data
检查DRBD切换:
DB1:
umount /data
drbdadm secondary all
DB2:
drbdadm primary all
mount /dev/drbd0 /data

在DB1 & DB2上安装mysql, 放在/data下面

配置HEARTBEAT, 主要编辑3个文件:
DB1 & DB2:
/etc/ha.d/authkeys
auth 3
3 md5 HelloDRBD

/etc/ha.d/haresources, 这里可以都设置成db1, 表示db1为主node, 也可以各自设子自己的db name, 这样就没有主从了, 哪个先获得资源就以哪个为主
db1 IPaddr::192.168.56.100/24/eth0 drbddisk::r0 Filesystem::/dev/drbd0::/data::ext3 mysql

不过这样配置后虚拟IP只能在本网段内被访问, 如果需要被其他网段访问, 还必须在网络配置中手动加上网关:
vi /etc/network/interfaces

auto em1:0
iface em1:0 inet static
address 192.168.56.100
netmask 255.255.255.0
gateway 192.168.56.1

DB1:
/etc/ha.d/ha.cf
debugfile /var/log/ha-debug
logfile /var/log/ha-log
logfacility     local0
keepalive 2
deadtime 30
warntime 10
initdead 60
udpport 694
baud    19200
ucast eth1 192.168.56.102 (对方的心跳地址)
auto_failback on
node    db1
node    db2

DB2:
debugfile /var/log/ha-debug
logfile /var/log/ha-log
logfacility     local0
keepalive 2
deadtime 30
warntime 10
initdead 60
udpport 694
baud    19200
ucast eth1 192.168.56.101(对方的心跳地址)
auto_failback on
node    db1
node    db2

DB1 & DB2:
service heartbeat restart

查看当前哪个server为主, 然后将该server的heartbeat服务停掉或者关机或者eth1 down, 观察另外一台是否能够切换为主server.

2011年11月17日星期四

二进制日志中语句的顺序

今天看到文档上说二进制日志中语句的顺序会按照commit的顺序出现, 于是我猜想这样如果设置了slave的话, 因为slave都是单线程执行的, 那么执行的顺序是否也会按照commit的顺序执行.
由此, 一个auto increment的表, 如果首先插入的后commit, 会不会到slave端后执行, 从而最终产生的auto increment值会与master不一样.
结果证明, 不会!!

slave段的数据与master端的数据是一致的.
呵呵, 看来是我自己太....了.

2011年11月11日星期五

sql server2008丢失管理权限

今天又台sql server2008的server不小心将login中的本机administrator用户删掉了, 而sa密码又不记得了, 结果登录以后没有权限做任何修改.

按照网上一篇文章的办法成功找回:
1. server上建一个administrators组的用户
2. 用这个用户登录操作系统, 停掉mssqlserver服务, 并以单用户模式启动mssqlserver
3. 以windows认证方式登录sql server, 将administrator加回到login
4. 以adminisstrator重新登录系统, 并重新启动sql server服务.

2011年11月10日星期四

MySQL怎样使用内存

今天看了mysql官方文档的使用内存一项, 觉得比较有用, 遂翻译了下.
原文地址:http://dev.mysql.com/doc/refman/5.1/en/memory-use.html

1. 所有进程共享myisam key buffer, 该buffer由key_buffer_size决定
2. 进程自己的内存有
一个栈, thread_stack
一个连接缓存, net_buffer_length
一个结果缓存, net_buffer_length
这两个buffer最初大小为net_buffer_length指定的大小, 可以动态扩展到max_allowed_packet,    
语句结束以后, 回缩回到net_buffer_length的大小
所有进程共享基础内存
当进程不再使用, 内存即释放, 除非其进入thread cache
5.1.4之前, 只有压缩的myisam表才可以使用memeory map, 但是5.1.4以后, 可以将myisam_use_map设为1, 使所有的myisam表均可以使用memory map'
每一个对表进行顺序扫描的进程都会分配一个read buffer, read_buffer_size
当以字面顺序来读取表的内容的时候, 可能会分配一个random-read buffer, 有read_rnd_buffer_size决定
所有的join操作都在一个单独的通道内完成, 大部分的排序甚至不需要用到临时表就可以完成, 大部分的临时表都是存储在内存中的hash表, 当行长度过大或者是包含blob类型的数据会存储在磁盘上.
当一个内存临时表过大, 就会转化为一个磁盘表, 并且也会转换为myisam, 可以修改允许的临时表大小
需要排序的请求会分配一个sort buffer, 并且产生0-2个临时文件, 这点取决于结果集大小
几乎所有的分析以及计算都会在一个可重用的进程内的内存池中完成,
当一个myisam表被打开的时候, 其索引只会被打开一次, 但是每个并行访问的进程都会分别打开数据文件. 对每个并行进程, 会分配表结构区, 列结构区, 一个一个3*n大小的buffer(n为最大行长度, 不包含blob列) 每个blob column会需要blob数据的长度加上5到8个字节的内存大小. myisam引擎还会需要另外一个buffer来作为内部使用.
对于每一个含有blob列的表, 会有一个额外的buffer来存储blob数据, 并且能够动态扩展
所有当前在使用的表的结构, 都会存储在缓存中, 并且使用FIFO来管理, 初始大小为table_open_cache, 如果两个表被同时打开, 那么在该缓存中会存在该表的两个入口.
使用flush tables语句或者mysqladmin flushtables命令会关闭所有当前未被使用的表, 而当前在使用的表也会被标记为使用结束后立即关闭, 这样可以有效的释放绝大多数当前在使用的内存. 知道所有的表都被关闭, 该命令才会返回
Grant, Create User, Create Server, Install Plugin的结构都会缓存在内存中, 对应的revoke, drop user, drop server, uninstall plugin语句也不会释放这些内存, 所以如果前面这些语句执行过多, 对应的内存占用也会增大.

2011年11月9日星期三

再谈read_rnd_buffer_size参数

<MySQL性能调优与架构设计>一书中在讲到read_rnd_buffer_size的时候, 说它是进行random scan的时候使用到的buffer, 但是我记得前面我读到过一篇资料, 说read_rnd_buffer_siza仅仅是在order by的时候能够起到有限的作用.
于是翻了一下前面的文章, 并且也参阅了mysql官方文档中<How MySQL Uses Memory>一部分, 发现那片文章中讲read_buffer_size是只是for MYISAM用的其实也是不正确的, 因为官方文档中说了, 这两个参数都是适用与所有存储引擎的.(不过那篇文章是07年写的, 那个时候也许还真是只for MYISAM)

说道的两篇文章参见:
其实read_rnd_buffer_size的作用在这两篇文张里面已经说得明白了, 我这里补充记录一下:
在第一种排序算法中, 因为放在sort buffer中的只有参与排序的列以及row point, 因为排序以后, row point是乱的, 是随机的, 所以直接按照排序的顺序去读取需要的数据会比较慢, 因为是随机读取. 而read_rnd_buffer_size的作用就在于将排序以后的列以及row point放到read_rnd_buffer中, 按照row point再进行一下排序, 然后按照这个顺序去读取数据, 运气好的话, 排序以后的row point对应的数据也是连续的(比如说按照类似于creation date这样的列进行排序), 这样读起来就会比较快了.

我觉得, 这样按照row point读取出来的数据有不是query需要的排序了, 所以还要按照原来sort buffer中的顺序再组织一下, 这一步应该也会带来一些cost.

key cache预加载

来自sky.jian<MySQL性能调优与架构设计>
mysql@sky:~$ cat /usr/local/mysql/etc/init.sql
SET GLOBAL hot_cache.key_buffer_size=16777216
SET GLOBAL cold_cache.key_buffer_size=16777216
CACHE INDEX example.top_message in hot_cache
CACHE INDEX example.event in cold_cache
LOAD INDEX INTO CACHE example.top_message,example.event IGNORE LEAVES
LOAD INDEX INTO CACHE example.user IGNORE LEAVES,exmple.groups

我发现重设key buffer size的时候, 会将key buffer中的数据全部清空.

MySQL的多key cache

我知道oracle中有keep buffer, recycle buffer, 接触MySQL后不知道mysql中有没有这样的机制, 今天知道mysql的key buffer cache也是有这种机制的.
首先设置一个另外的key cache:
SET GLOBAL hot_cache.eycache1.key_buffer_size=128*1024;
然后设置某些表使用这个key cache:
CACHE INDEX t1, t2, t3 IN hot_cache;
如果我们要去掉这个key cache, 我们只需要将该key cache大小设为0就可以了:
SET GLOBAL hot_cache.eycache1.key_buffer_size=0;
如果将某个key cache大小设为0, 原来归属于这个key cache的表就都归属到默认的key cache了
注意, 不能把默认的key cache大小设为0.

我们可以将这些放在系统启动脚本中完成, 这样每次重启就不会丢失原来的设置了:
key_buffer_size = 4G
hot_cache.key_buffer_size = 2G
cold_cache.key_buffer_size = 2G
init_file=/
path/to/data-directory/mysqld_init.sql
在mysqld_init.sql中我们可以写:
CACHE INDEX db1.t1, db1.t2, db2.t3 IN hot_cache
CACHE INDEX db1.t4, db2.t5, db2.t6 IN cold_cache



关于key buffer usage ratio的算法

今天看简朝阳的<<MySQL性能优化与架构设计>>, 降到key buffer的时候, 书中有一个关于key buffer usage ratio的算法, 感觉应该是错的:
Key_buffer_UsageRatio = (1 - Key_blocks_used/(Key_blocks_used + Key_blocks_unused)) * 100%
这个应该是反的, 只需要Key_blocks_used/(Key_blocks_used + Key_blocks_unused) * 100% 就可以了.

2011年11月8日星期二

MySQL的Nested Loop算法

原文地址:http://dev.mysql.com/doc/refman/5.0/en/nested-loop-joins.html
普通Nested-Loop算法:
有以下join:
Table   Join Type
t1      range
t2      ref
t3      ALL

实际join示意:
for each row in t1 matching range {
 for each row in t2 matching reference key {
   for each row in t3 {
     if row satisfies join conditions,
     send to client
   }
 }
}

因为普通Nested-Loop一次只将一行传入内存循环, 所以外层循环有多少行, 内存循环便要执行多少次.
Block Nested-Loop Join:
这种算法指的是使用join buffer的Nested Loop, 将外层循环的行读进buffer, 从而减少内层循环的次数. 例如, 如果读入了10行进入join buffer, 那么内存循环就可以一次与这10行进行比较, 这样就能够显著减少内层循环表扫描的次数.
MySQL使用Join Buffer有以下要点:
1. join_buffer_size变量决定buffer大小
2. 只有在join类型为all, index, range的时候才可以使用join buffer
3. 能够被buffer的每一个join都会分配一个buffer, 也就是说一个query最终可能会使用多个join buffer
4. 第一个nonconst table不会分配join buffer, 即便其扫描类型是all或者index
5. 在join之前就会分配join buffer, 在query执行完毕即释放
6. join buffer中只会保存参与join的列, 并非整个数据行

前面描述的query, 如果使用join buffer, 那么实际join示意如下:

for each row in t1 matching range {
 for each row in t2 matching reference key {
   store used columns from t1, t2 in join buffer
   if buffer is full {
     for each row in t3 {
       for each t1, t2 combination in join buffer {
         if row satisfies join conditions,
         send to client
       }
     }
     empty buffer
   }
 }
}

if buffer is not empty {
 for each row in t3 {
   for each t1, t2 combination in join buffer {
     if row satisfies join conditions,
     send to client
   }
 }
}

如果t1, t2参与join的列长度只和为s, c为二者组合数, 那么t3表被scan的次数为
(S * C)/join_buffer_size + 1
随着join_buffer_size的增大, t3将减少, 知道join buffer能够容纳所有的t1, t2组合, 到了这点以后, 再增大join buffer size, query的速度就不会再变快了.

2011年11月7日星期一

join_buffer_size是怎样分配的

原文地址:http://www.mysqlperformanceblog.com/2010/07/05/how-is-join_buffer_size-allocated/

Join Buffer不像很多其他buffer一样是每一个线程(thread)分配的,  而是每一个线程的每一次join分配的(per-join-per-thread).
当Join方式为All, Index, Range时, 参与join的table 列的所有值便会被cache到分配的join buffer中. 如果query需要join多个表, 那么便会分配多个join buffer:
例如以下的join便会分配两个join buffer:
select * from a_table
join b_table on b.col1 = a.col1
join c_table on c.col2 = b.col2

我们还是回头来看join buffer是如何分配的, 源代码:
14176 /*****************************************************************************
14177   Fill join cache with packed records
14178   Records are stored in tab->cache.buffer and last record in
14179   last record is stored with pointers to blobs to support very big
14180   records
14181 ******************************************************************************/
14182
14183 static int
14184 join_init_cache(THD *thd,JOIN_TAB *tables,uint table_count)
14185 {
... snip ...
14268   cache->length=length+blobs*sizeof(char*);
14269   cache->blobs=blobs;
14270   *blob_ptr=0;             /* End sequentel */
14271   size=max(thd->variables.join_buff_size, cache->length);
14272   if (!(cache->buff=(uchar*) my_malloc(size,MYF(0))))
14273     DBUG_RETURN(1);           /* Don't use cache */ /* purecov: inspected */
14274   cache->end=cache->buff+size;
14275   reset_cache_write(cache);
14276   DBUG_RETURN(0);
14277 }

从14721行我们可以看到, cache size 为 join_buffer_size与实际需要的大小中的较大的那个数, 并且是一次分配的, 也就是说, 如果你将join_buffer_size设的相当大, 害处是相当大的, 因为很容易就可以将内存耗尽.

Problem when monitor slave using MySQL Enterprise Monitor

Finally I successfully setup my Mysql Enterprise monitor to monitor the replication server of our production database.

It failed so many times. Every time I monitor the production database and slave database at the same time, the monitor server always identified them as the same server, and combine them into just one server, which puzzle me for a long time.

I have tried to use one single agent instances, multiple agent instances, and multiple agent installs. I thought the key problem might lie on the UUID of the server. Because no matter how I change the UUID in the agent configuration files, the UUID value I saw in the monitor page just never changes.

Finally I found an article said that every database stores a UUID value in mysql.inventory to identify it self.

So I checked it, sure enough I found same value in these two database. Sure they are!! I created the slave using the backup of the master server!!

I changed the slave's value, and it works!!

But why this not happened when I monitor another group database, it also includes one master and one slave. It's strange that they have different UUID value in mysql.inventory.

Maybe their version is higher!!

修改cacti脚本, 增加磁盘使用百分比

最近在部署cacti用来monitor各数据库服务器, 想要设置一个磁盘空间报警, 当分区满90的时候发alert mail, 但是看了现在已经设置的monitor disk space的data source, 里面并没有类似于used_percent项, 又懒得再去找其他的template, 变自己修改已经有的脚本来实现.

首先找到对应的data query:

我们知道了这个data query对应的xml文件, 于是找到这个xml文件, 编辑, 在field中增加一项:
<fields>
               <hrStorageIndex>
                       <name>Index</name>
                       <direction>input</direction>
                       <query_name>index</query_name>
               </hrStorageIndex>
......                
               <hrStorageUsedPercent>
                       <name>Used Percent</name>
                       <direction>output</direction>
                       <query_name>used_percent</query_name>
               </hrStorageUsedPercent>

从这个文件中我们也可以看出, 所用的php文件:
<script_path>|path_cacti|/scripts/ss_host_disk.php</script_path>

对应编辑这个php文件(不会php, 只能看着改了):
if ($cmd == "index") {
$return_arr = ss_host_disk_reindex(cacti_snmp_walk($hostname, $snmp_community, $oids["index"], $snmp_version, $snmp_auth_username, $snmp_auth_password, $snmp_auth_protocol, $snmp_priv_passphrase, $snmp_priv_protocol, $snmp_context, $snmp_port, $snmp_timeout, $ping_retries, $max_oids, SNMP_POLLER));

for ($i=0;($i<sizeof($return_arr));$i++) {
print $return_arr[$i] . "\n";
}

}elseif ($cmd == "num_indexes") {
$return_arr = ss_host_disk_reindex(cacti_snmp_walk($hostname, $snmp_community, $oids["index"], $snmp_version, $snmp_auth_username, $snmp_auth_password, $snmp_auth_protocol, $snmp_priv_passphrase, $snmp_priv_protocol, $snmp_context, $snmp_port, $snmp_timeout, $ping_retries, $max_oids, SNMP_POLLER));

print sizeof($return_arr);

}elseif ($cmd == "query") {
$arg = $arg1;

$arr_index = ss_host_disk_reindex(cacti_snmp_walk($hostname, $snmp_community, $oids["index"], $snmp_version, $snmp_auth_username, $snmp_auth_password, $snmp_auth_protocol, $snmp_priv_passphrase, $snmp_priv_protocol, $snmp_context, $snmp_port, $snmp_timeout, $ping_retries, $max_oids, SNMP_POLLER));
$arr = ss_host_disk_reindex(cacti_snmp_walk($hostname, $snmp_community, $oids[$arg], $snmp_version, $snmp_auth_username, $snmp_auth_password, $snmp_auth_protocol, $snmp_priv_passphrase, $snmp_priv_protocol, $snmp_context, $snmp_port, $snmp_timeout, $ping_retries, $max_oids, SNMP_POLLER));

for ($i=0;($i<sizeof($arr_index));$i++) {
print $arr_index[$i] . "!" . $arr[$i] . "\n";
}
}elseif ($cmd == "get") {
$arg = $arg1;
$index = $arg2;

if ($arg == "used_percent")
{
   $sau_used = preg_replace("/[^0-9]/i", "", db_fetch_cell("select field_value from host_snmp_cache where host_id=$host_id and field_name='hrStorageAllocationUnits' and snmp_index='$index'"));
$snmp_used = cacti_snmp_get($hostname, $snmp_community, $oids["used"] . ".$index", $snmp_version, $snmp_auth_username, $snmp_auth_password, $snmp_auth_protocol,$snmp_priv_passphrase,$snmp_priv_protocol, $snmp_context, $snmp_port, $snmp_timeout, $ping_retries, SNMP_POLLER);
if($snmp_data_used<0){
$used = (abs($snmp_used) + 2147483647) * $sau_used;
} else {
$used = $snmp_used * $sau_used;
}

$sau_total = preg_replace("/[^0-9]/i", "", db_fetch_cell("select field_value from host_snmp_cache where host_id=$host_id and field_name='hrStorageAllocationUnits' and snmp_index='$index'"));
$snmp_total = cacti_snmp_get($hostname, $snmp_community, $oids["total"] . ".$index", $snmp_version, $snmp_auth_username, $snmp_auth_password, $snmp_auth_protocol,$snmp_priv_passphrase,$snmp_priv_protocol, $snmp_context, $snmp_port, $snmp_timeout, $ping_retries, SNMP_POLLER);
if($snmp_total<0){
$total = (abs($snmp_total) + 2147483647) * $sau_total;
} else {
$total = $snmp_total * $sau_total;
}

return number_format($used * 100 / $total, 2);
}

if (($arg == "total") || ($arg == "used")) {
$sau = preg_replace("/[^0-9]/i", "", db_fetch_cell("select field_value from host_snmp_cache where host_id=$host_id and field_name='hrStorageAllocationUnits' and snmp_index='$index'"));
$snmp_data = cacti_snmp_get($hostname, $snmp_community, $oids[$arg] . ".$index", $snmp_version, $snmp_auth_username, $snmp_auth_password, $snmp_auth_protocol,$snmp_priv_passphrase,$snmp_priv_protocol, $snmp_context, $snmp_port, $snmp_timeout, $ping_retries, SNMP_POLLER);
if($snmp_data<0){
return (abs($snmp_data) + 2147483647) * $sau;
} else {
return $snmp_data * $sau;
}
}else{
return cacti_snmp_get($hostname, $snmp_community, $oids[$arg] . ".$index", $snmp_version, $snmp_auth_username, $snmp_auth_password, $snmp_auth_protocol,$snmp_priv_passphrase,$snmp_priv_protocol, $snmp_context, $snmp_port, $snmp_timeout, $ping_retries, SNMP_POLLER);
}
}

接下来就可以到cacti中操作了:
在data template中增加一个:

在data query中关联graph template以及 date template的地方:

然后修改graph template(其实应该没有必要这么加, 因为这个百分比相对于磁盘大小来说实在太小了, 在同一张图里面根本显示不出来, 只是我现在没研究透, 就全加了):

然后我们就可以添加threshold了.

2011年11月2日星期三

Setup a replication of MYISAM database

We have a database running on "192.168.1.106", and a DR site "10.1.0.4". Now we want to setup a slave database on "10.1.0.4". 

Most tables on "192.168.1.106" are MYISAM.


I think the most difficult part is to get a consistent copy of the whole database, because most of the tables on "192.168.1.106" are MYISAM tables. The general approach is to shutdown it or lock the whole database for a long time.

I have monitored the database server for days, try to get a accurate time when the loading is very low, so I can lock the database and minimize the side effect. Unfortunately I didn't get it.

 

The day before tomorrow I found that the "cp" command had a "update" option, which can copy only the updated files.

So I copied the whole database when the database is running, then locked it in read mode, got the bin-log position(for setup slave), and used the "update" option to copy the incremental part. The lock process just lasted for seconds.

 

Then I transferred the copy to "10.1.0.4", checked the configure file to make sure the database can work as a slave.

Because the database version on "10.1.0.4" is 5.1.53, which is higher than "192.168.1.106", so I run the "mysql_upgrade" command after I started the server.

 

Then I setup the slave, using the replication user I setup on "192.168.1.106" and the bin-log position I got when locked the database.

 

I found some tables were corrupt after I started the slave process, and I don't know why.

I can just re-dump then from "192.168.1.106" to import then into "10.1.0.4". It's too dangers to do it like this, because this would lead duplicate rows in those tables after restarted the slave process.

 

As expected, I saw "duplicate rows" error when I used "show slave status".

So I deleted the rows generated after I did the last "update" optioned "cp" command, and restarted the slave. It works!!!

2011年11月1日星期二

abs的tuning

上周收到一个报表sql语句, 跑的很慢, 几个小时还出不来, 锁死很多production的session.

原来业务上会cancel掉一个order或者order中的某个item, 而由于老系统设计的关系, cancel的时候不是改状态, 而是将对应的id设为负值.
而现在的报表需要找到曾经出现过某个状态的item列表, 而这些item现在可能是cancel掉也可能是正常的.

主要串了3张表, 三张表上order_id, item_id都有索引.
但是因为正负值的关系, 串表的时候都使用了abs, 这就到这3个表上的索引在则个sql中全部无效.

tuning方法是将abs串表改为正常的正负值串表, 最终用union, 将结果集合并, 并处理好串表时候的条件,   保证至少有一个join部分能够走到索引.

这样最终结果虽然效率也不是很高, 因为每段sql语句都还是会有一部分要走全表扫描, 但只要控制好表连接顺序将走全表扫描的尽量放到最后, 效果已经比原来的好多了. 原来几个小时也跑不出来的, tuning以后十几秒就出来了.


2011年10月25日星期二

将cacti数据库配为非3306端口

最近部署cacti来monitor一批mysql的服务器, 由于某些原因, 我需要将cacti的数据库放置在一个在3307端口监听的instance上.

但是发现修改config.php中的port不起作用, cacti依然选择去连接3306端口的数据库.

一番研究下, 发现cacti使用adodb来连接mysql, 并且默认使用的driver是php5-mysql,  使用NewADOConnection('mysql')的方式来连接mysql数据库.
而这个driver就我查到的看来是不支持非3306端口的连接. 并且cacti中连接mysql部分的函数的写法也是不支持其他端口, 因为在函数参数中hard code了3306.(不知道为何这么操作)

最终做了两处修改:
1. 修改[cacti_path]/lib/database.php, 使其能够接收port参数
       function db_connect_real($host, $user, $pass, $db_name, $db_type, $port = "3306", $db_ssl = false, $retries = 20) {
       global $cnn_id;

       $i = 0;
       $dsn = "$db_type://$user:$pass@$host/$db_name?persist&port=$port";
2. 修改[cacti_path]/include/config.path, 改为使用mysqli来连接mysql数据库
$database_type = "mysqli";

这样就可以自由修改config.php中的port了.

2011年10月20日星期四

修改log-bin路径

最近升级一台mysql, 因为原来这个mysql上的二进制日志文件跟数据文件是放在一起的, 感觉不是太好, 所以想改一下路径.
没料到把日志文移到新的路径下面并在配置文件中修改好, 但改完之后, 就是起不来, 老是报文件找不到.
/usr/local/mysql/bin/mysqld: File './mysql-bin.000014' not found (Errcode: 2)
111020  2:17:23 [ERROR] Failed to open log (file './mysql-bin.000014', errno 2)
111020  2:17:23 [ERROR] Could not open log file
111020  2:17:23 [ERROR] Can't init tc log
111020  2:17:23 [ERROR] Aborting
但是权限都是没问题的.

很郁闷的改回去, 先让其跑起来再说.
今天又想起来这个事情, 所以又折腾了一番.

原来关键在mysql-bin.index这个文件, mysql启动的时候会根据这个文件找最后一个二进制日志文件, 但是原来二进制日志文件与数据文件放在一起的时候, 这个文件里面存的是相对路径.
并且是相对于数据文件的相对路径(我前面也关注过这个index文件, 但以为这个相对路径是相对于index文件的相对路径), 所以修改到其他路径以后, 必须要将index文件中的路径改为绝对路径, 或者是改为新的相对路径, 要记住, 这个相对基础是数据文件的路径.

利用mk-query-digest查看general log中所有操作某表的sql

mk-query-digest --type genlog --filter '$event->{arg} =~ m/order_main/' --limit 100% mysql_log.log > table3.log

2011年10月17日星期一

第一个mysql case

上周碰到一个case, 原同事的朋友打电话给我, 说他们的mysql数据库挂了, 有什么办法可以恢复, 找到哪些文件可以恢复.
当时在外面, 问他有没有备份, 说没有, 只有9月份他自己的一个dump备份.

他说他现在看到有frm文件以及opt文件, 我就告诉他, 找到ibdata, iblogfile文件, 以及服务器的配置文件, 类似my.cnf之类的.
半个小时之后, 再电话过来, 说都找到了, 但是ibdata文件比较奇怪, 在原来server上显示10个G, 但是拷贝到另外一个server上, 几秒钟就拷贝完了, 而且拷贝过去显示size只有几百M.

跟他确认一下拷贝过程没问题之后, 基本确认应该是ibdata文件损坏. 问他有没有类似mysql-bin之类的文件, 说没有, 发过来的my.ini里面也没有看到有启动二进制日志.

QQ详谈, mysql数据库是放在服务商那的, 某天应用不能访问了, 服务商说是他们那网络异常了, 半个小时后恢复, 但是恢复之后数据就没有了. 控制台查看数据库, 只看到表, 但是表里面都没有数据.
根据他前面说的, 基本确定是ibdata文件损坏, 但体系统他说应该是服务商那边硬件或者OS故障导致, 断网不会出现这种问题.

他电话过去确认, 果然, 断过一次电, 重新启动之后ibdata文件就只有几百M了, 检查磁盘说有坏道. 另外, 服务商OS是windows 2003的.

很遗憾的告诉他, 我这边没有办法了, 只能请磁盘恢复的人看看能否找回!
1. 没有固定的备份策略, 最近的备份还是9月份的, 而且还是他自己dump出来的
2. 很关键的, 没有启动二进制日志.

2011年10月13日星期四

关于表join顺序的一次tuning

昨天收到一个sql, 很慢, 基本上跑不出来, 将其他session全都block住了, 只能kill掉.
SELECT DISTINCT a.order_id, a.item_id, a.update_date, a.operation, a.reason, a.comments, d.status_date, c.first_name, c.last_name, f.email
FROM po.item_update_history a
JOIN po.order_service b
JOIN po.vendor c ON a.order_id = - b.order_id
AND a.item_id = - b.item_id
AND b.vendor = c.user_id
LEFT JOIN po.order_status d ON a.order_id = d.order_id
AND d.status = "Supply Confirmed"
STRAIGHT_JOIN po.order_main f ON abs( f.order_id ) = abs( a.order_id )
WHERE a.operation
IN (
"Canceled", "Deleted"
)
AND c.department = "Antibody"
AND a.update_date
BETWEEN "2011-02-01 00:00:00"
AND "2011-02-28 23:59:59"
AND 1 =1
几个相关表的信息如下:
item_update_history   58132rows
order_service    133550rows   index:order_id
vendor 413rows  index:user_id
order_status  531835rows  index:order_id
order_main 130394  index:order_id

一番折腾之后, 在item_update_history表上添加了index(order_id, item_id)
然后速度依然没有反应, 感觉abs函数会影响性能, 于是将abs部分改成ON (f.order_id = a.order_id or f.order_id = - a.order_id)
速度上去了, 于是提交给RD那边改,  但是具体原理不是特别明白, 因为我并不确定不用函数能够对性能造成这么大的影响, 因为不用函数最多是能够走索引, 但是初步看explain结果, 并没有走order_main上面的索引.

今天仔细check. 发现两段sql的explain结果如下:

也就是说表join的顺序变掉了, 并且最后join order_main表的时候只是可能能够走到索引.
但是关键的应该是表join顺序, 第二段将order_main表放到最后join, 也就是使用条件过滤掉了大多数数据以后再join order_main表的, 而第一段是一开始就join,  这样两个大表在一起join, 速度自然慢了.
为了验证这一点, 我将第一段sql join order_main的部分改为straight_join, explain结果显示表join顺序与第二段的join顺序一样,  然后我跑了下, 虽然没有第二段速度快, 但也是很快就出来结果了, 大概是因为第二段sql中extra部分中描述的索引的影响.

2011年10月12日星期三

一次MySQL的升级

有一台Ubuntu上已经默认安装mysql5.1.49, 路径为默认路径, 并且有一个数据库在运行, 为master.
另外, 原来的数据库innodb数据文件设置不合理, 需要重新设置.
我需要将这太mysql升级到5.5.16.

升级步骤如下
1. 在这台server上二进制安装5.5.16, 在路径/usr/local/mysql下面
2. 将master的备份(mysqlbackup完成)apply到5.5.16, 并5.5.16的run mysql_upgrade
3. 使用mysqldump将数据库完整dump为sql文件
4. 停5.5.16, 删除所有数据以及log, 只保留mysql库(如果不保留mysql库, 因为view/procedure中有definer引用, 后面导回数据的时候会报错), 修正innodb数据文件配置为合适的配置
5. 将前面dump的sql文件导回5.5.16
6. 将5.5.16设为master的slave, 并启动slave, 保持数据一致
因为master是一个production环境, 所以需要申请停机时间, 申请了20分钟进行切换.
1. 在master上flush logs with read lock; 并查看主从数据是否一致
2. 主从一致, shutdown master, shutdown slave
3. 将master的data, log, cnf文件备份
4. 删除原有mysql5.1.49的安装, apt-get remove mysql-client, apt-get remove mysql-server, apt-get remove mysql-common(实际证明, 只有最后一项起作用, 因为实际的包名不是mysql-client, mysql-server)
5. 删除/etc/mysql, /etc/init/mysql.conf
6. mv /usr/local/mysql/support-files/mysql.server /etc/init.d/mysql
7. update-rc.d -f mysql defaults (重设mysql服务自动启动)
8. 将5.5.16的my.cnf文件移到/etc下, 并编辑, 修正端口以及server id
9. 删除原5.5.16下面的salve相关信息
10. 在my.cnf中添加skip-networking
11. 尝试service mysql start, 查看启动是否正常,  如果正常则再使用socket方式登录, 查看相关的variables值是否正常, 如果有问题则修正
12. 所有都正常以后, 将my.cnf文件中skip-networking去掉, 重新启动mysql服务, 并通知user可以重新使用

MySQL Replication的两个错误

昨天重做production的replication的时候发现有以下两种操作会引起slave端错误:
1. 在master端执行一个很长的update, 然后发现写法问题导致太慢, 于是cancel掉(在SQLYOG端cancel)
2. 直接将另外一个server的myisam表文件copy到数据库目录下, 然后在update/insert语句中使用这个文件

2011年10月8日星期六

MySQL产生随机密码

DELIMITER $$

DROP FUNCTION IF EXISTS `test`.`fun_generate_rand` $$

CREATE
    FUNCTION `test`.`fun_generate_rand`(p_length TINYINT)
    RETURNS VARCHAR(100)
    BEGIN
       DECLARE l_i TINYINT DEFAULT 0;
       DECLARE l_type TINYINT DEFAULT 1;
       DECLARE l_result VARCHAR(100) DEFAULT '';
       
       WHILE l_i < p_length DO
          SET l_type = CEIL(RAND() * 4);
          IF l_type = 1 THEN
             SET l_result = CONCAT(l_result, CHAR(97+CEIL(RAND()*25)));
          END IF;
          
          IF l_type = 2 THEN
             SET l_result = CONCAT(l_result, CHAR(65+CEIL(RAND()*25)));
          END IF;
          
          IF l_type = 3 THEN
             SET l_result = CONCAT(l_result, CHAR(48+CEIL(RAND()*9)));
          END IF;
          
          IF l_type = 4 THEN
             SET l_result = CONCAT(l_result, CHAR(35+CEIL(RAND()*3)));
          END IF;
          
          SET l_i = l_i + 1;
       END WHILE;
       
       RETURN l_result;
    END$$

DELIMITER ;

2011年9月26日星期一

[转]MySQL的性能优化

http://ourmysql.com/archives/262

什么是我们能和应该优化的

Hardware
OS / libraries
SQL server (setup and queries)
API
Application

优化硬件

如果你需要大表(>2G),你应该考虑使用64位硬件,像Alpha,Sparc或者IA64.由于MySQL使用大量内部的64位整型,64位的CPU将有更好的表现.
对于大数据库,优化顺序通常是内存,硬盘,CPU.
更多的内存可以通过把更多的索引页保留在内存中来提高索引更新的速度
如果你不使用事务安全表或者有一个大硬盘并且想避免大文件检查,有一个UPS是个好办法,它能在断电的时候使系统正常关闭.
对于数据库运行在专用的服务器上的系统来说,应该考虑1G的网络,传输延迟和吞吐量同样重要.

优化磁盘

有专用的磁盘来运行系统,程序以及临时文件.如果更改很频繁,把更新日志和事务日志放在专用的磁盘上.
对于数据库磁盘来说短寻道时间很重要;对于大表你可以预估需要:log(row_count) / log(index_block_length/3
2/(key_length + data_ptr_length))+1 次查找来找到一行.对于一个有500000行的表要索引(medium int)log(500,000)/log(1024/3
2/(3+4)) +1 = 4 次.上面的索引将需要500,000
7
3/2 = 5.2M,在实际文件中,大部分数据块将被缓冲,因此大概只需要1-2次查找.
对写入操作你将需要(像上面一样)4次查找请求,为了找到索引存放的地方,通常要2次查找来更新索引和写入行.
对实在大的数据库,你的应用将基于你的磁盘查找的速度,当你获得更多数据的时候,它以NlogN的速度增长.
把数据库和表拆分到不同的磁盘上.在MySQL里你可以使用符号连接来实现.
RAID0将提高读和写
RAID0+1能提供安全性和提高读的速度.写操作将稍慢.
不要在磁盘上使用镜像或者RAID(除了RAID0)用来存放临时文件或者为了数据能够恢复.
在Linux下,的磁盘上启动时使用hdparm -m16 -d1开启多重扇面同时读写能力,和DMA.这可以提高5-50%的响应.
在Linux上,用async(默认)和noatime来mount磁盘.
对一些特殊的应用,有可能希望用一个ram磁盘存放特殊的表,但是通常这个不需要的.

优化OS

不要swap;如果你有内存问题,多加一些RAM来代替或者配置你的系统用更少的内存
不要使用NFS磁盘来存储数据(会遇到NFS锁的问题).
为系统和SQL服务器提高开放文件的数量(添加ulimit -n #在safe_mysqld脚本里).
为系统提高处理器和线程的数量.
如果有相对比较少的大表,告诉文件系统不要把文件分散到不同的柱面上(Solaris).
使用支持大文件的文件系统(Solaris).
选择使用哪个系统;Reiserfs在Linux上打开,读和写非常快.文件检查只需要几秒.

选择API

PERL
OS和数据库之间可移植的程序
对快速的原型设计有好处
应该使用DBI/DBD接口

PHP
比PERL简单
比PERL使用更少的资源,使它更容易在Web服务器上使用.
能通过升级到PHP4来获得更快的速度

C
到MYSQL天然的接口
更快,更容易控制
更底层,因此你要做的更多

C++
更高的级别给你更多的时间来编写你的应用
仍在开发中

ODBC
运行在Windows和Unix上
几乎在不同的SQL服务器上可移植
慢,MyODBC,是一个简单的穿透驱动比天然接口慢19%.
很多途径做同样的事;像很多ODBC驱动一样在不同的区域有不同的bug很难运行.
问题重重;Microsoft经常更改接口.
不稳定的未来(Microsoft在OLE上比ODBC花更多的精力).

JDBC
理论上在OS和数据库之间可移植
可以在web客户端上运行

Python+others
可能很好,但是我们不使用他们.

优化应用程序

应该有人专注于解决问题
写应用程序的时候应该决定什么是最重要的:
速度
OS间可移植
SQL服务器间可移植

使用持续连接
在你的应用程序中使用缓存来减少SQL服务器的压力.
不要在你的应用程序中查询不需要的列.
不要使用SELECT * FROM table_name…
检查你的程序的所有部分,但是把努力放在检查整个应用下面最坏可能"合理"的压力上.
如果一下子做很多改变,使用LOCK TABLES;例如很多UPDATE或者DELETE在一起的时候.
可移植的应用程序应该使用:

Perl DBI/DBD
ODBC
JDBC
Python(或者任何其他有全面的SQL接口的语言)
你应该只使用在所有的目标SQL服务器上存在的或者可以轻易被其他构造模仿的SQL构造.
写封装来为其他的OS/SQL服务器提供缺少的功能.
如果你需要更快的速度,你应该:

找到瓶颈(CPU,磁盘,内存,SQL服务器,OS,API,或者应用程序)并专心解决他们
使用能给你带来更多速度/灵活性的扩展
了解你的SQL服务器以便于你在遇到问题或者避免瓶颈的时候使用可能最快的SQL构造
优化你的表结构和查询
使用备份来获得更多的select速度
如果连接到数据库的网速很慢,使用压缩的客户端/服务器协议.
不要担心你的第一个版本不是完全可移植的,当你解决了难题,优化总是可以稍后进行

优化MySQL

选择编译程序合编译选项
为你的系统找到做好的MySQL启动选项
学习MySQL手册,读Paul DuBois的MySQL著作
使用EXPLAIN SELECT, SHOW VARIABLES, SHOW STATUS 和 SHOW PROCESSLIST.
学习查询优化器是如何工作的.

优化你的表结构

维护你的表(myisamchk, CHECK TABLE, OPTIMIZE TABLE).
使用MySQL扩展获得更快的速度.
写一个MySQL UDF函数如果你发现你在很多地方需要这样的函数
如果你不是确实需要,不要在表一级或者列一级使用GRANT
付费给MySQL客服来获得帮助,解决问题:)

编译和安装MySQL

为你的系统选择最好的编译程序,通常可以获得10-30%更好的性能
在Linux/Intel上,用pgcc编译MySQL.但是,二进制文件将只能运行在Pentium CPU上.
对特殊的平台,使用MySQL手册总推荐的优化选项
通常对特别的CPU(像Sparc的Sun工作站)本地的编译器会比gcc提供个个你好的性能,但是不总是这样.
只用你将要使用的字符集编译MySQL
把mysqld编译成静止可执行(使用�with-mysqld-ldflags=-all-static)并使用strip sql/mysqld去掉最终可执行.
注意由于MySQL不使用C++异常,编译MySQL时去掉异常支持将获得很大性能的提升.
如果你的OS支持,那么使用本地线程.
使用MySQL性能测试来测试最终的二进制文件.

维护

如果可能,偶而运行OPTIMIZE table .这对于经常更新的可变大小的行非常重要.
偶而使用myisamchk -a来更新你的表中索引的分布状态;记住做这个之前关闭MySQL.
如果有文件碎片,把所有文件拷贝到另外的磁盘,清理旧磁盘再把文件拷回来或许很有意义.
如果有问题,用myisamchk 或者CHECK table来检查你的表
用 mysqladmin -i10 processlist extended-status 监视MySQL状态
用mysqladmin debug 来获得锁和性能的信息
优化SQL
把SQL用在它擅长的地方,用应用程序做其他事.用SQL服务器来做:

通过where条件找到行
联合表
GROUP BY
ORDER BY
DISTINCT
不要用SQL服务器做:

检查数据(像日期)
作为计算器
Tips

精明的使用索引

索引对搜索有好处,但是对插入/更新索引所在列没好处
保持数据符合第三范式,但是不要怕重复信息或者在需要更快速度的时候建立摘要表.
用建立和查询大表的摘要表来代替在一个大表上进行多次GROUP BY操作.
UPDATE table set count=count+1 where key_column=constant非常快.
对日志表,偶而从他们中生产摘要表可能比让摘要表一直存在要好.
INSERT的时候利用默认值的优点
不同SQL服务器之间的不同速度
Reading 2000000 rows by key: NT Linux
mysql 367 249
mysql_odbc 464
db2_odbc 1206
informix_odbc 121126
ms-sql_odbc 1634
oracle_odbc 20800
solid_odbc 877
sybase_odbc 17614

Inserting (350768) rows: NT Linux
mysql 381 206
mysql_odbc 619
db2_odbc 3460
informix_odbc 2692
ms-sql_odbc 4012
oracle_odbc 11291
solid_odbc 1801
sybase_odbc 4802

在上面的测试中,MySQL运行于8M的缓存,其他数据库运行在默认安装上.

重要的MySQL启动选项
back_log Change if you do a lot of new connections.
thread_cache_size Change if you do a lot of new connections.
key_buffer_size Pool for index pages; Can be made very big
bdb_cache_size Record and key cache used by BDB tables.
table_cache Change if you have many tables or simultaneous connections
delay_key_write Set if you need to buffer all key writes
log_slow_queries Find queries that takes a lot of time
max_heap_table_size Used with GROUP BY
sort_buffer Used with ORDER BY and GROUP BY
myisam_sort_buffer_size Used with REPAIR TABLE
join_buffer_size When doing a join without keys

优化表

MySQL有丰富的类型.你应该为每一列选择最有效的类型
ANALYSE过程能帮助你找到一个表的最佳类型:SELECT * FROM table_name PROCEDURE ANALYSE()
使用NOT NULL 将不保存空值.这对建立索引的列尤为重要.
把你的表由ISAM类型改变为MyISAM类型
如果可能,用固定表格式建立表.
不会使用的时候不要建立索引
记住MySQL可以在索引的前缀上进行搜索;如果你有INDEX (a,b),就不必在(a)上建立索引.
在CHAR/VARCHAR列上建立索引的时候,用建立前缀索引来节省空间.CREATE TABLE table_name (hostname CHAR(255) not null, index(hostname(10)))
为每个表选择最有效的类型
不同表中的同样信息的列应该同样定义并且有同样的名字.
MySQL如何存储数据

数据按目录存放
表以文件的形式存放
列以变长或者固定尺寸的格式存放.在BDB表中数据按页存放
支持基于内存的表
数据库和表可以从不同的磁盘上做符号连接
在Windows上MySQL支持对数据库通过.sym文件做内部的符号连接
MySQL表类型

HEAP表;只存在于内存中的固定行尺寸表,用哈希索引
ISAM表;MySQL3.22中的老的B树表
MyISAM表;包括大量扩展的ISAM表的新版本:
二进制可移植
空值列上的索引
动态尺寸列上比ISAM表更少的碎片
支持大文件
更好的索引压缩
更好的关键字统计
更好并且更快的auto_increment 处理

从Sleepycat来的Berkeley DB (BDB):事务安全(通过BEGIN WORK / COMMIT | ROLLBACK).
MySQL行类型(有关ISAM/MyISAM表)

如果每列都是固定长度的格式(没有VARCHAR,BLOB或者TEXT列)MySQL将建立固定尺寸的表格式,否则,表被建立成动态尺寸格式
固定尺寸比动态尺寸格式格式更快也更安全
动态尺寸格式的行通常占更少的空间但是如果表经常更新会一直产生碎片
有时候为了提高主表的速度把所有的VARCHAR,BLOB和TEXT列转移到其他表中是值得的.
用myisampack (ISAM中用pack_isam )可以建立一个只读的,打包的表.它最小的利用磁盘空间在慢磁盘时效果很好.打包的表最好用在不会更新的日志表上.
MySQL缓存(一旦分配,所有线程共享)

关键字缓存:key_buffer_size,默认8M
表缓存;table_cache,默认64
线程缓存;thread_cache_size,默认0
域名缓存;在编译时可以更改,默认128
内存地址表;目前只用在压缩表上.
注意MySQL没用行缓存,让OS来处理这个.

MySQL缓冲变量(通过命令分配,不共享)

sort_buffer ; ORDER BY / GROUP BY
record_buffer ;扫描表
join_buffer_size :不通过关键字连接
myisam_sort_buffer_size :REPAIR TABLE
net_buffer_length ;用来读SQL语句和缓冲结果
tmp_table_size;临时结果的HEAP表尺寸

MySQL的表缓存如何工作

MyISAM表的每个打开的实例使用一个索引文件和一个数据文件.如果表被两个线程使用或者在一个查询中使用两次,MyISAM将共享索引文件但是会打开数据文件的另一个实例.
如果缓存中的所有表都在使用缓存将会临时变得比表缓存尺寸更大.如果这样,下一个被释放的表将被关闭.
你可以通过检查mysqld的变量Opend_tables来检查你的表缓存是否太小.如果这个值很高你应该提高你的表缓存.
提高你的速度的MySQL扩展/

使用合适的表类型(HEAP,MyISAM,或者BDB表)
为你的数据使用合适的列
如果可能使用固定的行尺寸
使用不同的锁类型(SELECT HIGH_PRIORITY, INSERT LOW_PRIORITY)
Auto_increment
REPLACE (REPLACE INTO table_name VALUES (…))
INSERT DELAYED
LOAD DATA INFILE / LOAD_FILE()
使用多行INSERT来一次插入很多行
SELECT INTO OUTFILE
LEFT JOIN, STRAIGHT JOIN
LEFT JOIN 结合IS NULL
ORDER BY 有时候能使用索引
如果你只查询在一个索引中的列,只用索引树会被用来处理查询.
联合查询通常比子查询要快(这适用于大部分SQL服务器)
LIMIT
SELECT

from table1 WHERE a > 10 LIMIT 10,20
DELETE
from table1 WHERE a > 10 LIMIT 10
foo IN (list of constants)很快.
GET_LOCK()/RELEASE_LOCK()
LOCK TABLES
INSERT和SELECT可以同时运行
UDF函数可以装载进一个运行中的服务器
压缩只读表
CREATE TEMPORARY TABLE
CREATE TABLE .. SELECT
MyISAM表通过RAID选项把一个文件分散成许多文件来突破有些文件系统上的2G限制
Delayed_keys
备份
MySQL什么时候使用索引

在一个关键字上使用>, >=, =, <, <=, IF NULL 和BETWEEN
SELECT * FROM table_name WHERE key_part1=1 and key_part2 > 5;

SELECT * FROM table_name WHERE key_part1 IS NULL;

使用不以%开头的like语句的时候
SELECT * FROM table_name WHERE key_part1 LIKE 'jani%'

连表的时候从其他表检索行
SELECT * from t1,t2 where t1.col=t2.key_part

在一个指定的索引上找到MAX() 或者MIN()值
SELECT MIN(key_part2),MAX(key_part2) FROM table_name where key_part1=10

在一个关键字的前缀上ORDER BY或者GROUP BY
SELECT * FROM foo ORDER BY key_part1,key_part2,key_part3

所有查询中的列都是关键字的一部分
SELECT key_part3 FROM table_name WHERE key_part1=1

MySQL什么时候不使用索引

如果MySQL能计算出扫描整个表可能更快它不使用索引.例如如果key_part1 分布在1到100之间,在下面的查询中使用索引效果就不好:
SELECT * FROM table_name where key_part1 > 1 and key_part1 < 90
如果你使用HEAP表不要在所有的关键字部分使用=来搜索
在HEAP表上使用ORDER BY的时候
如果你不使用首个关键字部分
SELECT * FROM table_name WHERE key_part2=1

如果你使用以%开头的LIKE
SELECT * FROM table_name WHERE key_part1 LIKE '%jani%'

当你在一个索引上查找,然后ORDER BY另一个
SELECT * from table_name WHERE key_part1 = # ORDER BY key2

学习使用EXPLAIN
在你认为慢的每个查询上很实用EXPLAIN
mysql> explain select t3.DateOfAction, t1.TransactionID
-> from t1 join t2 join t3
-> where t2.ID = t1.TransactionID and t3.ID = t2.GroupID
-> order by t3.DateOfAction, t1.TransactionID;
+――-+――�+―――――+―――+―――+――――――+――+―――――――――――+
| table | type | possible_keys | key | key_len | ref | rows | Extra |
+――-+――�+―――――+―――+―――+――――――+――+―――――――――――+
| t1 | ALL | NULL | NULL | NULL | NULL | 11 | Using temporary; Using filesort |
| t2 | ref | ID | ID | 4 | t1.TransactionID | 13 | |
| t3 | eq_ref | PRIMARY | PRIMARY | 4 | t2.GroupID | 1 | |
+――-+――�+―――――+―――+―――+――――――+――+―――――――――――+
ALL和range类型表明了潜在的问题

学习使用SHOW PROCESSLIST
使用SHOW PROCESSLIST来找到什么正在运行

+―-+――-+―――�+―-+―――+――+――――�+――――――――――――-+
| Id | User | Host | db | Command | Time | State | Info |
+―-+――-+―――�+―-+―――+――+――――�+――――――――――――-+
| 6 | monty | localhost | bp | Query | 15 | Sending data | select

from station,station as s1 |
| 8 | monty | localhost | | Query | 0 | | show processlist |
+―-+――-+―――�+―-+―――+――+――――�+――――――――――――-+

怎样找出MySQL如何处理一个查询
运行下面的命令努力理解输出:
SHOW VARIABLES;
SHOW COLUMNS FROM …\G
EXPLAIN SELECT …\G
FLUSH STATUS;
SELECT …;
SHOW STATUS;

MySQL特别适合用于:

记录日志
当你运行很多连接的时候;连接非常快
同时使用SELECT和INSERT的时候
当你不把执行很长时间的查找和更新联在一起的时候
当大部分查找/更新使用唯一关键字的时候
当你使用没有冲突锁的大量表的时候
当你有大表(MySQL使用非常紧密的表结构)的时候
不要让MySQL做:

在删除行的时候更新或者插入一个表,联合长时间的SELECT
在WHERE条件中使用HAVING.
不使用关键字的JOIN或者关键字不唯一
在有不同列类型的列上JOIN
使用HEAP表的时候用=匹配而不使用完全的关键字
在MySQL监视器中使用没用WHERE条件的UPDATE或者DELETE.如果你可能这样做,在mysql客户端使用-i-am-a-dummy选项
MySQL中不同的锁

内部表锁
LOCK TABLES(运行在所有表类型上)
GET_LOCK()/RELEASE_LOCK()
页锁(BDB表)
ALTER TABLE在BDB表上也造成一个表锁
LOCK TABLES 允许在一个表上的复合读操作或者一个写操作
通常写锁比读锁在禁止写操作的时候有更高的优先级.对写操作来说,可以使用LOW_PRIORITY关键字来使锁管理器优先进行读操作
UPDATE LOW_PRIORITY SET value=10 WHERE id=10;

给MySQL更多信息来更好解决问题的窍门
注意你通常可以注释掉一个MySQL特性来使查询变得可移植:
SELECT /*! SQL_BUFFER_RESULTS */ …

SELECT SQL_BUFFER_RESULTS …
将强制MySQL来建立一个临时结果集.一旦临时结果集完成,表上的所有锁被释放.当你遇到表锁或者需要很长时间来把结果传递给客户端的时候这个很有帮助.

SELECT SQL_SMALL_RESULT … GROUP BY …
告诉优化器结果只包含很少的行

SELECT SQL_BIG_RESULT … GROUP BY …
告诉优化器结果包含很多行

SELECT STRAIGHT_JOIN …
强制优化器按照FROM条件排列的顺序来联合表

SELECT … FROM table_name [USE INDEX (index_list) | IGNORE INDEX (index_list)] table_name2
强制MySQL使用/忽略列出的索引

处理事务的例子

怎样使用MyISAM表做事务操作:
mysql> LOCK TABLES trans READ, customer WRITE;
mysql> select sum(value) from trans where customer_id=some_id;
mysql> update customer set totalvalue=sum_from_previous_statement
where customer_id=some_id;
mysql> UNLOCK TABLES;

怎样使用BDB表做事务操作:
mysql> BEGIN WORK;
mysql> select sum(value) from trans where customer_id=some_id;
mysql> update customer set totalvalue=sum_from_previous_statement
where customer_id=some_id;
mysql> COMMIT;

注意你通常可以通过组合避免事务:
UPDATE customer SET value=value+new_value WHERE customer_id=some_id;

使用REPLACE的例子
REPLACE的作用特别像INSERT,除了如果表中存在一个有相同值的旧记录在唯一的索引中作为一个新记录,在新记录插入之前旧记录被删除.下面的语句:
SELECT 1 FROM t1 WHERE key=#
IF found-row
LOCK TABLES t1
DELETE FROM t1 WHERE key1=#
INSERT INTO t1 VALUES (…)
UNLOCK TABLES t1;
ENDIF
可以代替为:
REPLACE INTO t1 VALUES (…)

General tips

用短的主键.使用数字,而不是字符串来连接表
使用联合关键字的时候,第一部分是最有用的关键字
不确定的情况下,使用有很多重复值的列首先最好进行关键字压缩
如果客户端和MySQL服务区运行在同一台机器上,使用socket代替TCP/IP来连接MySQL(能提升7.5%的性能).你可以通过连接到MySQL服务器的时候使用不指定域名或者使用locahost来实现.
如果可能,使用�skip-locking (在有些OS上默认的).它将关闭外部锁提供更好的性能.
使用应用程序级的哈希值代替使用长关键字:

SELECT
FROM table_name WHERE hash=MD5(concat(col1,col2)) AND
col_1='constant' AND col_2='constant'

把你需要的BLOB类型的文件存为文件,只在数据库中保存文件名称.
删除所有行比删除大部分行要快.
如果SQL不够快,看看底层的获得数据的接口