Jump to navigation

You are currently browsing all posts tagged with 'mysql'

mysql的一个小技巧:Shadow Table

  • Posted on September 8, 2010 at 4:57 pm

假设我们有一张表,用来记录广告点击情况的。每天的数据大概100万行左右。
在每天0:00的时候,要对这张表的数据进行分析,然后归档数据,再清空整张表。
我们以前的做法就是写个cron,0点开始执行,大概执行3-5分钟即可完毕。
注意:在这个过程中,仍然不断有新数据会被写入。
虽然这种办法统计到的结果包含一些0点之后的数据,但也没有大碍,整体看来还是可以接受的。

今天看了一篇文章,非常佩服作者的想法:
原文地址:http://tech.ovaistariq.net/2010/06/the-shadow-table-trick

操作步骤如下:
1 创建一个新的table,称之为shadow table, 它和原表有完全相同的结构。
2 使用RENAME TABLE语句,把这二张表交换。shadow table变成线上的表,里面完全没有数据,线上的表变成shadow table,里面包含昨天的所有数据。
3 在新的shadow table上面做统计,归档,清空操作。

举个例子吧:
线上的表,用来记录页面访问的。

CREATE TABLE `hits` (
`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`post_id` bigint(20) unsigned NOT NULL,
KEY `pp_postid_ts` (`post_id`,`ts`)
) ENGINE=MyISAM;

创建一个shadow table:

CREATE TABLE `hits_shadow` LIKE `hits`;

交换两张表:

RENAME TABLE hits TO hits_temp, hits_shadow TO hits, hits_temp TO hits_shadow;

然后就可以在hits_shadow上面做统计了。

rename table的速度实在是太快了,太爽了,完美。

使用这种办法之后,我们的数据统计更加精确了。

mysql索引 之 组合索引与字段顺序

  • Posted on September 8, 2010 at 3:47 pm

原文http://tech.ovaistariq.net/2010/05/mysql-indexes-multi-column-indexes-and-order-of-columns

很多时候,我们在mysql中创建了索引,但是某些查询还是很慢,根本就没有使用到索引!
一般来说,可能是某些字段没有创建索引,或者是组合索引中字段的顺序与查询语句中字段的顺序不符。

看下面的例子:
假设有一张订单表(orders),包含order_id和product_id二个字段。
一共有31条数据。符合下面语句的数据有5条。
执行下面的sql语句:

select product_id 
from orders
where order_id in (123, 312, 223, 132, 224);

这条语句要mysql去根据order_id进行搜索,然后返回匹配记录中的product_id。
所以组合索引应该按照以下的顺序创建:

create index orderid_productid on orders(order_id, product_id)
mysql> explain select product_id from orders where order_id in (123, 312, 223, 132, 224) \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: orders
         type: range
possible_keys: orderid_productid
          key: orderid_productid
      key_len: 5
          ref: NULL
         rows: 5
        Extra: Using where; Using index
1 row in set (0.00 sec)

可以看到,这个组合索引被用到了,扫描的范围也很小,只有5行。

如果把组合索引的顺序换成product_id, order_id的话,
mysql就会去索引中搜索 *123 *312 *223 *132 *224,必然会有些慢了。

mysql> create index orderid_productid on orders(product_id, order_id);                                                       
Query OK, 31 rows affected (0.01 sec)
Records: 31  Duplicates: 0  Warnings: 0
 
mysql> explain select product_id from orders where order_id in (123, 312, 223, 132, 224) \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: orders
         type: index
possible_keys: NULL
          key: orderid_productid
      key_len: 10
          ref: NULL
         rows: 31
        Extra: Using where; Using index
1 row in set (0.00 sec)

这次索引搜索的性能显然不能和上次相比了。
rows:31,我的表中一共就31条数据。
索引被使用部分的长度:key_len:10,比上一次的key_len:5多了一倍。
不知道是这样在索引里面查找速度快,还是直接去全表扫描更快呢?

mysql> alter table orders add modify_a char(255) default 'aaa';
Query OK, 31 rows affected (0.01 sec)
Records: 31  Duplicates: 0  Warnings: 0
 
mysql> 
mysql> 
mysql> explain select modify_a from orders where order_id in (123, 312, 223, 132, 224) \G          
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: orders
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 31
        Extra: Using where
1 row in set (0.00 sec)

这样就不会用到索引了。 刚才是因为select的product_id与where中的order_id都在索引里面的。

为什么要创建组合索引呢?这么简单的情况直接创建一个order_id的索引不就行了吗?
如果只有一个order_id索引,没什么问题,会用到这个索引,
然后mysql要去磁盘上的表里面取到product_id。
如果有组合索引的话,mysql可以完全从索引中取到product_id,速度自然会快。

再多说几句组合索引的最左优先原则:
组合索引的第一个字段必须出现在查询组句中,这个索引才会被用到。
如果有一个组合索引(col_a,col_b,col_c)
下面的情况都会用到这个索引:

col_a = "some value";
col_a = "some value" and col_b = "some value";
col_a = "some value" and col_b = "some value" and col_c = "some value";
col_b = "some value" and col_a = "some value" and col_c = "some value";

对于最后一条语句,mysql会自动优化成第三条的样子~~。

下面的情况就不会用到索引:

col_b = "aaaaaa";
col_b = "aaaa" and col_c = "cccccc";

mysql官方文档放弃CHM格式,改用EPUB格式。

  • Posted on September 7, 2010 at 11:07 am

原文地址:

http://blogs.sun.com/mysqlf/entry/farewell_chm_hello_epub

很长一段时间以来,mysql官方的文档都是CHM格式的。
可以在 http://dev.mysql.com/doc 下载到。
CHM(Complied HTML Help)1997年就在windows上做为标准的帮助文件出现了。
那时应该是win95吧?或者win98? win98se?
到现在已经10多年了,这项技术已经过时了。
到了Windows Vista,微软使用了AP Help,可惜好景不长,它随着vista一起没落了。
后来人们更多地使用HTML或者PDF格式的帮助文档。

编译一个CHM格式的文档,是一件比较吃力的事情。
这里不谈细节。
最大的问题就是编译之后,没有办法验证编译出的CHM文档是否正确,
只能人工一页一页地去检查。
所以呀,这项技术已经过时了。

MYSQL5.1的文档包含160万个单词…编译成CHM文档,非常痛苦,经常会编译出错。
所以官方几个月之前就决定,不再提供CHM格式的帮助文档。
虽然在下载页面还可以看到CHM的链接,但是点击之后,就是404了。

取而代之的就是EPUB格式的文档,15M左右。
EPUB类似于PDF,open standard format,可以在pc, mobile 上面阅读。
平时我们都用 Firefox的一个扩展epubreader 来阅读EPUB文档。
文档的加载速度还是蛮快的,而且文字颜色还可以自定义。

恢复mysql数据的另外一种方法–mysql replication 主从数据库

  • Posted on September 5, 2010 at 11:28 am

原文地址:

http://www.xaprb.com/blog/2010/09/04/why-mysql-replication-is-better-than-mysqlbinlog-for-recovery

通常,我们在恢复mysql数据的时候,都是恢复一个完整的备份,
再通过mysqlbinlog来导入此备份之后的二进制日志(binary logs)
这里有另外一个更好的方法:

操作如下:
首先恢复一个完整的备份,假设此服务器为SERVER_A。
然后在另外一台服务器SERVER_B上面,安装一个新的mysql,复制过去二进制日志文件和二进制日志的索引文件(index),然后启动mysqld,设置为主服务器。
最后把SERVER_A设置为 SERVER_B的从服务器,从第一个二进制日志文件的position 0 开始 replication复制。

为什么这个方法更好呢?

因为测试证明,replication比mysqlbinlog更加可靠。

比如下面这条语句:
insert into tbl(col) values(connection_id());
使用mysqlbinlog进行恢复,肯定会得到错误的结果。

而且在某些版本的mysql上,BLOB类型的数据无法通过mysqlbinlog恢复。
详见:http://bugs.mysql.com/bug.php?id=33048

而且replication更加灵活,你可以使用start slave until 等语句,
还可以暂时停止复制,继续复制等等。

mysql存储过程–自动删除长期不活动的连接

  • Posted on September 3, 2010 at 10:57 am

原文地址: http://feedproxy.google.com/~r/blogspot/yyLW/~3/i0SICZyo1Wk/mysql-kill-sleeping-connections.html
要翻墙。

mysqld的wait_timeout通常都比较大,有的连接也不会自己断开,就在那里Sleep着,占用一个连接数。
人工去kill的话,显然比较累,那么就写一个存储过程吧,让它自己去执行。

使用root登录mysql之后,运行以下代码:

USE test;
 
DELIMITER $$ 
DROP PROCEDURE IF EXISTS `uKillSleepingSessions`$$
 
 
CREATE PROCEDURE `uKillSleepingSessions`() 
COMMENT 'This routne is used to kill idle sessions'
READS SQL DATA 
 
 
BEGIN 
 
 
DECLARE no_more_rows BOOLEAN;
DECLARE loop_cntr INT DEFAULT 0;
DECLARE num_rows INT DEFAULT 0;
DECLARE uID bigint(4);
 
DECLARE my_cur CURSOR FOR
SELECT ID 
  FROM information_schema.PROCESSLIST PL
WHERE PL.COMMAND='Sleep' AND PL.TIME > 180;
 
 
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET no_more_rows = TRUE;
 
 
OPEN my_cur;
select FOUND_ROWS() into num_rows;
 
the_loop: LOOP
 
 
FETCH my_cur
INTO uID;
 
 
IF no_more_rows THEN
    CLOSE my_cur;
    LEAVE the_loop;
END IF;
 
 
SET @tmp_sql= CONCAT("KILL ",uID); 
PREPARE s1 FROM @tmp_sql; 
EXECUTE s1; 
 
 
DEALLOCATE PREPARE s1; 
SET loop_cntr = loop_cntr + 1;
END LOOP the_loop;
 
 
END$$
DELIMITER ;

调用方法:
root登录到mysql之后,运行:

call test.uKillSleepingSessions();

就会自动kill sleep超过180秒的连接了。

mysql> show processlist;
+----------+------+-----------+------+---------+------+-------+------------------+
| Id       | User | Host      | db   | Command | Time | State | Info             |
+----------+------+-----------+------+---------+------+-------+------------------+
| 34035650 | root | localhost | NULL | Sleep   |  251 |       | NULL             | 
| 34036126 | root | localhost | test | Query   |    0 | NULL  | show processlist | 
+----------+------+-----------+------+---------+------+-------+------------------+
2 rows in set (0.00 sec)
 
mysql> call test.uKillSleepingSessions();
Query OK, 0 rows affected (0.01 sec)
 
mysql> show processlist;            
+----------+------+-----------+------+---------+------+-------+------------------+
| Id       | User | Host      | db   | Command | Time | State | Info             |
+----------+------+-----------+------+---------+------+-------+------------------+
| 34036126 | root | localhost | test | Query   |    0 | NULL  | show processlist | 
+----------+------+-----------+------+---------+------+-------+------------------+
1 row in set (0.00 sec)

可以写个cron

*/10 * * * *  /usr/bin/mysql -uroot -pxxxx -e "call test.uKillSleepingSessions()"

DBA的新工具–笔和笔记本

  • Posted on September 3, 2010 at 10:28 am

原文地址: http://opensourcedba.wordpress.com/2010/09/01/pen-and-paper-as-a-dba-tool/
PS:这里说的笔记本是指几块钱的那种。不是laptop。

“嗨,亲爱的DBA。您还记得四五个月之前,修改过192.168.1.111上面mysql的配置吗?修改之后好象没什么效果,您能尽快把配置改回原来的样子吗?”

这简直是DBA的恶梦。我们怎么可能记得当时改过什么?
要知道我有数十台的mysql服务器要维护的。

最简单的办法:
准备一个笔记本,还有一支笔。时刻带在你身边。
无论何时你要对数据库进行操作,请记录下来您所做的改动,为什么要这样改,日期。
开始的时候,可能会觉得无聊,请坚持下来,它一定会帮你的大忙。

并且,一定要写得比较清楚。如果您自己都认不出来,那就完了。

有人可能会问了,我把这些改动记录到我的电脑里面不行吗?
原因如下:
1 笔记本重量轻,更容易携带。
2 不需要用电
3 展示您的一手好字。

然后,请把所有的my.cnf打印出来,放到笔记本里面。

快速关闭使用InnoDB的mysqld服务

  • Posted on September 3, 2010 at 9:37 am

原文地址: http://www.mysqlperformanceblog.com/2010/09/02/how-long-innodb-shutdown-may-take/
以及

http://www.mysqlperformanceblog.com/2009/04/15/how-to-decrease-innodb-shutdown-times/

有时候,关闭使用InnoDB的mysqld服务,速度相当慢,
数分钟之久,一度让我以为会有什么错误发生。
这是因为mysqld要把内存buffer pool里面的dirty pages 写回到硬盘。

innodb_max_dirty_pages_pct 此参数控制buffer pool里面最多有百分之多少的pages可以是dirty page。
默认值是90。也就是说buffer pool的90%都有可能是dirty pages。

写入硬盘所用的时间还跟另外二个因素有关:
1 硬盘。 普通硬盘每秒可以写入200次。牛X一些的可以每秒写入数千次。写回是多线程的,如果您的数据分布在多块硬盘上,那么速度还是会快一些的。
2 dirty pages是否连续。 如果连续,mysqld可能每次会写入1MB的数据。如果不连续的话,那就只能一页一页地写了。通常每页大小是16K或者8K。

如果我们只有一块每秒200次写入的硬盘,48G的buffer pool, 90%是dirty pages,而且完全是随机不连续的page,那么需要13500秒才能完全写入。

如果在写回的过程中,服务器断电,或者您kill -9 mysqld了,也不用担心数据出错。
下次mysqld启动的时候,会根据日志进行数据恢复。可能需要更长的时间。

一个加速的办法是:pre-flush 。 提前写回dirty pages.
操作方法:

mysql> set global innodb_max_dirty_pages_pct = 0;
$ mysqladmin ext -i10 -uroot -p | grep dirty

可以查看到目前buffer pool中有多少个dirty page,每10秒刷新一次。
当这个值比较小的时候,您就可以关闭mysqld了。
关闭速度一定非常的快。

mysql 时间函数

  • Posted on April 14, 2010 at 10:49 am

mysql 时间函数

mysql> select now(), current_date(), current_time(), unix_timestamp();
+---------------------+----------------+----------------+------------------+
| now()               | current_date() | current_time() | unix_timestamp() |
+---------------------+----------------+----------------+------------------+
| 2010-04-13 10:32:04 | 2010-04-13     | 10:32:04       |       1271125924 |
+---------------------+----------------+----------------+------------------+
mysql> select FROM_UNIXTIME(1271125924), unix_timestamp('2010-04-13 10:32:04');
+---------------------------+---------------------------------------+
| FROM_UNIXTIME(1271125924) | unix_timestamp('2010-04-13 10:32:04') |
+---------------------------+---------------------------------------+
| 2010-04-13 10:32:04       |                            1271125924 |
+---------------------------+---------------------------------------+
mysql> select TO_DAYS('2010-04-13 22:33:11'), from_days(734240);
+--------------------------------+-------------------+
| TO_DAYS('2010-04-13 22:33:11') | from_days(734240) |
+--------------------------------+-------------------+
|                         734240 | 2010-04-13        |
+--------------------------------+-------------------+
year('2010-04-13 22:33:11'): 2010
        month('2010-04-13'): 4
    monthname('2010-04-13'): April
   dayofmonth('2010-04-13'): 13
    dayofyear('2010-04-13'): 103
    dayofweek('2010-04-13'): 3
      weekday('2010-04-13'): 1
hour('2010-04-13 22:33:11'): 22
         minute('22:33:11'): 33
         second('22:33:11'): 11
1 row in set (0.00 sec)

注:2010-04-13是星期二

                      "1997-12-31 23:59:59" + INTERVAL 1 SECOND: 1998-01-01 00:00:00
                                  INTERVAL 1 DAY + "1997-12-31": 1998-01-01
                               "1998-01-01" - INTERVAL 1 SECOND: 1997-12-31 23:59:59
             DATE_ADD("1997-12-31 23:59:59", INTERVAL 1 SECOND): 1998-01-01 00:00:00
               DATE_ADD("1997-12-31 23:59:59",  INTERVAL 1 DAY): 1998-01-01 23:59:59
 DATE_ADD("1997-12-31 23:59:59",  INTERVAL "1:1" MINUTE_SECOND): 1998-01-01 00:01:00
DATE_SUB("1998-01-01 00:00:00",  INTERVAL "1 1:1:1" DAY_SECOND): 1997-12-30 22:58:59
    DATE_ADD("1998-01-01 00:00:00",  INTERVAL "-1 10" DAY_HOUR): 1997-12-30 14:00:00
                        DATE_SUB("1998-01-02", INTERVAL 31 DAY): 1997-12-02
                                EXTRACT(YEAR FROM "1999-07-02"): 1999
                 EXTRACT(YEAR_MONTH FROM "1999-07-02 01:02:03"): 199907
                 EXTRACT(DAY_MINUTE FROM "1999-07-02 01:02:03"): 20102

jira与mysql的整合

  • Posted on March 8, 2010 at 4:25 pm

为什么要用外部数据库呢?

As part of its installation process, JIRA Standalone automatically installs, configures and connects itself to an HSQLDB database. This is fine for evaluation purposes, however HSQLDB is prone to database corruption. For production installations, we strongly recommend that you connect JIRA to an enterprise database. This also lets you take advantage of existing database backup and recovery procedures.

英文版文档: http://confluence.atlassian.com/display/JIRA/Connecting+JIRA+to+MySQL

我们使用mysql 的 innodb .  因为jira是要用到事务的。

create database jiradb character set utf8;

grant all on jiradb.* to ‘jirauser’@’localhost’ identified by ‘jirapwd227’;

复制 mysql-connector-java-5.1.12-bin.jar 到 jira安装目录/common/lib/下。

mysql-connector-java-5.1.12-bin.jar的下载地址: http://dev.mysql.com/downloads/connector/j

修改conf/server.xml

<Server port=”10001″ shutdown=”SHUTDOWN”>

<Service>

<Connector port=”10000″

maxHttpHeaderSize=”8192″ maxThreads=”150″ minSpareThreads=”25″ maxSpareThreads=”75″

enableLookups=”false” redirectPort=”8443″ acceptCount=”100″

connectionTimeout=”20000″ disableUploadTimeout=”true” />

<Engine defaultHost=”localhost”>

<Host appBase=”webapps” unpackWARs=”true” autoDeploy=”true”>

<Context path=”" docBase=”${catalina.home}/atlassian-jira” reloadable=”false”>

<Resource name=”jdbc/JiraDS” auth=”Container” type=”javax.sql.DataSource”

username=”jirauser”

password=”jirapwd227″

driverClassName=”com.mysql.jdbc.Driver”

url=”jdbc:mysql://localhost/jiradb? autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=UTF8″

maxActive=”20″

validationQuery=”select 1″/>

<Resource name=”UserTransaction” auth=”Container” type=”javax.transaction.UserTransaction”

factory=”org.objectweb.jotm.UserTransactionFactory” jotm.timeout=”60″/>

<Manager pathname=”"/>

</Context>

</Host>

</Engine>

</Service>

</Server>

注意:我们删除了原文件中的

minEvictableIdleTimeMillis  与 timeBetweenEvictionRunsMillis

修改 atlassian-jira/WEB-INF/classes/entityengine.xml

field-type-name 改成 mysql

删除 schema-name=”PUBLIC”

重新启动jira服务。

再多修改一下, 为以后与confluence整合做好准备。

修改 conf/server.xml

把原来的 <Context path=””

改为 <Context path=”/jira”

重启jira服务。 以后再访问jira 就用 http://192.168.0.163:10000/jira

mysql配置中的concurrent_insert与low-priority-updates

  • Posted on February 23, 2010 at 9:49 am

MyISAM在读操作占主导的情况下是很高效的。特别是count(*)操作,简直无敌了。 可一旦出现大量的读写并发,由于table lock的原因,同InnoDB相比,MyISAM的效率就会直线下降. 关于myisam 的table lock ,可以参照我的另外一篇日志: http://www.masalife.com/index.php/archives/184

而且,MyISAM和InnoDB的数据存储方式也有显著不同: 通常,在MyISAM里,新数据会被附加到数据文件的结尾,可是做了一些DELETE操作之后,数据文件就不再是连续的,形象一点来说,就是数据文件里出现了很多hole,此时再插入新数据时,按缺省设置会先看这些hole的大小是否可以容纳下新数据,如果可以,则直接把新数据保存到hole里,反之,则把新数据保存到数据文件的结尾。之所以这样做是为了减少数据文件的大小,降低文件碎片的产生。

但InnoDB里则不是这样,在InnoDB里,由于主键是cluster的,所以,数据文件始终是按照主键排序的,如果使用自增ID做主键,则新数据始终是位于数据文件的结尾。 了解了这些基础知识,下面说说MyISAM几个容易忽视的配置选项:

concurrent_insert:

通常来说,在MyISAM里读写操作是串行的,但当对同一个表进行查询和插入操作时,为了降低锁竞争的频率,根据concurrent_insert的设置,MyISAM是可以并行处理查询和插入的:

mysql_version() > 5.0.6 :

当concurrent_insert=0时,不允许在SELECT操作的时候,并发执行INSERT。

当concurrent_insert=1时,如果表里面没有hole(可能性不大吧),SELECT操作的时候,可以并发执行INSERT,新数据位于数据文件结尾(缺省)。如果同时有多个INSERT操作,这些INSERT是串行执行的。

当concurrent_insert=2时,不管表有没有hole,在select操作的时候,都允许在数据文件结尾并发执行INSERT。如果同时有多个INSERT操作,这些INSERT是串行的。而且一旦select释放了lock之后,INSERT还是会首先尝试插入数据到hole里面。 For a table with a hole, new rows are inserted at the end of the table if it is in use by another thread. Otherwise, MySQL acquires a normal write lock and inserts the row into the hole.

这样看来,把concurrent_insert设置为2是很划算的,至于由此产生的文件碎片,可以定期使用OPTIMIZE TABLE语法优化。

max_write_lock_count:

缺省情况下,写操作的优先级要高于读操作的优先级,即便是先发送的读请求,后发送的写请求,此时也会优先处理写请求,然后再处理读请求。这就造成一个问题:一旦我发出若干个写请求,就会堵塞所有的读请求,直到写请求全都处理完,才有机会处理读请求。此时可以考虑使用max_write_lock_count: max_write_lock_count=2 有了这样的设置,当系统处理2个写操作后,就会暂停写操作,给读操作执行的机会。

low-priority-updates:

我们还可以更干脆点,直接降低写操作的优先级,给读操作更高的优先级。 low-priority-updates=1

综合来看,concurrent_insert=2是绝对推荐的,但是要定期optimize table. 至于max_write_lock_count=1和low-priority-updates=1,则视情况而定,如果可以降低写操作的优先级,则使用low-priority-updates=1,否则使用max_write_lock_count=2。

Top