手机浏览 RSS 2.0 订阅 膘叔的简单人生 , 腾讯云RDS购买 | 超便宜的Vultr , 注册 | 登陆
浏览模式: 标准 | 列表Tag:mysql

ORDER BY RAND()

本文作者好象是写mysql proxy的,这是他写的关于order by rand的效率方面的文章。事实上我们在使用中是尽量避免采用order by rand这种的,但应该如何写,他给了我们一些其他的解决方法,并将其效率进行了分析,我觉得很有用,就留下来了。

原文网址:http://jan.kneschke.de/2007/2/15/order-by-rand

内容如下:
If you read the MySQL manual you might have seen the ORDER BY RAND() to randomize the the rows and using the LIMIT 1 to just take one of the rows.

SELECT name
FROM random
ORDER BY RAND()
LIMIT 1;

This example works fine and is fast if you only when let's say 1000 rows. As soon as you have 10000 rows the overhead for sorting the rows becomes important. Don't forget: we only sort to throw nearly all the rows away.

I never liked it. And there are better ways to do it. Without a sorting. As long as we have a numeric primary key.

For the first examples we assume the be ID is starting at 1 and we have no holes between 1 and the maximum value of the ID.

move the work into the application

First idea: We can simplify the whole job if we calculate the ID beforehand in the application.

SELECT MAX(id) FROM random;
## generate random id in application
SELECT name FROM random WHERE id = <random-id>

As MAX(id) == COUNT(id) we just generate random number between 1 and MAX(id) and pass it into the database to retrieve the random row.

The first SELECT is a NO-OP and is optimized away. The second is a eq_ref against a constant value and also very fast.

move the job into the database

But is it really necessary to do it in the application ? Can't we do it in the database ?

# generating a random ID
> SELECT RAND() * MAX(id) FROM random;
+------------------+
| RAND() * MAX(id) |
+------------------+
| 689.37582507297 |
+------------------+
# oops, this is a double, we need an int

> SELECT CEIL(RAND() * MAX(id)) FROM random;
+-------------------------+
| CEIL(RAND() * MAX(id)) |
+-------------------------+
| 1000000 |
+-------------------------+
# better. But how is the performance:

> EXPLAIN
SELECT CEIL(RAND() * MAX(id)) FROM random;
+----+-------------+-------+-------+------+-------------+
| id | select_type | table | type | rows | Extra |
+----+-------------+-------+-------+------+-------------+
| 1 | SIMPLE | random | index | 1000000 | Using index |
+----+-------------+-------+-------+------+-------------+
## a index scan ? we lost our optimization for the MAX()

> EXPLAIN
SELECT CEIL(RAND() * (SELECT MAX(id) FROM random));
+----+-------------+-------+------+------+------------------------------+
| id | select_type | table | type | rows | Extra |
+----+-------------+-------+------+------+------------------------------+
| 1 | PRIMARY | NULL | NULL | NULL | No tables used |
| 2 | SUBQUERY | NULL | NULL | NULL | Select tables optimized away |
+----+-------------+-------+------+------+------------------------------+
## a simple Sub-Query is bringing us the performance back.

Ok, now we know how to generate the random ID, but how to get the row ?

> EXPLAIN
SELECT name
FROM random
WHERE id = (SELECT CEIL(RAND() *
(SELECT MAX(id)
FROM random));
+----+-------------+--------+------+---------------+------+---------+------+---------+------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+------+---------+------+---------+------------------------------+
| 1 | PRIMARY | random | ALL | NULL | NULL | NULL | NULL | 1000000 | Using where |
| 3 | SUBQUERY | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Select tables optimized away |
+----+-------------+--------+------+---------------+------+---------+------+---------+------------------------------+
> show warnings;
+-------+------+------------------------------------------+
| Level | Code | Message |
+-------+------+------------------------------------------+
| Note | 1249 | Select 2 was reduced during optimization |
+-------+------+------------------------------------------+

NO, NO, NO. Don't go this way. This is the most obvious, but also the most wrong way to do it. The reason: the SELECT in the WHERE clause is executed for every row the outer SELECT is fetching. This leads to 0 to 4091 rows, depending on your luck.

We need a way to make sure that the random-id is only generated once:

SELECT name
FROM random JOIN
(SELECT CEIL(RAND() *
(SELECT MAX(id)
FROM random)) AS id
) AS r2
USING (id);
+----+-------------+------------+--------+------+------------------------------+
| id | select_type | table | type | rows | Extra |
+----+-------------+------------+--------+------+------------------------------+
| 1 | PRIMARY | <derived2> | system | 1 | |
| 1 | PRIMARY | random | const | 1 | |
| 2 | DERIVED | NULL | NULL | NULL | No tables used |
| 3 | SUBQUERY | NULL | NULL | NULL | Select tables optimized away |
+----+-------------+------------+--------+------+------------------------------+

The inner SELECT is generating a constant TEMPORARY table and the JOIN is selecting just on single row. Perfect.

No Sorting, No Application, Most parts of the query optimized away.

adding holes to the numbers

To generalize the last solution we add the possibility of holes, like when you DELETE rows.

SELECT name
FROM random AS r1 JOIN
(SELECT (RAND() *
(SELECT MAX(id)
FROM random)) AS id)
AS r2
WHERE r1.id >= r2.id
ORDER BY r1.id ASC
LIMIT 1;
+----+-------------+------------+--------+------+------------------------------+
| id | select_type | table | type | rows | Extra |
+----+-------------+------------+--------+------+------------------------------+
| 1 | PRIMARY | <derived2> | system | 1 | |
| 1 | PRIMARY | r1 | range | 689 | Using where |
| 2 | DERIVED | NULL | NULL | NULL | No tables used |
| 3 | SUBQUERY | NULL | NULL | NULL | Select tables optimized away |
+----+-------------+------------+--------+------+------------------------------+

The JOIN now adds all the IDs which are greater or equal than our random value and selects only the direct neighboor if a direct match is not possible. BUT as soon as one row is found, we stop (the LIMIT 1). And we read the rows according to the index (ORDER BY id ASC). As we are using >= instead of a = we can get rid of a the CEIL and get the same result with a little less work.

Equal Distribution

As soon as the distribution of the IDs is not equal anymore our selection of rows isn't really random either.

> select * from holes;
+----+----------------------------------+----------+
| id | name | accesses |
+----+----------------------------------+----------+
| 1 | d12b2551c6cb7d7a64e40221569a8571 | 107 |
| 2 | f82ad6f29c9a680d7873d1bef822e3e9 | 50 |
| 4 | 9da1ed7dbbdcc6ec90d6cb139521f14a | 132 |
| 8 | 677a196206d93cdf18c3744905b94f73 | 230 |
| 16 | b7556d8ed40587a33dc5c449ae0345aa | 481 |
+----+----------------------------------+----------+

The RAND function is generating IDs like 9 to 15 which all lead to the id 16 to be selected as the next higher number.

There is no real solution for this problem, but your data is mostly constant you can add a mapping table which maps the row-number to the id:

> create table holes_map ( row_id int not NULL primary key, random_id int not null);
> SET @id = 0;
> INSERT INTO holes_map SELECT @id := @id + 1, id FROM holes;
> select * from holes_map;
+--------+-----------+
| row_id | random_id |
+--------+-----------+
| 1 | 1 |
| 2 | 2 |
| 3 | 4 |
| 4 | 8 |
| 5 | 16 |
+--------+-----------+

The row_id is now free of holes again and we can run our random query again:

SELECT name FROM holes
JOIN (SELECT r1.random_id
FROM holes_map AS r1
JOIN (SELECT (RAND() *
(SELECT MAX(row_id)
FROM holes_map)) AS row_id)
AS r2
WHERE r1.row_id >= r2.row_id
ORDER BY r1.row_id ASC
LIMIT 1) as rows ON (id = random_id);

After 1000 fetches we see a equal distribution again:

> select * from holes;
+----+----------------------------------+----------+
| id | name | accesses |
+----+----------------------------------+----------+
| 1 | d12b2551c6cb7d7a64e40221569a8571 | 222 |
| 2 | f82ad6f29c9a680d7873d1bef822e3e9 | 187 |
| 4 | 9da1ed7dbbdcc6ec90d6cb139521f14a | 195 |
| 8 | 677a196206d93cdf18c3744905b94f73 | 207 |
| 16 | b7556d8ed40587a33dc5c449ae0345aa | 189 |
+----+----------------------------------+----------+

Multiple Rows at once

If you want to get more than one row returned, you can:

  • execute the Query several times
  • write a stored procedure which is executing the query and stores the result in a temp-table
  • (make a UNION)[http://jan.kneschke.de/2007/2/22/analyzing-complex-queries]

Performance

Now let's see what happends to our performance. We have 3 different queries for solving our problems.

  • Q1. ORDER BY RAND()
  • Q2. RAND() * MAX(ID)
  • Q3. RAND() * MAX(ID) + ORDER BY ID

Q1 is expected to cost N * log2(N), Q2 and Q3 are nearly constant.

The get real values we filled the table with N rows ( one thousand to one million) and executed each query 1000 times.

100        1.000      10.000     100.000    1.000.000
Q1 0:00.718s 0:02.092s 0:18.684s 2:59.081s 58:20.000s
Q2 0:00.519s 0:00.607s 0:00.614s 0:00.628s 0:00.637s
Q3 0:00.570s 0:00.607s 0:00.614s 0:00.628s 0:00.637s

As you can see the plain ORDER BY RAND() is already behind the optimized query at only 100 rows in the table.

A more detailed analysis of those queries is at analyzing-complex-queries.

 

Tags: mysql, order, rand, explain, analyze

MySQL配置文件my.cnf 例子最详细翻译

对于my.cnf,我一向是懒得看的,因为在windows下面mysql默认有四个ini文件,只要拿其中的一个改为my.ini就行了。一般是拿最大的那个改,而且因为很少用innodb之类的,所以真正改的也就是第一个区块的内容。

看到冰山上的播客上面有翻译,于是COPY过来,与大家分享一下,毕竟即使不是你自己配置数据库,但了解一下也好。

XML/HTML代码
  1. #BEGIN CONFIG INFO  
  2. #DESCR: 4GB RAM, 只使用InnoDB, ACID, 少量的连接, 队列负载大  
  3. #TYPE: SYSTEM  
  4. #END CONFIG INFO  
  5. #  
  6. # 此mysql配置文件例子针对4G内存,并在www.bt285.cn bt下载与 www.5a520.cn 小说520,这两个日ip 2w ,pv 20w  测试过的。  
  7. # 主要使用INNODB  
  8. #处理复杂队列并且连接数量较少的mysql服务器  
  9. #  
  10. # 将此文件复制到/etc/my.cnf 作为全局设置,  
  11. # mysql-data-dir/my.cnf 作为服务器指定设置  
  12. # (@localstatedir@ for this installation) 或者放入  
  13. # ~/.my.cnf 作为用户设置.  
  14. #  
  15. # 在此配置文件中, 你可以使用所有程序支持的长选项.  
  16. # 如果想获悉程序支持的所有选项  
  17. # 请在程序后加上”–help”参数运行程序.  
  18. #  
  19. # 关于独立选项更多的细节信息可以在手册内找到  
  20. #  
  21. #  
  22. # 以下选项会被MySQL客户端应用读取.  
  23. # 注意只有MySQL附带的客户端应用程序保证可以读取这段内容.  
  24. # 如果你想你自己的MySQL应用程序获取这些值  
  25. # 需要在MySQL客户端库初始化的时候指定这些选项  
  26. #  
  27. [client]  
  28. #password = [your_password]  
  29. port = @MYSQL_TCP_PORT@  
  30. socket = @MYSQL_UNIX_ADDR@  
  31. # *** 应用定制选项 ***  
  32. #  
  33. #  MySQL 服务端  
  34. #  
  35. [mysqld]  
  36. # 一般配置选项  
  37. port = @MYSQL_TCP_PORT@  
  38. socket = @MYSQL_UNIX_ADDR@  
  39. # back_log 是操作系统在监听队列中所能保持的连接数,  
  40. # 队列保存了在MySQL连接管理器线程处理之前的连接.  
  41. # 如果你有非常高的连接率并且出现”connection refused” 报错,  
  42. # 你就应该增加此处的值.  
  43. # 检查你的操作系统文档来获取这个变量的最大值.  
  44. # 如果将back_log设定到比你操作系统限制更高的值,将会没有效果  
  45. back_log = 50  
  46. # 不在TCP/IP端口上进行监听.  
  47. # 如果所有的进程都是在同一台服务器连接到本地的mysqld,  
  48. # 这样设置将是增强安全的方法  
  49. # 所有mysqld的连接都是通过Unix sockets 或者命名管道进行的.  
  50. # 注意在windows下如果没有打开命名管道选项而只是用此项  
  51. # (通过 “enable-named-pipe” 选项) 将会导致mysql服务没有任何作用!  
  52. #skip-networking  
  53. # MySQL 服务所允许的同时会话数的上限  
  54. # 其中一个连接将被SUPER权限保留作为管理员登录.  
  55. # 即便已经达到了连接数的上限.  
  56. max_connections = 100  
  57. 一般像在我这个www.bt285.cn pv 10w   max_connections=30 就够了。但是如果页面都像http://www.bt285.cn/content.php?id=1196863 这个甜性涩爱页面一样,max_connections=30是不够的。  
  58. # 每个客户端连接最大的错误允许数量,如果达到了此限制.  
  59. # 这个客户端将会被MySQL服务阻止直到执行了”FLUSH HOSTS” 或者服务重启  
  60. # 非法的密码以及其他在链接时的错误会增加此值.  
  61. # 查看 “Aborted_connects” 状态来获取全局计数器.  
  62. max_connect_errors = 10  
  63. # 所有线程所打开表的数量.  
  64. # 增加此值就增加了mysqld所需要的文件描述符的数量  
  65. # 这样你需要确认在[mysqld_safe]中 “open-files-limit” 变量设置打开文件数量允许至少4096  
  66. table_cache = 2048  
  67. # 允许外部文件级别的锁. 打开文件锁会对性能造成负面影响  
  68. # 所以只有在你在同样的文件上运行多个数据库实例时才使用此选项(注意仍会有其他约束!)  
  69. # 或者你在文件层面上使用了其他一些软件依赖来锁定MyISAM表  
  70. #external-locking  
  71. # 服务所能处理的请求包的最大大小以及服务所能处理的最大的请求大小(当与大的BLOB字段一起工作时相当必要)  
  72. # 每个连接独立的大小.大小动态增加  
  73. max_allowed_packet = 16M  
  74. # 在一个事务中binlog为了记录SQL状态所持有的cache大小  
  75. # 如果你经常使用大的,多声明的事务,你可以增加此值来获取更大的性能.  
  76. # 所有从事务来的状态都将被缓冲在binlog缓冲中然后在提交后一次性写入到binlog中  
  77. # 如果事务比此值大, 会使用磁盘上的临时文件来替代.  
  78. # 此缓冲在每个连接的事务第一次更新状态时被创建  
  79. binlog_cache_size = 1M  
  80. # 独立的内存表所允许的最大容量.  
  81. # 此选项为了防止意外创建一个超大的内存表导致永尽所有的内存资源.  
  82. max_heap_table_size = 64M  
  83. # 排序缓冲被用来处理类似ORDER BY以及GROUP BY队列所引起的排序  
  84. # 如果排序后的数据无法放入排序缓冲,  
  85. # 一个用来替代的基于磁盘的合并分类会被使用  
  86. # 查看 “Sort_merge_passes” 状态变量.  
  87. # 在排序发生时由每个线程分配  
  88. sort_buffer_size = 8M  
  89. # 此缓冲被使用来优化全联合(full JOINs 不带索引的联合).  
  90. # 类似的联合在极大多数情况下有非常糟糕的性能表现,  
  91. # 但是将此值设大能够减轻性能影响.  
  92. # 通过 “Select_full_join” 状态变量查看全联合的数量  
  93. # 当全联合发生时,在每个线程中分配  
  94. join_buffer_size = 8M  
  95. # 我们在cache中保留多少线程用于重用  
  96. # 当一个客户端断开连接后,如果cache中的线程还少于thread_cache_size,  
  97. # 则客户端线程被放入cache中.  
  98. # 这可以在你需要大量新连接的时候极大的减少线程创建的开销  
  99. # (一般来说如果你有好的线程模型的话,这不会有明显的性能提升.)  
  100. thread_cache_size = 8  
  101. # 此允许应用程序给予线程系统一个提示在同一时间给予渴望被运行的线程的数量.  
  102. # 此值只对于支持 thread_concurrency() 函数的系统有意义( 例如Sun Solaris).  
  103. # 你可可以尝试使用 [CPU数量]*(2..4) 来作为thread_concurrency的值  
  104. thread_concurrency = 8  
  105. # 查询缓冲常被用来缓冲 SELECT 的结果并且在下一次同样查询的时候不再执行直接返回结果.  
  106. # 打开查询缓冲可以极大的提高服务器速度, 如果你有大量的相同的查询并且很少修改表.  
  107. # 查看 “Qcache_lowmem_prunes” 状态变量来检查是否当前值对于你的负载来说是否足够高.  
  108. # 注意: 在你表经常变化的情况下或者如果你的查询原文每次都不同,  
  109. # 查询缓冲也许引起性能下降而不是性能提升.  
  110. query_cache_size = 64M  
  111. # 只有小于此设定值的结果才会被缓冲  
  112. # 此设置用来保护查询缓冲,防止一个极大的结果集将其他所有的查询结果都覆盖.  
  113. query_cache_limit = 2M  
  114. # 被全文检索索引的最小的字长.  
  115. # 你也许希望减少它,如果你需要搜索更短字的时候.  
  116. # 注意在你修改此值之后,  
  117. # 你需要重建你的 FULLTEXT 索引  
  118. ft_min_word_len = 4  
  119. # 如果你的系统支持 memlock() 函数,你也许希望打开此选项用以让运行中的mysql在在内存高度紧张的时候,数据在内存中保持锁定并且防止可能被swapping out  
  120. # 此选项对于性能有益  
  121. #memlock  
  122. # 当创建新表时作为默认使用的表类型,  
  123. # 如果在创建表示没有特别执行表类型,将会使用此值  
  124. default_table_type = MYISAM  
  125. # 线程使用的堆大小. 此容量的内存在每次连接时被预留.  
  126. # MySQL 本身常不会需要超过64K的内存  
  127. # 如果你使用你自己的需要大量堆的UDF函数  
  128. # 或者你的操作系统对于某些操作需要更多的堆,  
  129. # 你也许需要将其设置的更高一点.  
  130. thread_stack = 192K  
  131. # 设定默认的事务隔离级别.可用的级别如下:  
  132. # READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE  
  133. transaction_isolation = REPEATABLE-READ  
  134. # 内部(内存中)临时表的最大大小  
  135. # 如果一个表增长到比此值更大,将会自动转换为基于磁盘的表.  
  136. # 此限制是针对单个表的,而不是总和.  
  137. tmp_table_size = 64M  
  138. # 打开二进制日志功能.  
  139. # 在复制(replication)配置中,作为MASTER主服务器必须打开此项  
  140. # 如果你需要从你最后的备份中做基于时间点的恢复,你也同样需要二进制日志.  
  141. log-bin=mysql-bin  
  142. # 如果你在使用链式从服务器结构的复制模式 (A->B->C),  
  143. # 你需要在服务器B上打开此项.  
  144. # 此选项打开在从线程上重做过的更新的日志,  
  145. # 并将其写入从服务器的二进制日志.  
  146. #log_slave_updates  
  147. # 打开全查询日志. 所有的由服务器接收到的查询 (甚至对于一个错误语法的查询)  
  148. # 都会被记录下来. 这对于调试非常有用, 在生产环境中常常关闭此项.  
  149. #log  
  150. # 将警告打印输出到错误log文件.  如果你对于MySQL有任何问题  
  151. # 你应该打开警告log并且仔细审查错误日志,查出可能的原因.  
  152. #log_warnings  
  153. # 记录慢速查询. 慢速查询是指消耗了比 “long_query_time” 定义的更多时间的查询.  
  154. # 如果 log_long_format 被打开,那些没有使用索引的查询也会被记录.  
  155. # 如果你经常增加新查询到已有的系统内的话. 一般来说这是一个好主意,  
  156. log_slow_queries  
  157. # 所有的使用了比这个时间(以秒为单位)更多的查询会被认为是慢速查询.  
  158. # 不要在这里使用”1″, 否则会导致所有的查询,甚至非常快的查询页被记录下来(由于MySQL 目前时间的精确度只能达到秒的级别).  
  159. long_query_time = 2  
  160. # 在慢速日志中记录更多的信息.  
  161. # 一般此项最好打开.  
  162. # 打开此项会记录使得那些没有使用索引的查询也被作为到慢速查询附加到慢速日志里  
  163. log_long_format  
  164. # 此目录被MySQL用来保存临时文件.例如,  
  165. # 它被用来处理基于磁盘的大型排序,和内部排序一样.  
  166. # 以及简单的临时表.  
  167. # 如果你不创建非常大的临时文件,将其放置到 swapfs/tmpfs 文件系统上也许比较好  
  168. # 另一种选择是你也可以将其放置在独立的磁盘上.  
  169. # 你可以使用”;”来放置多个路径  
  170. # 他们会按照roud-robin方法被轮询使用.  
  171. #tmpdir = /tmp  
  172. # ***  复制有关的设置  
  173. # 唯一的服务辨识号,数值位于 1 到 2^32-1之间.  
  174. # 此值在master和slave上都需要设置.  
  175. # 如果 “master-host” 没有被设置,则默认为1, 但是如果忽略此选项,MySQL不会作为master生效.  
  176. server-id = 1  
  177. # 复制的Slave (去掉master段的注释来使其生效)  
  178. #  
  179. # 为了配置此主机作为复制的slave服务器,你可以选择两种方法:  
  180. #  
  181. # 1) 使用 CHANGE MASTER TO 命令 (在我们的手册中有完整描述) -  
  182. #    语法如下:  
  183. #  
  184. #    CHANGE MASTER TO MASTER_HOST=<host>MASTER_PORT=<port>,  
  185. #    MASTER_USER=<user>MASTER_PASSWORD=<password> ;  
  186. #  
  187. #    你需要替换掉 <host><user><password> 等被尖括号包围的字段以及使用master的端口号替换<port> (默认3306).  
  188. #  
  189. #    例子:  
  190. #  
  191. #    CHANGE MASTER TO MASTER_HOST=’125.564.12.1′, MASTER_PORT=3306,  
  192. #    MASTER_USER=’joe’, MASTER_PASSWORD=’secret’;  
  193. #  
  194. # 或者  
  195. #  
  196. # 2) 设置以下的变量. 不论如何, 在你选择这种方法的情况下, 然后第一次启动复制(甚至不成功的情况下,  
  197. #     例如如果你输入错密码在master-password字段并且slave无法连接),  
  198. #    slave会创建一个 master.info 文件,并且之后任何对于包含在此文件内的参数的变化都会被忽略  
  199. #    并且由 master.info 文件内的内容覆盖, 除非你关闭slave服务, 删除 master.info 并且重启slave 服务.  
  200. #    由于这个原因,你也许不想碰一下的配置(注释掉的) 并且使用 CHANGE MASTER TO (查看上面) 来代替  
  201. #  
  202. # 所需要的唯一id号位于 2 和 2^32 - 1之间  
  203. # (并且和master不同)  
  204. # 如果master-host被设置了.则默认值是2  
  205. # 但是如果省略,则不会生效  
  206. #server-id = 2  
  207. #  
  208. # 复制结构中的master - 必须  
  209. #master-host = <hostname>  
  210. #  
  211. # 当连接到master上时slave所用来认证的用户名 - 必须  
  212. #master-user = <username>  
  213. #  
  214. # 当连接到master上时slave所用来认证的密码 - 必须  
  215. #master-password = <password>  
  216. #  
  217. # master监听的端口.  
  218. # 可选 - 默认是3306  
  219. #master-port = <port>  
  220. # 使得slave只读.只有用户拥有SUPER权限和在上面的slave线程能够修改数据.  
  221. # 你可以使用此项去保证没有应用程序会意外的修改slave而不是master上的数据  
  222. #read_only  
  223. #*** MyISAM 相关选项  
  224. # 关键词缓冲的大小, 一般用来缓冲MyISAM表的索引块.  
  225. # 不要将其设置大于你可用内存的30%,  
  226. # 因为一部分内存同样被OS用来缓冲行数据  
  227. # 甚至在你并不使用MyISAM 表的情况下, 你也需要仍旧设置起 8-64M 内存由于它同样会被内部临时磁盘表使用.  
  228. key_buffer_size = 32M  
  229. # 用来做MyISAM表全表扫描的缓冲大小.  
  230. # 当全表扫描需要时,在对应线程中分配.  
  231. read_buffer_size = 2M  
  232. # 当在排序之后,从一个已经排序好的序列中读取行时,行数据将从这个缓冲中读取来防止磁盘寻道.  
  233. # 如果你增高此值,可以提高很多ORDER BY的性能.  
  234. # 当需要时由每个线程分配  
  235. read_rnd_buffer_size = 16M  
  236. # MyISAM 使用特殊的类似树的cache来使得突发插入  
  237. # (这些插入是,INSERT … SELECT, INSERT … VALUES (…), (…), …, 以及 LOAD DATA  
  238. # INFILE) 更快. 此变量限制每个进程中缓冲树的字节数.  
  239. # 设置为 0 会关闭此优化.  
  240. # 为了最优化不要将此值设置大于 “key_buffer_size”.  
  241. # 当突发插入被检测到时此缓冲将被分配.  
  242. bulk_insert_buffer_size = 64M  
  243. # 此缓冲当MySQL需要在 REPAIR, OPTIMIZE, ALTER 以及 LOAD DATA INFILE 到一个空表中引起重建索引时被分配.  
  244. # 这在每个线程中被分配.所以在设置大值时需要小心.  
  245. myisam_sort_buffer_size = 128M  
  246. # MySQL重建索引时所允许的最大临时文件的大小 (当 REPAIR, ALTER TABLE 或者 LOAD DATA INFILE).  
  247. # 如果文件大小比此值更大,索引会通过键值缓冲创建(更慢)  
  248. myisam_max_sort_file_size = 10G  
  249. # 如果被用来更快的索引创建索引所使用临时文件大于制定的值,那就使用键值缓冲方法.  
  250. # 这主要用来强制在大表中长字串键去使用慢速的键值缓冲方法来创建索引.  
  251. myisam_max_extra_sort_file_size = 10G  
  252. # 如果一个表拥有超过一个索引, MyISAM 可以通过并行排序使用超过一个线程去修复他们.  
  253. # 这对于拥有多个CPU以及大量内存情况的用户,是一个很好的选择.  
  254. myisam_repair_threads = 1  
  255. # 自动检查和修复没有适当关闭的 MyISAM 表.  
  256. myisam_recover  
  257. # 默认关闭 Federated  
  258. skip-federated  
  259. # *** BDB 相关选项 ***  
  260. # 如果你运行的MySQL服务有BDB支持但是你不准备使用的时候使用此选项. 这会节省内存并且可能加速一些事.  
  261. skip-bdb  
  262. # *** INNODB 相关选项 ***  
  263. # 如果你的MySQL服务包含InnoDB支持但是并不打算使用的话,  
  264. # 使用此选项会节省内存以及磁盘空间,并且加速某些部分  
  265. #skip-innodb  
  266. # 附加的内存池被InnoDB用来保存 metadata 信息  
  267. # 如果InnoDB为此目的需要更多的内存,它会开始从OS这里申请内存.  
  268. # 由于这个操作在大多数现代操作系统上已经足够快, 你一般不需要修改此值.  
  269. # SHOW INNODB STATUS 命令会显示当先使用的数量.  
  270. innodb_additional_mem_pool_size = 16M  
  271. # InnoDB使用一个缓冲池来保存索引和原始数据, 不像 MyISAM.  
  272. # 这里你设置越大,你在存取表里面数据时所需要的磁盘I/O越少.  
  273. # 在一个独立使用的数据库服务器上,你可以设置这个变量到服务器物理内存大小的80%  
  274. # 不要设置过大,否则,由于物理内存的竞争可能导致操作系统的换页颠簸.  
  275. # 注意在32位系统上你每个进程可能被限制在 2-3.5G 用户层面内存限制,  
  276. # 所以不要设置的太高.  
  277. innodb_buffer_pool_size = 2G  
  278. # InnoDB 将数据保存在一个或者多个数据文件中成为表空间.  
  279. # 如果你只有单个逻辑驱动保存你的数据,一个单个的自增文件就足够好了.  
  280. # 其他情况下.每个设备一个文件一般都是个好的选择.  
  281. # 你也可以配置InnoDB来使用裸盘分区 - 请参考手册来获取更多相关内容  
  282. innodb_data_file_path = ibdata1:10M:autoextend  
  283. # 设置此选项如果你希望InnoDB表空间文件被保存在其他分区.  
  284. # 默认保存在MySQL的datadir中.  
  285. #innodb_data_home_dir = <directory>  
  286. # 用来同步IO操作的IO线程的数量. This value is  
  287. # 此值在Unix下被硬编码为4,但是在Windows磁盘I/O可能在一个大数值下表现的更好.  
  288. innodb_file_io_threads = 4  
  289. # 如果你发现InnoDB表空间损坏, 设置此值为一个非零值可能帮助你导出你的表.  
  290. # 从1开始并且增加此值知道你能够成功的导出表.  
  291. #innodb_force_recovery=1  
  292. # 在InnoDb核心内的允许线程数量.  
  293. # 最优值依赖于应用程序,硬件以及操作系统的调度方式.  
  294. # 过高的值可能导致线程的互斥颠簸.  
  295. innodb_thread_concurrency = 16  
  296. # 如果设置为1 ,InnoDB会在每次提交后刷新(fsync)事务日志到磁盘上,  
  297. # 这提供了完整的ACID行为.  
  298. # 如果你愿意对事务安全折衷, 并且你正在运行一个小的食物, 你可以设置此值到0或者2来减少由事务日志引起的磁盘I/O  
  299. # 0代表日志只大约每秒写入日志文件并且日志文件刷新到磁盘.  
  300. # 2代表日志写入日志文件在每次提交后,但是日志文件只有大约每秒才会刷新到磁盘上.  
  301. innodb_flush_log_at_trx_commit = 1  
  302. # 加速InnoDB的关闭. 这会阻止InnoDB在关闭时做全清除以及插入缓冲合并.  
  303. # 这可能极大增加关机时间, 但是取而代之的是InnoDB可能在下次启动时做这些操作.  
  304. #innodb_fast_shutdown  
  305. # 用来缓冲日志数据的缓冲区的大小.  
  306. # 当此值快满时, InnoDB将必须刷新数据到磁盘上.  
  307. # 由于基本上每秒都会刷新一次,所以没有必要将此值设置的太大(甚至对于长事务而言)  
  308. innodb_log_buffer_size = 8M  
  309. # 在日志组中每个日志文件的大小.  
  310. # 你应该设置日志文件总合大小到你缓冲池大小的25%~100%  
  311. # 来避免在日志文件覆写上不必要的缓冲池刷新行为.  
  312. # 不论如何, 请注意一个大的日志文件大小会增加恢复进程所需要的时间.  
  313. innodb_log_file_size = 256M  
  314. # 在日志组中的文件总数.  
  315. # 通常来说2~3是比较好的.  
  316. innodb_log_files_in_group = 3  
  317. # InnoDB的日志文件所在位置. 默认是MySQL的datadir.  
  318. # 你可以将其指定到一个独立的硬盘上或者一个RAID1卷上来提高其性能  
  319. #innodb_log_group_home_dir  
  320. # 在InnoDB缓冲池中最大允许的脏页面的比例.  
  321. # 如果达到限额, InnoDB会开始刷新他们防止他们妨碍到干净数据页面.  
  322. # 这是一个软限制,不被保证绝对执行.  
  323. innodb_max_dirty_pages_pct = 90  
  324. # InnoDB用来刷新日志的方法.  
  325. # 表空间总是使用双重写入刷新方法  
  326. # 默认值是 “fdatasync”, 另一个是 “O_DSYNC”.  
  327. #innodb_flush_method=O_DSYNC  
  328. # 在被回滚前,一个InnoDB的事务应该等待一个锁被批准多久.  
  329. # InnoDB在其拥有的锁表中自动检测事务死锁并且回滚事务.  
  330. # 如果你使用 LOCK TABLES 指令, 或者在同样事务中使用除了InnoDB以外的其他事务安全的存储引擎  
  331. # 那么一个死锁可能发生而InnoDB无法注意到.  
  332. # 这种情况下这个timeout值对于解决这种问题就非常有帮助.  
  333. innodb_lock_wait_timeout = 120  
  334. [mysqldump]  
  335. # 不要在将内存中的整个结果写入磁盘之前缓存. 在导出非常巨大的表时需要此项  
  336. quick  
  337. max_allowed_packet = 16M  
  338. [mysql]  
  339. no-auto-rehash  
  340. # 仅仅允许使用键值的 UPDATEs 和 DELETEs .  
  341. #safe-updates  
  342. [isamchk]  
  343. key_buffer = 512M  
  344. sort_buffer_size = 512M  
  345. read_buffer = 8M  
  346. write_buffer = 8M  
  347. [myisamchk]  
  348. key_buffer = 512M  
  349. sort_buffer_size = 512M  
  350. read_buffer = 8M  
  351. write_buffer = 8M  
  352. [mysqlhotcopy]  
  353. interactive-timeout  
  354. [mysqld_safe]  
  355. # 增加每个进程的可打开文件数量.  
  356. # 警告: 确认你已经将全系统限制设定的足够高!  
  357. # 打开大量表需要将此值设高 j  
  358. open-files-limit = 8192  

Tags: mysql, 配置, 翻译

联合索引的经典例子

1.SQL需求,统计当天的数据量。

SQL> SELECT count(*) FROM test_union WHERE win_type=1 AND gmt_create >= trunc(sysdate,'dd') and gmt_create <= trunc(sysdate,'dd')+1;

  COUNT(*)
----------
   20063

1 row selected.

2.查看其索引,以gmt_create开头。

sql>create index idx_union on test_union (gmt_create,win_type) tablespace tbs_index compute statistics;

3.查看awr报表的性能,逻辑读很高,达到9700个。

Buffer Gets   Executions  Gets per Exec %Total Time  Time (s) Hash Value
--------------- ---------- -------------- ------ -------- --------- ------
205,157,987    21,236      9,660.9      34.5  6733.21  7568.58 1532799124
Module: java@app12345 (TNS V1-V3)
SELECT count(*) FROM test_union WHERE win_type=1 AND gmt_create >= trunc(sysdate,'dd') and gmt_create <= trunc(sysdate,'dd')+1

因为是只通过索引扫描,当看到返回结果集在2万左右,我们很容易估算出这个sql需要的逻辑读,(gmt_date字段7个字节+win_type字段1个字节+rowid+…)*2万,小于100个,现在很明显是偏高的。
4.调整前我们先去看数据分布。

SQL> select win_type,count(*) from test_union group by win_type;  --按win_type分组
  win_type   count(*)
---------- ----------
         0    8583162
         1    2725424
         2    765237
         3    2080156
         4    2090871
         5    3568682
SQL> select count(*) from test_union where gmt_create >= trunc(sysdate,'dd') and gmt_create <= trunc(sysdate,'dd')+1; --按gmt_create统计,一天数据量在22万左右

  COUNT(*)
----------
     229857

1 row selected.

5.调整索引,改为以win_type开头,为什么要以win_type开头呢?

create index idx_union123 on test_union (win_type,gmt_create) tablespace tbs_index compute statistics;  --新索引

6.查看其执行计划,逻辑读变成了89。

sql>set auto traceonly
sql> select count(*) from test_union
where win_type=1 and gmt_create >= trunc(sysdate,'dd') and gmt_create <= trunc(sysdate,'dd')+1;

Elapsed: 00:00:07.17

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=9)
   1    0   SORT (AGGREGATE)
   2    1     FILTER
   3    2       INDEX (RANGE SCAN) OF 'idx_union123' (NO
          N-UNIQUE) (Cost=7 Card=1 Bytes=9)
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         89  consistent gets
          0  physical reads
          0  redo size
        493  bytes sent via SQL*Net to client
        655  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

都在说建索引一定要看数据分布,从数据分布来看,一天的的数据(gmt_create)量在22万左右,而win_type的数据量非常 大,win_type为1有300万左右,为什么还要把win_type放在索引的前面呢?抛块砖,希望大家能对联合索引有更深入的理解,希望一起讨论。

--EOF--

帖子下的几个评论也很有意思:

XML/HTML代码
  1.  1.     1棉花糖ONE  
  2.   
  3.     hehe,所以我在itpub上经常建议人家如果是建复合索引的话,把范围查询的谓词尽量放在后面,这样能增加木匠说的有效索引选择度,对oracle执行计划的判断有意义,当然更主要的是这样创建索引确实是减少了索引扫描的范围,不知道楼主的环境还在不在,根据数据来看,这2个字段是相关的,而且楼主恰好查询的是 count(*),可以不扫描表,走复合索引就比较正常,如果是select * from … ,这样很可能就不会选择走索引,就11g以前除了动态采样,加hint,统计信息上应该是搞不对,我测试了11g的扩展列的统计信息,感觉这玩意效果没有想象中的理想  
  4.     Comment on Jan 10th, 2009 at 9:58 am    
  5.  2. 2木匠  
  6.   
  7.     如果没有搞清楚SQL optimizer engine and Run time engine怎么工作,怎么使用composite index,  
  8.     你的观测结果只是表面现象,永远也不知道为什么.  
  9.   
  10.     查看数据分布是对的,  
  11.     但是这种情况是需要寻根问底的. my CA$0.02 + tax, 我的一点建议.  
  12.   
  13.     木匠不辞劳苦,再重复一遍SQL optimizer and runtime engine 是怎么工作的:  
  14.   
  15.     as soon as we have a range scan on a column used somewhere in the  
  16.     middle of an index definition or fail to supply a test on such a column, the predicates on later columns do not restrict the selection of index leaf blocks that we have to examine.  
  17.   
  18.     In this case, the effective index selectivity has to be calculated from the predicate on just the “gmt_create” column. Because the test on “gmt_create” is range-based, the predicates on “win_type” do not restrict the number of index leaf blocks we have to walk.  
  19.   
  20.     关键字: effective index selectivity.  
  21.     Comment on Jan 10th, 2009 at 1:49 am    
  22.  3. 3丁原  
  23.   
  24.     这个案例已经很久了。  
  25.     实际上是没有打算写的,这个例子一直有人在问为什么,问的多了,我干脆就邮件中的内容整理出来,大家 讨论加深印象。  
  26.     我尝试把线上的数据拉下来做测试,发觉太大了根本拉不下来,只能是拼拼凑凑,gmt_create的索引已经drop掉了,部分测试数据可能不那么准确,但是数据还是在范围之内,不会偏差很大。  
  27.   
  28.     逻辑读怎么算?  
  29.     select (7+1+rowid+..)*20000/8192,差不多就是我们要的逻辑读。  
  30.   
  31.     to木匠:cost不一定准确的,我更多的是查看数据分布,结果集,以逻辑读来判断。  
  32.     to棉花糖:回复有审核的,防止很多垃圾广告。  
  33.     Comment on Jan 9th, 2009 at 5:11 pm    
  34.  4. 4carcase  
  35.   
  36.     win_type,gmt_create 扫描的索引块少多了,确定了win_type=1的范围后,再确定了是当天的数据  
  37.     (索引是win_type,gmt_create 排序的,范围扫描马上能定位到当天的数据)也就扫描了2万多就够了 ,速度得到了提升。  
  38.     和  
  39.     gmt_create,win_type 扫描的索引块多了很多,相当于扫描了 gmt_create当天所有的数据了,22万多数据都要扫描一遍,过滤出win_type=1 的数据  
  40.     Comment on Jan 9th, 2009 at 11:05 am    
  41.  5. 5棉花糖ONE  
  42.   
  43.     Your comment is awaiting moderation.  
  44.     到底咋回事啊  
  45.     Comment on Jan 9th, 2009 at 10:45 am    
  46.  6. 6棉花糖ONE  
  47.   
  48.     逻辑读89是不是搞错了  
  49.     Comment on Jan 9th, 2009 at 10:45 am    
  50.  7. 7jlttt  
  51.   
  52.     我们很容易估算出这个sql需要的逻辑读,(gmt_date字段7个字节+win_type字段1个字节+rowid+…)*2万,小于100个  
  53.     (7+1+10+…)×2W 所读的块怎么算出小于100的?  
  54.     Comment on Jan 9th, 2009 at 9:46 am    
  55.  8. 8棉花糖ONE  
  56.   
  57.     to 木匠:  
  58.     cardinality=1可能和9i有关,sysdate包含函数计算的时候,没有直方图的情况下,范围查询的选择度是按5%算的,2个5%*5%  
  59.     Comment on Jan 9th, 2009 at 9:42 am    
  60.  9. 9棉花糖ONE  
  61.   
  62.     如果把范围的放前面,索引的第二个字段只是起到filter的作用,并没有减少索引扫描的块,从索引的成本计算也能看出这一点  
  63.     Comment on Jan 9th, 2009 at 9:36 am    
  64. 10. 10木匠  
  65.   
  66.     对不起,再唠叨一句.  
  67.   
  68.     除了比较cost,还要比较Cardinality, 第二个SQL,index scan这一步的Card=1.  
  69.     但是我觉得index scan这一步的Cardinality应该大于一, 不知道哪里出了问题.  
  70.   
  71.     每一个Oracle版本的Cost and Card会有不同的结果.9.2.0.6 and 10.1.0.4或更高版本会提供更有用的信息.  
  72.   
  73.     (木匠真够粗心大意的, 请包涵)  
  74.     Comment on Jan 9th, 2009 at 2:26 am    
  75. 11. 11木匠  
  76.   
  77.     In this case, the effective index selectivity has to be calculated from the predicate on just the “gmt_create” column. Because the test on “gmt_create” is range-based, the predicates on “win_type” do not restrict the number of index leaf blocks we have to walk.  
  78.   
  79.     照搬老刘(Lewis)的原话, 改一下列名就行了. :)  
  80.   
  81.     上一个评论中的cost 指的是整个SQL(with index scan)的cost.  
  82.   
  83.     另外, 你忘了记录走第一个索引的SQL执行计划,可以比较一下 INDEX (RANGE SCAN) 的 cost.  
  84.   
  85.     INDEX (RANGE SCAN) OF ‘idx_union123′ (NON-UNIQUE) (Cost=7 Card=1 Bytes=9), 我们看到走第二个索引的(index range scan) cost=7.  
  86.     Comment on Jan 9th, 2009 at 2:03 am    
  87. 12. 12木匠  
  88.   
  89.     Q: 为什么还要把win_type放在索引的前面呢?  
  90.     A: leaf_blocks * effective index selectivity  
  91.   
  92.     第一个索引, range scan on gmt_create, first column on index.  
  93.     Because as soon as we have a range scan on a column used somewhere in the  
  94.     middle of an index definition or fail to supply a test on such a column, the predicates on later columns do not restrict the selection of index leaf blocks that we have to examine.  
  95.   
  96.     参考:  
  97.     cost =  
  98.     blevel +  
  99.     ceiling(leaf_blocks * effective index selectivity) +  
  100.     ceiling(clustering_factor * effective table selectivity)  
  101.   
  102.     a well-known guideline for arranging the columns in a multicolumn  
  103.     index. Columns that usually appear with range-based tests should generally appear later in the index than columns that usually appear with equality tests. Unfortunately, changing the column ordering in an index can have other contrary effects, which we will examine in Chapter 5.  
  104.   
  105.     Now you got test case. very well ! ^_^  
  106.   
  107.     Reference: page 74,62,67 of book Cost-Based Oracle Fundamentals  
  108.     Comment on Jan 9th, 2009 at 1:52 am    

Tags: 索引, mysql, 联合索引, 数据库

MYSQL数据库中取得同一字符串出现的次数

在PHP中,取得一个字符串中某一个字符出现的次数那是相当的简单的呀,方法也有N种,再不济我还可以把这个字符当成分隔标记进行分隔,最后求分隔出来的数组的长度就行了。

但MYSQL不行呀,没有这样的函数,还好,网上很多人给出了方案,我这里贴个最简单的:

原理:(用原始字符串的总长度 减去  将原始字符串中要统计的字符串去除后的字符串长度)再除以 要统计的字符串长度

SQL代码
  1. SELECT ( char_length( 'test' ) - char_length( replace('test''t' , ''))) / (char_length('t'));  
  2. ---- 注:test为原始字符串,t为要统计的字符串  

Tags: mysql, str_count, char_length

MYSQL中EXPLAIN的说明

EXPLAIN列的解释:

  • table:显示这一行的数据是关于哪张表的
  • type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、indexhe和ALL
  • possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引。可以为相关的域从WHERE语句中选择一个合适的语句
  • key:实际使用的索引。如果为NULL,则没有使用索引。很少的情况下,MYSQL会选择优化不足的索引。这种情况下,可以在SELECT语句 中使用USE INDEX(indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制MYSQL忽略索引
  • key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好
  • ref:显示索引的哪一列被使用了,如果可能的话,是一个常数
  • rows:MYSQL认为必须检查的用来返回请求数据的行数
  • Extra:关于MYSQL如何解析查询的额外信息。将在表4.3中讨论,但这里可以看到的坏的例子是Using temporary和Using filesort,意思MYSQL根本不能使用索引,结果是检索会很慢

extra列返回的描述的意义

  • Distinct:一旦MYSQL找到了与行相联合匹配的行,就不再搜索了
  • Not exists: MYSQL优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,就不再搜索了
  • Range checked for each Record(index map:#):没有找到理想的索引,因此对于从前面表中来的每一个行组合,MYSQL检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一
  • Using filesort: 看到这个的时候,查询就需要优化了。MYSQL需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行
  • Using index: 列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候
  • Using temporary 看到这个的时候,查询需要优化了。这里,MYSQL需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上,而不是GROUP BY上
  • Where used 使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且连接类型ALL或index,这就会发生,或者是查询有问题不同连接类型的解释(按照效率高低的顺序排序)
  • system 表只有一行:system表。这是const连接类型的特殊情况
  • const:表中的一个记录的最大值能够匹配这个查询(索引可以是主键或惟一索引)。因为只有一行,这个值实际就是常数,因为MYSQL先读这个值然后把它当做常数来对待
  • eq_ref:在连接中,MYSQL在查询时,从前面的表中,对每一个记录的联合都从表中读取一个记录,它在查询使用了索引为主键或惟一键的全部时使用
  • ref:这个连接类型只有在查询使用了不是惟一或主键的键或者是这些类型的部分(比如,利用最左边前缀)时发生。对于之前的表的每一个行联合,全部记录都将从表中读出。这个类型严重依赖于根据索引匹配的记录多少—越少越好
  • range:这个连接类型使用索引返回一个范围中的行,比如使用>或<查找东西时发生的情况
  • index: 这个连接类型对前面的表中的每一个记录联合进行完全扫描(比ALL更好,因为索引一般小于表数据)
  • ALL:这个连接类型对于前面的每一个记录联合进行完全扫描,这一般比较糟糕,应该尽量避免

Tags: mysql, database, explain