深入MYSQL字符数字转换的详解

1、将字符的数字转成数字,比如’0’转成0可以直接用加法来实现

例如:将pony表中的d 进行排序,可d的定义为varchar,可以这样解决

select * from pony order by (d+0)

2、在进行ifnull处理时,比如 ifnull(a/b,’0′) 这样就会导致 a/b成了字符串,因此需要把’0’改成0,即可解决此困扰

3、比较数字和varchar时,比如a=11,b=”11ddddd”;

则 select 11=”11ddddd”相等

若绝对比较可以这样:

select binary 11 =binary "11ddddd"

4、字符集转换 : CONVERT(xxx USING gb2312)

类型转换和SQL Server一样,就是类型参数有点点不同 : CAST(xxx AS 类型) , CONVERT(xxx,类型),类型必须用下列的类型:

可用的类型

二进制,同带binary前缀的效果 : BINARY

字符型,可带参数 : CHAR()

日期 : DATE

时间: TIME

日期时间型 : DATETIME

浮点数 : DECIMAL

整数 : SIGNED

无符号整数 : UNSIGNED

用SQL实现统计报表中的”小计”与”合计”的方法详解

客户提出需求,针对某一列分组加上小计,合计汇总。网上找了一些有关SQL加合计的语句。都不是很理想。决定自己动手写。

思路有三个:

1、很多用GROUPPING和ROLLUP来实现。

优点:实现代码简洁,要求对GROUPPING和ROLLUP很深的理解。

缺点:低版本的Sql Server不支持。

2、游标实现。

优点:思路逻辑简洁。

缺点:复杂和低效。

3、利用临时表。

优点:思路逻辑简洁,执行效率高。SQL实现简单。

缺点:数据量大时耗用内存.

综合三种情况,决定“利用临时表”实现。

实现效果

原始表TB

未分类

加上小计,合计后效果

未分类

SQL语句

select * into #TB from TB 
select * into #TB1 from #TB where 1<>1 
select distinct zcxt into #TBype from #TB order by zcxt
select identity(int,1,1)  fid,zcxt into #TBype1 from #TBype
DECLARE @i int
DECLARE @k int

 select @i=COUNT(*) from #TBype
 set @k=0
  DECLARE @strfname varchar(50)
 WHILE @k < @i
  BEGIN
   Set @k =@k +1
   select @strfname=zcxt from #TBype1 where fid =@k
   set IDENTITY_INSERT #TB1 ON
      insert into #TB1(fid,qldid,fa_cardid,ztbz,fa_name,model,i_number,gzrq,zcyz,ljzj,jz,sybm,zcxt,fa_ljjzzb)
       select fid,qldid,fa_cardid,ztbz,fa_name,model,i_number,gzrq,zcyz,ljzj,jz,sybm,zcxt,fa_ljjzzb from
      (
   select * from #TB where zcxt=@strfname 
   union all
         select 0 fid,'' qldid,'' fa_cardid,'' ztbz,'小计' fa_name,'' model,sum(i_number) as i_number,'' gzrq,sum(CAST(zcyz as money)) as zcyz,sum(CAST(ljzj as money)) as ljzj,sum(CAST(jz as money)) as jz,'' sybm,'' zcxt,Sum(fa_ljjzzb) as fa_ljjzzb
   from #TB where zcxt=@strfname
   group by ztbz 
  ) as B
 set IDENTITY_INSERT #TB1 off
  END
select qldid,fa_cardid,zcxt,fa_name,model,i_number,gzrq,zcyz,ljzj,jz,sybm,ztbz,fa_ljjzzb from #TB1
union all
select '' qldid,'' fa_cardid,'' ztbz,'合计' fa_name,'' model,sum(i_number) as i_number,'' gzrq,sum(CAST(zcyz as money)) as zcyz,sum(CAST(ljzj as money)) as ljzj,sum(CAST(jz as money)) as jz,'' sybm,'' zcxt,Sum(fa_ljjzzb) as fa_ljjzzb
from #TB
drop table #TB1 
drop table #TBype1
drop table #TBype
drop table #TB 

扩展改进

可以改写成一个通用的添加合计小计的存储过程。

SQL语句中SUM与COUNT的区别深入分析

SUM是对符合条件的记录的数值列求和

COUNT 是对查询中符合条件的结果(或记录)的个数

例如:

表fruit

id     name    price

1     apple     3.00

2     pear       4.00

select count(price) from fruit; —-执行之后结果为:2 (表示有2条记录)

select sum(price) from fruit;—执行之后结果为:7:00(表示各记录price字段之和为7.00)

基于MySQL游标的具体使用详解

测试表 level ;

create table test.level (name varchar(20));

再 insert 些数据 ;

代码

初始化

drop procedure if exists useCursor //

建立 存储过程 create

CREATE PROCEDURE useCursor()

BEGIN

局部变量的定义 declare

declare tmpName varchar(20) default '' ;  
declare allName varchar(255) default '' ;  
declare cur1 CURSOR FOR SELECT name FROM test.level ;  

MySQL 游标 异常后 捕捉

并设置 循环使用 变量 tmpname 为 null 跳出循环。

declare CONTINUE HANDLER FOR SQLSTATE '02000' SET tmpname = null; 

开游标

OPEN cur1;

游标向下走一步

FETCH cur1 INTO tmpName;

循环体 这很明显 把MySQL 游标查询出的 name 都加起并用 ; 号隔开

WHILE ( tmpname is not null) DO 
set tmpName = CONCAT(tmpName ,";") ; 
set allName = CONCAT(allName ,tmpName) ; 

游标向下走一步

FETCH cur1 INTO tmpName;

结束循环体:

END WHILE;

关闭游标

CLOSE cur1;

选择数据

select allName ;

结束存储过程

END;//

调用存储过程:

call useCursor()//

运行结果:

mysql> call useCursor()//

+--------------------------------------+
| allName                              |
+--------------------------------------+
| f1;c3;c6;c5;c2;c4;c1;f1;f3;f4;f2;f5; |
+--------------------------------------+
1 row in set (0.00 sec)

loop循环游标:

DELIMITER $$  

DROP PROCEDURE IF EXITS cursor_example$$  
CREATE PROCEDURE cursor_example()  
     READS SQL DATA  
BEGIN  
     DECLARE l_employee_id INT;  
     DECLARE l_salary NUMERIC(8,2);  
     DECLARE l_department_id INT;  
     DECLARE done INT DEFAULT 0;  
     DECLARE cur1 CURSOR FOR SELECT employee_id, salary, department_id FROM employees;  
     DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;  

     OPEN cur1;  
     emp_loop: LOOP  
         FETCH cur1 INTO l_employee_id, l_salary, l_department_id;  
         IF done=1 THEN  
             LEAVE emp_loop;  
         END IF;  
     END LOOP emp_loop;  
     CLOSE cur1;  
END$$  
DELIMITER ;  

repeat循环游标:

/*创建过程*/
DELIMITER //
DROP PROCEDURE IF EXISTS test //
CREATE PROCEDURE test()
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE a VARCHAR(200) DEFAULT '';
    DECLARE c VARCHAR(200) DEFAULT '';

    DECLARE mycursor CURSOR FOR SELECT  fusername FROM uchome_friend;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;

    OPEN mycursor;

    REPEAT 
        FETCH mycursor INTO a;
        IF NOT done THEN
            SET c=CONCAT(c,a);/*字符串相加*/
        END IF;

    UNTIL done END REPEAT;

    CLOSE mycursor;

    SELECT c;
END //
DELIMITER ;
/*创建过程*/
DELIMITER //
DROP PROCEDURE IF EXISTS test //
CREATE PROCEDURE test()
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE a VARCHAR(200) DEFAULT '';
    DECLARE c VARCHAR(200) DEFAULT '';

    DECLARE mycursor CURSOR FOR SELECT  fusername FROM uchome_friend;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;

    OPEN mycursor;

    REPEAT 
        FETCH mycursor INTO a;
        IF NOT done THEN
            SET c=CONCAT(c,a);/*字符串相加*/
        END IF;

    UNTIL done END REPEAT;

    CLOSE mycursor;

    SELECT c;
END //
DELIMITER ;

使用use index优化sql查询的详细介绍

先看一下arena_match_index的表结构,大家注意表的索引结构

CREATE TABLE `arena_match_index` (
  `tid` int(10) unsigned NOT NULL DEFAULT '0',
  `mid` int(10) unsigned NOT NULL DEFAULT '0',
  `group` int(10) unsigned NOT NULL DEFAULT '0',
  `round` tinyint(3) unsigned NOT NULL DEFAULT '0',
  `day` date NOT NULL DEFAULT '0000-00-00',
  `begintime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  UNIQUE KEY `tm` (`tid`,`mid`),
  KEY `mid` (`mid`),
  KEY `begintime` (`begintime`),
  KEY `dg` (`day`,`group`),
  KEY `td` (`tid`,`day`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

接着看下面的sql:

SELECT round  FROM arena_match_index WHERE `day` = '2010-12-31' AND `group` = 18 AND `begintime` < '2010-12-31 12:14:28' order by begintime LIMIT 1; 

这条sql的查询条件显示可能使用的索引有`begintime`和`dg`,但是由于使用了order by begintime排序mysql最后选择使用`begintime`索引,explain的结果为:

mysql> explain SELECT round  FROM arena_match_index  WHERE `day` = '2010-12-31' AND `group` = 18 AND `begintime` < '2010-12-31 12:14:28' order by begintime LIMIT 1;
+----+-------------+-------------------+-------+---------------+-----------+---------+------+--------+-------------+
| id | select_type | table             | type  | possible_keys | key       | key_len | ref  | rows   | Extra       |
+----+-------------+-------------------+-------+---------------+-----------+---------+------+--------+-------------+
|  1 | SIMPLE      | arena_match_index | range | begintime,dg  |<STRONG> </STRONG>begintime<STRONG> </STRONG>| 8       | NULL | 226480 | Using where | 
+----+-------------+-------------------+-------+---------------+-----------+---------+------+--------+-------------+

explain的结果显示使用`begintime`索引要扫描22w条记录,这样的查询性能是非常糟糕的,实际的执行情况也是初次执行(还未有缓存数据时)时需要30秒以上的时间。

实际上这个查询使用`dg`联合索引的性能更好,因为同一天同一个小组内也就几十场比赛,因此应该优先使用`dg`索引定位到匹配的数据集合再进行排序,那么如何告诉mysql使用指定索引呢?使用use index语句:

mysql> explain SELECT round  FROM arena_match_index use index (dg) WHERE `day` = '2010-12-31' AND `group` = 18 AND `begintime` < '2010-12-31 12:14:28' order by begintime LIMIT 1;
+----+-------------+-------------------+------+---------------+------+---------+-------------+------+-----------------------------+
| id | select_type | table             | type | possible_keys | key  | key_len | ref         | rows | Extra                       |
+----+-------------+-------------------+------+---------------+------+---------+-------------+------+-----------------------------+
|  1 | SIMPLE      | arena_match_index | ref  | dg            | dg   | 7       | const,const |  757 | Using where; Using filesort | 
+----+-------------+-------------------+------+---------------+------+---------+-------------+------+-----------------------------+

explain结果显示使用`dg`联合索引只需要扫描757条数据,性能直接提升了上百倍,实际的执行情况也是几乎立即就返回了查询结果。

在最初的查询语句中只要把order by begintime去掉,mysql就会使用`dg`索引了,再次印证了order by会影响mysql的索引选择策略!

mysql> explain SELECT round  FROM arena_match_index  WHERE `day` = '2010-12-31' AND `group` = 18 AND `begintime` < '2010-12-31 12:14:28'  LIMIT 1;
+----+-------------+-------------------+------+---------------+------+---------+-------------+------+-------------+
| id | select_type | table             | type | possible_keys | key  | key_len | ref         | rows | Extra       |
+----+-------------+-------------------+------+---------------+------+---------+-------------+------+-------------+
|  1 | SIMPLE      | arena_match_index | ref  | begintime,dg  | dg   | 7       | const,const |  717 | Using where | 
+----+-------------+-------------------+------+---------------+------+---------+-------------+------+-------------+

通过上面的例子说mysql有时候也并不聪明,并非总能做出最优选择,还是需要我们开发者对它进行“调教”!

mysql prompt的用法详解

prompt命令可以在mysql提示符中显示当前用户、数据库、时间等信息

mysql -uroot -p --prompt="\u@\h:\d \r:\m:\s>"

设置成功后:

Welcome to the MySQL monitor.  Commands end with ; or g.
Your MySQL connection id is 5
Server version: 5.1.60-log Source distribution
Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.
root@localhost:(none) 11:04:23>

也可以在在my.cnf配置文件里进行配置:

[mysql]  
prompt=mysql(\u@\h:\d)>  

default-character-set=utf8  

选项说明:

Option  Description
c  A counter that increments for each statement you issue
D  The full current date
d The default database
h The server host
l The current delimiter (new in 5.1.12)
m  Minutes of the current time
n  A newline character
O  The current month in three-letter format (Jan, Feb, …)
o  The current month in numeric format
P  am/pm
p The current TCP/IP port or socket file
R  The current time, in 24-hour military time (0–23)
r  The current time, standard 12-hour time (1–12)
S  Semicolon
s  Seconds of the current time
t  A tab character
U   www.jb51.net  
Your full user_name@host_name account name

u Your user name
v  The server version
w  The current day of the week in three-letter format (Mon, Tue, …)
Y  The current year, four digits
y  The current year, two digits
_  A space
   A space (a space follows the backslash)
'  Single quote
"  Double quote
\  A literal “” backslash character
x 
x, for any “x” not listed above

mysql prompt一个特别好用的命令

想必大家在命令行操作mysql的时候会十分发愁现在的操作的是那个数据库吧,至少我就纠结过,可能您会说我打一条命令不就知道了,是的这样做确实可以的。

可是今儿给大家介绍个好用的命令–prompt

[root@fsailing1 ~]# mysql -uroot -p --prompt="\u@\h:\d \r:\m:\s>"
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or g.
Your MySQL connection id is 378
Server version: 5.0.95 Source distribution

Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.
root@localhost:(none) 08:23:32>use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
root@localhost:test 08:23:44>exit;

这样做的好处就是让我们大家在操作数据库时更加方便快捷。

您也可以在在my.cnf配置文件里进行配置:

[mysql]
prompt=mysql(\u@\h:\d)>

default-character-set=utf8
[root@fsailing1 ~]# vim /etc/my.cnf
[root@fsailing1 ~]# service mysqld restart
停止 MySQL:                                               [确定]
启动 MySQL:                                               [确定]
[root@fsailing1 ~]# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or g.
Your MySQL connection id is 2
Server version: 5.0.95 Source distribution

Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.
mysql(root@localhost:(none))>use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql(root@localhost:test)>

这里也给出需要的一些个选项供大家参考。

未分类

MySQL查询优化–调整内部变量的详解

MySQL是如此的开放,所以可轻松地进一步调整其缺省设置以获得更优的性能及稳定性。需要优化的一些关键变量如下:

改变索引缓冲区长度(key_buffer)

一般,该变量控制缓冲区的长度在处理索引表(读/写操作)时使用。MySQL使用手册指出该变量可以不断增加以确保索引表的最佳性能,并推荐使用与系统内存25%的大小作为该变量的值。这是MySQL十分重要的配置变量之一,如果你对优化和提高系统性能有兴趣,可以从改变 key_buffer_size变量的值开始。

改变表长(read_buffer_size)

当一个查询不断地扫描某一个表,MySQL会为它分配一段内存缓冲区。read_buffer_size变量控制这一缓冲区的大小。如果你认为连续扫描进行得太慢,可以通过增加该变量值以及内存缓冲区大小提高其性能。

设定打开表的数目的最大值(table_cache)

该变量控制MySQL在任何时候打开表的最大数目,由此能控制服务器响应输入请求的能力。它跟max_connections变量密切相关,增加 table_cache值可使MySQL打开更多的表,就如增加max_connections值可增加连接数一样。当收到大量不同数据库及表的请求时,可以考虑改变这一值的大小。

对缓长查询设定一个时间限制(long_query_time)

MySQL带有“慢查询日志”,它会自动地记录所有的在一个特定的时间范围内尚未结束的查询。这个日志对于跟踪那些低效率或者行为不端的查询以及寻找优化对象都非常有用。long_query_time变量控制这一最大时间限定,以秒为单位。

以上讨论并给出用于分析和优化SQL查询的三种工具的使用方法,以此提高你的应用程序性能。使用它们快乐地优化吧!

MySQL查询优化之explain的深入解析

在分析查询性能时,考虑EXPLAIN关键字同样很管用。EXPLAIN关键字一般放在SELECT查询语句的前面,用于描述MySQL如何执行查询操作、以及MySQL成功返回结果集需要执行的行数。explain 可以帮助我们分析 select 语句,让我们知道查询效率低下的原因,从而改进我们查询,让查询优化器能够更好的工作。

一、MySQL 查询优化器是如何工作的

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

EXPLAIN 的每个输出行提供一个表的相关信息,并且每个行包括下面的列:

未分类

未分类

未分类

未分类

未分类

未分类

未分类

未分类

未分类

未分类

extra 中出现以下 2 项意味着 MYSQL 根本不能使用索引,效率会受到重大影响。应尽可能对此进行优化。

未分类

下面来举一个例子来说明下 explain 的用法。

先来一张表:

CREATE TABLE IF NOT EXISTS `article` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`author_id` int(10) unsigned NOT NULL,
`category_id` int(10) unsigned NOT NULL,
`views` int(10) unsigned NOT NULL,
`comments` int(10) unsigned NOT NULL,
`title` varbinary(255) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`)
);

再插几条数据:

INSERT INTO `article`
(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES
(1, 1, 1, 1, '1', '1'),
(2, 2, 2, 2, '2', '2'),
(1, 1, 3, 3, '3', '3');

需求:

查询 category_id 为 1 且 comments 大于 1 的情况下,views 最多的 article_id。

先查查试试看:

EXPLAIN
SELECT author_id
FROM `article`
WHERE category_id = 1 AND comments > 1
ORDER BY views DESC
LIMIT 1G

看看部分输出结果:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: article
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 3
        Extra: Using where; Using filesort
1 row in set (0.00 sec)

很显然,type 是 ALL,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。

嗯,那么最简单的解决方案就是加索引了。好,我们来试一试。查询的条件里即 where 之后共使用了 category_id,comments,views 三个字段。那么来一个联合索引是最简单的了。

ALTER TABLE `article` ADD INDEX x ( `category_id` , `comments`, `views` );

结果有了一定好转,但仍然很糟糕:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: article
         type: range
possible_keys: x
          key: x
      key_len: 8
          ref: NULL
         rows: 1
        Extra: Using where; Using filesort
1 row in set (0.00 sec)

type 变成了 range,这是可以忍受的。但是 extra 里使用 Using filesort 仍是无法接受的。但是我们已经建立了索引,为啥没用呢?这是因为按照 BTree 索引的工作原理,先排序 category_id,如果遇到相同的 category_id 则再排序 comments,如果遇到相同的 comments 则再排序 views。当 comments 字段在联合索引里处于中间位置时,因comments > 1 条件是一个范围值(所谓 range),MySQL 无法利用索引再对后面的 views 部分进行检索,即 range 类型查询字段后面的索引无效。

那么我们需要抛弃 comments,删除旧索引:

DROP INDEX x ON article;

然后建立新索引:

ALTER TABLE `article` ADD INDEX y ( `category_id` , `views` ) ;

接着再运行查询:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: article
         type: ref
possible_keys: y
          key: y
      key_len: 4
          ref: const
         rows: 1
        Extra: Using where
1 row in set (0.00 sec)

可以看到,type 变为了 ref,Extra 中的 Using filesort 也消失了,结果非常理想。

再来看一个多表查询的例子。

首先定义 3个表 class 和 room。

CREATE TABLE IF NOT EXISTS `class` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`card` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `book` (
`bookid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`card` int(10) unsigned NOT NULL,
PRIMARY KEY (`bookid`)
);
CREATE TABLE IF NOT EXISTS `phone` (
`phoneid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`card` int(10) unsigned NOT NULL,
PRIMARY KEY (`phoneid`)
) engine = innodb;

然后再分别插入大量测试数据。插入数据的php脚本:

<?php
$link = mysql_connect("localhost","root","870516");
mysql_select_db("test",$link);
for($i=0;$i<10000;$i++)
{
    $j   = rand(1,20);
    $sql = " insert into class(card) values({$j})";
    mysql_query($sql);
}
for($i=0;$i<10000;$i++)
{
    $j   = rand(1,20);
    $sql = " insert into book(card) values({$j})";
    mysql_query($sql);
}
for($i=0;$i<10000;$i++)
{
    $j   = rand(1,20);
    $sql = " insert into phone(card) values({$j})";
    mysql_query($sql);
}
mysql_query("COMMIT");
?>

然后来看一个左连接查询:

explain select * from class left join book on class.card = book.cardG

分析结果是:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
2 rows in set (0.00 sec)

显然第二个 ALL 是需要我们进行优化的。

建立个索引试试看:

ALTER TABLE `book` ADD INDEX y ( `card`);
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
         type: ref
possible_keys: y
          key: y
      key_len: 4
          ref: test.class.card
         rows: 1000
        Extra: 
2 rows in set (0.00 sec)

可以看到第二行的 type 变为了 ref,rows 也变成了 1741*18,优化比较明显。这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要建立索引。

删除旧索引:

DROP INDEX y ON book;

建立新索引。

ALTER TABLE `class` ADD INDEX x ( `card`);

结果

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
2 rows in set (0.00 sec)

基本无变化。

然后来看一个右连接查询:

explain select * from class right join book on class.card = book.card;

分析结果是:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
         type: ref
possible_keys: x
          key: x
      key_len: 4
          ref: test.book.card
         rows: 1000
        Extra: 
2 rows in set (0.00 sec)

优化较明显。这是因为 RIGHT JOIN 条件用于确定如何从左表搜索行,右边一定都有,所以左边是我们的关键点,一定需要建立索引。

删除旧索引:

DROP INDEX x ON class;

建立新索引。

ALTER TABLE `book` ADD INDEX y ( `card`);

结果

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
2 rows in set (0.00 sec)

基本无变化。

最后来看看 inner join 的情况:

explain select * from class inner join book on class.card = book.card;

结果:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
         type: ref
possible_keys: x
          key: x
      key_len: 4
          ref: test.book.card
         rows: 1000
        Extra: 
2 rows in set (0.00 sec)

删除旧索引:

DROP INDEX y ON book;

结果

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
2 rows in set (0.00 sec)

建立新索引。

ALTER TABLE `class` ADD INDEX x ( `card`);

结果

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
2 rows in set (0.00 sec)

综上所述,inner join 和 left join 差不多,都需要优化右表。而 right join 需要优化左表。

我们再来看看三表查询的例子

添加一个新索引:

ALTER TABLE `phone` ADD INDEX z ( `card`);
ALTER TABLE `book` ADD INDEX y ( `card`);
explain select * from class left join book on class.card=book.card left join phone on book.card = phone.card;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20000
        Extra: 
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
         type: ref
possible_keys: y
          key: y
      key_len: 4
          ref: test.class.card
         rows: 1000
        Extra: 
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: phone
         type: ref
possible_keys: z
          key: z
      key_len: 4
          ref: test.book.card
         rows: 260
        Extra: Using index
3 rows in set (0.00 sec)

后 2 行的 type 都是 ref 且总 rows 优化很好,效果不错。

MySql 中的 explain 语法可以帮助我们改写查询,优化表的结构和索引的设置,从而最大地提高查询效率。当然,在大规模数据量时,索引的建立和维护的代价也是很高的,往往需要较长的时间和较大的空间,如果在不同的列组合上建立索引,空间的开销会更大。因此索引最好设置在需要经常查询的字段中。

用ceph-deploy安装ceph并部署集群

部署安装

关于安装ceph整个过程中遇到的问题,以及靠谱的解决办法,个人亲测有效,不代表广大同行的观点。

我用的是服务器,所以没搞什么用户的问题。机器是centOS7.3。我安装的ceph版本是jewel,目前只用了3个节点。

节点IP      命名    角色

10.0.1.92   e1092   mon 
10.0.1.93   e1093   mon,osd 
10.0.1.94   e1094   mon,osd 

第一步:准备工作(以下工作在所有节点上都要执行)

首先是配置yum源:

需要特别注意的是,Ceph的安装过程还需要第三方组件依赖,其中一些第三方组件在CentOS yum.repo Base等官方源中是没有的(例如LevelDB),所以读者在安装过程中会有一定的几率遇到各种依赖关系异常,并要求先行安装XXX第三方组件的提示(例如提示先安装liblevel.so)。虽然我们后文将会介绍的Ceph辅助部署工具,Ceph-deploy的工作本质还是通过yum命令去安装管理组件,但是既然CentOS yum.repo Base官方源中并没有某些需要依赖的第三方组件,所以一旦遇到类似的组件依赖问题安装过程就没法自动继续了。解决这个问题,本示例中建议引入CentOS的第三方扩展源epel。(我在这上面坑了很久才走出来)。

首先引入第三方扩展源:

# wget  -O  /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

配置ceph源:

# vim  /etc/yum.repos.d/ceph.repo   # 增加ceph源,将下面内容输进去
[ceph]
name=ceph
baseurl=http://mirrors.aliyun.com/ceph/rpm-jewel/el7/x86_64/
gpgcheck=0
priority=1

[ceph-noarch]
name=cephnoarch
baseurl=http://mirrors.aliyun.com/ceph/rpm-jewel/el7/noarch/
gpgcheck=0
priority=1

[ceph-source]
name=Ceph source packages
baseurl=http://mirrors.163.com/ceph/rpm-jewel/el7/SRPMS
enabled=0
gpgcheck=1
type=rpm-md
gpgkey=http://mirrors.163.com/ceph/keys/release.asc
priority=1

更新源:

# yum makecache
# yum update

安装ceph(我是在各节点分别安装的,而没有用ceph-deploy一键安装,个人感觉分别安装更不容易犯错):

# yum install  -y  ceph

查看ceph版本:

# ceph -v
ceph version 10.2.9 (2ee413f77150c0f375ff6f10edd6c8f9c7d060d0)

关闭selinux:

# sed  -i  's/SELINUX=.*/SELINUX=disabled/'  /etc/selinux/config
# setenforce  0
setenforce: SELinux is disabled

关闭防火墙firewalld:

# systemctl stop firewalld
# systemctl disable firewalld

安装ntp服务器

为保证各个服务器的时间一致,安装ntp服务器

# yum install -y ntp ntpdate ntp-doc

访问:http://www.pool.ntp.org/zone/cn,获取中国区公用时间同步服务器。如:

server 0.cn.pool.ntp.org 
server 1.asia.pool.ntp.org 
server 2.asia.pool.ntp.org

将这三个服务器添加到/etc/ntp.conf,用#注释掉文件中原有的:

server 0.centos.pool.ntp.org iburst 
server 1.centos.pool.ntp.org iburst 
server 2.centos.pool.ntp.org iburst 
server 3.centos.pool.ntp.org iburst 

再执行下面的命令手工从服务器同步并启动ntp服务:

# ntpdate 0.cn.pool.ntp.org
# hwclock -w
# systemctl enable ntpd.service
# systemctl start ntpd.service

安装ssh服务:

# yum install openssh-server

第二步、准备工作做好了,现在开始部署ceph集群。

备注:以下操作均在admin-node节点执行,在本文中,由于admin-node与e1093共享,所以在e1093上执行就可以了

修改/etc/hosts

# vim /etc/hosts
10.0.1.92 e1092 
10.0.1.93 e1093 
10.0.1.94 e1094 

生成ssh密钥对并复制到各节点

# ssh-keygen
# scp-copy-id e1092
# scp-copy-id e1093
# scp-copy-id e1094

安装部署工具ceph-deploy

# yum install ceph-deploy
# ceph-deploy  --version

创建集群,
这里先要创建一个目录,因为在执行ceph-deploy的过程中会产生一些配置文件。以后只要是执行ceph-deploy的命令所产生的文件都在这个目录下。

# mkdir /home/my-cluster
# cd my-cluster

部署新的monitor节点(我的是把e1093、e1093、e1094都作为mon 节点):

# ceph-deploy new e1092 e1093 e1094

查看my-cluster目录下生成的文件:

# ls 
ceph.conf  ceph-deploy-ceph.log  ceph.mon.keyring

修改配置文件:

# vim ceph.conf
mon_initial_members = e1092, e1093, e1094
mon_host = 10.0.1.92,10.0.1.93,10.0.1.94
auth_cluster_required = none
auth_service_required = none
auth_client_required = none
osd pool default size = 2
public network = 10.0.1.0/24

对其中的参数做一下说明:

前面5项是自动生成的,不过我修改了auth_cluster_required,auth_service_required,auth_client_required为none,原始默认是cephx,表示要通过认证,这里我不需要认证,所以设为none。

osd pool default size是副本的个数,我只配置两个副本,所以设为2.
public network是公共网络,是osd之间通信的网络,该项建议设置,如果不设置,后面可能执行命令的时候有警告信息,该参数其实就是你的mon节点IP最后一项改为0,然后加上/24。例如我的节点ip是10.0.1.8*系列的,所以我的public network就是10.0.1.0/24。
部署monitors,并获取密钥key,此命令会在my-cluster目录下生成几个key。

# ceph-deploy  --overwrite-conf mon  create-initial

未分类

未分类

未分类

这里我贴出了一部分输出信息作为参考,看到最后一部分信息表示成功。

查看my-cluster目录下生成的文件:

# ls
ceph.bootstrap-mds.keyring 
ceph.bootstrap-rgw.keyring  
ceph.conf             
ceph.mon.keyring
ceph.bootstrap-osd.keyring  
ceph.client.admin.keyring   
ceph-deploy-ceph.log

查看集群状态:

# ceph -s

未分类

接下来部署osd:

由于没有足够多的磁盘,就用文件夹,我用的就是文件夹,如果用磁盘的话,网上教程更多:

在e1092 e1093 e1094上执行:

# mkdir  /var/local/osd1  
# chmod  777  -R  /var/local/osd1

以下在有ceph-deploy的节点上执行:

准备osd:

# ceph-deploy osd prepare e1092:/var/local/osd1 e1093:/var/local/osd1 e1094:/var/local/osd1

激活osd:

# ceph-deploy osd activate e1092:/var/local/osd1 e1093:/var/local/osd1 e1094:/var/local/osd1

再次查看集群状态,应该没什么问题了。

安装过程中出现的问题以及解决办法:

1、关于yum 源的问题

建议使用国内源,比如:

网易镜像源http://mirrors.163.com/ceph
阿里镜像源http://mirrors.aliyun.com/ceph
中科大镜像源http://mirrors.ustc.edu.cn/ceph
宝德镜像源 http://mirrors.plcloud.com/ceph

以jewel为例:

http://mirrors.163.com/ceph/rpm-jewel/el7
http://mirrors.163.com/ceph/keys/release.asc

2、关于执行ceph-deploy –overwrite-conf mon create-initial出现的问题(最容易出问题的感觉也就是这一条命令)

(1)出现admin-socket问题

当主机名/etc/hostname 和/etc/hosts中给该主机命的名字不一样,例如我之前安装ceph-deploy用的主机是10.0.1.90,我把主机10.0.1.90命名为e1090,即/etc/hostname设为e1090,然而我在/etc/hosts中给该主机取的名字为mon。然后就会出现这个问题,如下所示:

未分类

排查这个问题要注意看ERROR上面的INFO信息:Running command: ceph –cluster=ceph –admin-daemon /var/run/ceph-mon.mon.asok mon_status
这条信息之后就出现了error,说明很有可能它所执行的那条命令没成功,首先进入这个目录查看有没有这个文件:

# ls  /var/run/
ceph-mon.e1090.asok

发现我的这个目录下的文件名为ceph-mon.e1090.asok,而不是INFO中的ceph-mon.mon.asok,所以立马修改/etc/hosts,将名字重新命名为与hostname 一致的名字,然后这个问题就解决了。另外,如果ceph.conf配置文件中没有设置public netmork这一项,也有可能会出现这个问题,所以最好设置。

(2)[WARNIN] monitor e1090 does not exist in monmap

未分类

如果上面(1)那个问题解决之后还出现这个问题,就说明你的这台mon机器down掉了,我刚开始的时候也不敢相信,(纳尼?我刚部署它就down掉了?),其实可以看上面的信息,发现mons这一项中的addr信息是0.0.0.0:0/1,这里就知道了应该就是down掉了,而且注意看name这一项还是之前取的名字mon ,虽然第一个问题解决了,但还是没成功,所以索性换了节点,不用这个节点。

这一条可以自己通过将其他节点也作为mon节点试一下,如果其他节点成功了就该节点不成功,那一定是down掉了,用ceph -s查看集群状态的时候也能够看出来它down了。

(3)failed to connect to host:e1092,e1093, e1094

未分类

看WARNIN信息,发现说:no mon key found in host: e1092,后面的WARNIN也是一样的。这时候可以看下my-cluster目录下有没有生成key,出现这个错误应该是没有生成,解决办法就是将my-cluster目录下的ceph-mon.keying 文件拷贝到所有节点的/var/lib/ceph/mon/ceph-$hostname目录下。即

# cp /root/cluster/ceph.mon.keyring /var/lib/ceph/mon/ceph-1093/keyring
# scp /home/chenjuan/my-cluster/ceph.mon.keyring e1092:/var/lib/ceph/mon/ceph-e1092/keyring
# scp /home/chenjuan/my-cluster/ceph.mon.keyring e1094:/var/lib/ceph/mon/ceph-e1094/keyring

再次执行ceph-deploy –overwrite-conf create-initial,应该就成功了。