MySQL 自增ID 和 UUID 做主键的初步性能研究

这几天在纠结数据表主键的设计问题,考虑使用自增ID还是UUID来做主键,数据库后端为MySQL。
首先在互联网上搜索,得到实测 Mysql UUID 性能这篇文章,他的结论是:

当数据表的引擎为MyISAM 时,自增 ID 无疑是效率最高的, UUID 效率略低,但不会低到无法接受。一旦数据引擎为 InnodB 时,效率下降非常严重,已经达到令人发指的地步。由于 InnodB 主键采用 聚集索引 ,会对插入的记录进行物理排序,而 UUID本身基本上是无序的,所以造成了巨大的 I/O 开销。所以如果使用 innodB 千万不要使用 UUID 。

结论经过我后来的测试验证基本正确,但是对这篇文章中间的测试方法不敢苟同。

其测试过程中重大错误:针对自增id的两个表的插入操作没有写入varchar字段,考虑到varchar插入的性能消耗,这一点是绝对不能够忽略的!

建立四张测试用表:

uuidtest_inno(uuid,text),
idtest_inno(id,text),
uuidtest_myisam(uuid,text),
idtest_myisam(id,text)

建立四个存储过程,测试数据量插入100 000行:

DROP PROCEDURE IF EXISTS p_uuid_inno//
CREATE PROCEDURE p_uuid_inno()
BEGIN
DECLARE i INT;
SET i=0;
WHILE i

清空这四个表:

TRUNCATE inttest_inno//
TRUNCATE uuidtest_inno//
TRUNCATE inttest_myisam//
TRUNCATE uuidtest_myisam//

执行存储过程:

call p_int_myisam()//
call p_uuid_myisam()//
call p_int_inno()//
call p_uuid_inno()//

发现执行时间巨长无比。无奈,将测试数据量缩减到1000次插入。
myisam的时间都是0.2s左右,innodb为55s左右。

考虑数据库优化,放弃ACID支持,
设置 innodb_flush_log_at_trx_commit = 2
得到:

mysql> call p_int_myisam();
Query OK, 1 row affected (2.02 sec)

mysql> call p_uuid_myisam();
Query OK, 1 row affected (2.63 sec)

mysql> call p_int_inno();
Query OK, 1 row affected (9.71 sec)

mysql> call p_uuid_inno();
Query OK, 1 row affected (13.88 sec)

再设置 innodb_flush_method = O_DIRECT
得到:

mysql> call p_int_myisam();
Query OK, 1 row affected (2.06 sec)

mysql> call p_uuid_myisam();
Query OK, 1 row affected (2.56 sec)

mysql> call p_int_inno();
Query OK, 1 row affected (7.59 sec)

mysql> call p_uuid_inno();
Query OK, 1 row affected (10.88 sec)

再设置 innodb_log_buffer_size = 8M(之前的设置是3M)
得到:

mysql> call p_int_myisam();
Query OK, 1 row affected (1.96 sec)

mysql> call p_uuid_myisam();
Query OK, 1 row affected (2.63 sec)

mysql> call p_int_inno();
Query OK, 1 row affected (5.28 sec)

mysql> call p_uuid_inno();
Query OK, 1 row affected (9.59 sec)

可以看到对innodb来说插入速度低于myisam,这与选择uuid还是自增ID做主键没有太大的关系,uuid的确要比自增ID慢但是不至于说是数量级上的慢。

利用Java获取网络标准时间

import java.net.*;
import java.io.*;
public class DaytimeClient
{
	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		// TODO Auto-generated method stub
		String[] hostname = new String[]{"", "time.windows.com","time.nist.gov","time-nw.nist.gov","time-a.nist.gov","time-b.nist.gov"};
		int i = 1;
		boolean RecvFlag = false;
		if(args.length > 0)
		{
			hostname[0] = args[0];
			i = 0;
		}
		while(i <=5 && !RecvFlag)
		{
			try
			{
				Socket timeSocket = new Socket(hostname[i], 13);
				InputStream timeStream = timeSocket.getInputStream();
				StringBuffer time = new StringBuffer();
				int c;
				while((c=timeStream.read()) != -1)
					time.append((char)c);
				String timeString = time.toString().trim();
				System.out.println("It is " + timeString +" at " + hostname[i] +" on port " + timeSocket.getPort());
				RecvFlag = true;
				timeSocket.close();
			}
			catch(UnknownHostException e)
			{
				System.out.println("Failed to get the information from " + hostname[i]);
				System.out.println(e);
				i++;
			}
			catch(Exception e)
			{
				System.out.println("Failed to get the information from " + hostname[i]);
				System.out.println(e);
				i++;
			}
		}
		if(i == 6)
			System.out.println("Failed to get the information please check your network settings.");
		else
			System.out.println("Complete.");
	}
}

JavaApplet程序编译注意事项

首先附上一JavaApplet的Helloworld程序,及在html中的嵌入代码:
源代码路径:JavaAppletsrcHelloworld.java
JavaAppletbinJavaTest.html

import java.awt.*;
import java.applet.*;
public class HelloWorld extends Applet
{
	public void paint(Graphics g)
	{
		g.drawString("Hello World!", 5, 35);
	}
}
<HTML>
<HEAD><TITLE>JavaApplet-Text</TITLE></HEAD>
<BODY>
<applet code = "HelloWorld.class" width = 200 height = 200></applet>
</BODY>
</HTML>

要使上述代码正常运行要注意以下几点:
1、Helloworld.java编译成功后会在…bin中生成Helloworld.class文件,要将html文件同class文件放在同一目录下。若不在同一目录,则要在html中加入”codebase”代码,即

<applet code = "HelloWorld.class" codebase= "location" width = 200 height = 200></applet>

2、确保Java是最新版
3、在控制面板->程序->Java->安全设置中,把安全等级调到最低
4、不要为Helloworld创建包(package),否则要在html中指定包名。P.S.目前DS还不会指定包名=。=!