一切福田,不離方寸,從心而覓,感無不通。

Category Archives: MySQL

大型网站应用中 MySQL 的架构演变史

没有什么东西是一成不变的,包含我们的理想和生活!MySQL作为一个免费的开源的关系型数据库,深受大家喜爱,从最初的无人问津到当下的去IOE,都体现出了MySQL举足轻重的作用。今天我们就从淘宝的发展来阐述MySQL在大型网站下的架构演变史! MySQL的可扩展性 架构的可扩展性往往和并发是息息相关,没有并发的增长,也就没有必要做高可扩展性的架构,这里对可扩展性进行简单介绍一下,常用的扩展手段有以下两种 Scale-up : 纵向扩展,通过替换为更好的机器和资源来实现伸缩,提升服务能力 Scale-out : 横向扩展,  通过加节点(机器)来实现伸缩,提升服务能力 对于互联网的高并发应用来说,无疑Scale out才是出路,通过纵向的买更高端的机器一直是我们所避讳的问题,也不是长久之计,在scale out的理论下,可扩展性的理想状态是什么? 可扩展性的理想状态 一个服务,当面临更高的并发的时候,能够通过简单增加机器来提升服务支撑的并发度,且增加机器过程中对线上服务无影响(no down time),这就是可扩展性的理想状态! MySQL架构的演变 MySQL简单网站架构(V1.0) 一个简单的小型网站或者应用背后的架构可以非常简单,  数据存储只需要一个mysql instance就能满足数据读取和写入需求(这里忽略掉了数据备份的实例),处于这个时间段的网站,一般会把所有的信息存到一个database instance里面。 在这样的架构下,我们来看看数据存储的瓶颈是什么? 1.数据量的总大小  一个机器放不下时 2.数据的索引(B+ Tree)一个机器的内存放不下时 3.访问量(读写混合)一个实例不能承受 只有当以上3件事情任何一件或多件满足时,我们才需要考虑往下一级演变。 从此我们可以看出,事实上对于很多小公司小应用,这种架构已经足够满足他们的需求了,初期数据量的准确评估是杜绝过度设计很重要的一环,毕竟没有人愿意为不可能发生的事情而浪费自己的经历。 这里简单举个我的例子,对于用户信息这类表 (3个索引),16G内存能放下大概2000W行数据的索引,简单的读和写混合访问量3000/s左右没有问题,你的应用场景是否 MySQL的垂直架构(V2.0) 一般当V1.0 遇到瓶颈时,首先最简便的拆分方法就是垂直拆分,何谓垂直?就是从业务角度来看,将关联性不强的数据拆分到不同的instance上,从而达到消除瓶颈的目标。以图中的为例,将用户信息数据,和业务数据拆分到不同的三个实例上。对于重复读类型比较多的场景,我们还可以加一层cache,来减少对DB的压力。 在这样的架构下,我们来看看数据存储的瓶颈是什么? 单实例单业务,依然存在V1.0所述瓶颈,遇到瓶颈时可以考虑往本文更高V版本升级, 若是读请求导致达到性能瓶颈可以考虑往V3.0升级, 其他瓶颈考虑往V4.0升级 MySQL的主从架构(V3.0) 此类架构主要解决V2.0架构下的读问题,通过给Instance挂数据实时备份的思路来迁移读取的压力,在Mysql的场景下就是通过主从结构,主库抗写压力,通过从库来分担读压力,对于写少读多的应用,V3.0主从架构完全能够胜任 在这样的架构下,我们来看看数据存储的瓶颈是什么? 写入量主库不能承受 MySQL的路由多集群架构(V4.0) 对于V2.0 V3.0方案遇到瓶颈时,都可以通过水平拆分来解决,水平拆分和垂直拆分有较大区别,垂直拆分拆完的结果,在一个实例上是拥有全量数据的,而水平拆分之后,任何实例都只有全量的1/n的数据,以下图Userinfo的拆分为例,将userinfo拆分为3个cluster,每个cluster持有总量的1/3数据,3个cluster数据的总和等于一份完整数据(注:这里不再叫单个实例 而是叫一个cluster 代表包含主从的一个小mysql集群) 数据如何路由? 1.Range拆分 sharding key按连续区间段路由,一般用在有严格自增ID需求的场景上,如Userid, Userid Range的小例子:以userid 3000W 为Range进行拆分 1号cluster userid 1-3000W 2号cluster userid 3001W-6000W 2.List拆分 List拆分与Range拆分思路一样,都是通过给不同的sharding key来路由到不同的cluster,但是具体方法有些不同,List主要用来做sharding key不是连续区间的序列落到一个cluster的情况,如以下场景: 假定有20个音像店,分布在4个有经销权的地区,如下表所示: 业务希望能够把一个地区的所有数据组织到一起来搜索,这种场景List拆分可以轻松搞定 3.Hash拆分 通过对sharding key 进行哈希的方式来进行拆分,常用的哈希方法有除余,字符串哈希等等,除余如按userid%n 的值来决定数据读写哪个cluster,其他哈希类算法这里就不细展开讲了。 数据拆分后引入的问题: 数据水平拆分引入的问题主要是只能通过sharding key来读写操作,例如以userid为sharding key的切分例子,读userid的详细信息时,一定需要先知道userid,这样才能推算出再哪个cluster进而进行查询,假设我需要按username进行检索用户信息,需要引入额外的反向索引机制(类似HBASE二级索引),如在redis上存储username->userid的映射,以username查询的例子变成了先通过查询username->userid,再通过userid查询相应的信息。 实际上这个做法很简单,但是我们不要忽略了一个额外的隐患,那就是数据不一致的隐患。存储在redis里的username->userid和存储在mysql里的userid->username必须需要是一致的,这个保证起来很多时候是一件比较困难的事情,举个例子来说,对于修改用户名这个场景,你需要同时修改redis和mysql,这两个东西是很难做到事务保证的,如mysql操作成功 但是redis却操作失败了(分布式事务引入成本较高),对于互联网应用来说,可用性是最重要的,一致性是其次,所以能够容忍小量的不一致出现. 毕竟从占比来说,这类的不一致的比例可以微乎其微到忽略不计(一般写更新也会采用mq来保证直到成功为止才停止重试操作) 在这样的架构下,我们来看看数据存储的瓶颈是什么? 在这个拆分理念上搭建起来的架构,理论上不存在瓶颈(sharding key能确保各cluster流量相对均衡的前提下),不过确有一件恶心的事情,那就是cluster扩容的时候重做数据的成本,如我原来有3个cluster,但是现在我的数据增长比较快,我需要6个cluster,那么我们需要将每个cluster 一拆为二,一般的做法是 1.摘下一个slave,停同步, 2.对写记录增量log(实现上可以业务方对写操作 多一次写持久化mq  或者mysql主创建trigger记录写 等等方式) […]

龙生   22 Jul 2016
View Details

Mysql支持的数据类型(总结)

一.数值类型 Mysql支持所有标准SQL中的数值类型,其中包括严格数据类型(INTEGER,SMALLINT,DECIMAL,NUMBERIC),以及近似数值数据类型(FLOAT,REAL,DOUBLE PRESISION),并在此基础上进行扩展。 扩展后增加了TINYINT,MEDIUMINT,BIGINT这3种长度不同的整形,并增加了BIT类型,用来存放位数据。 整数类型        字节       范围(有符号)      范围(无符号)          用途  TINYINT        1字节        (-128,127)          (0,255)            小整数值 SMALLINT       2字节     (-32 768,32 767)       (0,65 535)         大整数值 MEDIUMINT      3字节    (-8 388 608,8 388 607) (0,16 777 215)      大整数值 INT或INTEGER   4字节   (-2 147 483 648,2 147 483 647) […]

龙生   17 Jul 2016
View Details

VS2015 +EF6 连接MYSQL数据库生成实体

使用EF设计器 此时此刻,发现二逼了,咋没有MySQL Database呢? 好吧,下面重点: 需要下载安装: 1:mysql-for-visualstudio-1.2.6.msi http://dev.mysql.com/downloads/file/?id=460645 2:mysql-connector-net-6.9.8.msi http://dev.mysql.com/downloads/connector/net/ 3:使用Nuget安装EF   我这边是已经安装完了(版本选择6.1以上的),所以显示“更新”和“卸载”,如果你安装了,继续看下面的   下图是随便找了一个没安装的,就会有“安装”按钮,   4:使用Nuget安装Mysql.Data.Entity 与安装EF相同 点击安装后,一会会出现如下图:     执行完上述步骤,看web.config文件,会自动增加如下代码 OK,现在我们所有的步骤就执行完了。最好重启下VS(不知道是不是必须,反正我重启了),之后重新编译。 再之后,就可以按开始的步骤生成MYSQL对应的实体了。 注:如果刚开始那两个MSI文件安装有问题,则生成实体的时候,到了这一步之后(如下图),会出现闪退问题,无法生成实体   (本人就遇到过这个问题) 到了这一步,已经没有任何悬念了。   来自为知笔记(Wiz) from:http://www.cnblogs.com/RushPasser/p/5438334.html

龙生   14 Jul 2016
View Details

mysql修改字段默认值

龙生   11 Nov 2015
View Details

mysql修改字段类型

龙生   11 Nov 2015
View Details

mysql添加字段及默认值

龙生   11 Nov 2015
View Details

在Mac如何启动MySQL

安装好MySQL服务后(安装步骤可以参考系列经验1)。打开“系统偏好设置”,单击下端的“MySQL”图标。 在“MySQL”对话框中,单击“启动MySQL服务”按钮。 在弹出的窗口中,输入管理员密码,然后单击“好”按钮。 在“MySQL”对话框中,MySQL服务的状态显示为:如下状态表示MySQL服务已经启动。 使用终端登录MySQL:  在Finder的侧边栏中单击“应用程序”,然后在“实用工具”中,双击启动“终端”命令。  在终端中输入添加MySQL路径的命令: PATH="$PATH":/usr/local/mysql/bin  在终端登录到MySQL的命令如下:mysql -u root -p 然后输入密码,如果没有设置密码,直接按enter键。  如果显示的内容如下,即是已经成功登录到MySQL服务。  

龙生   13 Sep 2015
View Details

PowerDesigner更改数据库类型

工具:powerdesigner

数据库:sql server2000 --> mysql 5.0

1、更改数据库

14207159_1

将DBMS里的数据库(原为sqlserver2000)改为mysql5.0

2、生成mysql脚本

Database --> Generate Database

在directory里选择生成的sql脚本存放的路径;

在file name里修改sql脚本的名字;

one file only 勾选后,只生成一个sql脚本;不勾选,按照表的个数生成多个脚本;

由于建模时是按sqlserver标准设计的,转成mysql会有错误,取消掉check model前面的勾可避免生成sql脚本时发生如下的错误:

Generation aborted due to errors detected during the verification of the model.

其他错误见结尾

为了避免错误,在此将create primary key改为inside

format选项卡里主要修改文本格式,如果有中文,改成UTF-8,避免生成的脚本有乱码;

在这个选项卡里选择workspace,点击旁边的include sub_objects,在下面的列表里会显示该工程下的所有表。点击select all,列表中的所有表前面会出现勾(也可以只选择几个表)。

点击确定,完成脚本生成。
———————————————————--

常见问题

自增问题: 解决方法如下:

在你所要设为自增型的键上(比如你的 id )双击,弹出一个 Column Properties 对话框,右下角有一个 Identify 的选择框,选中它 OK ,就可以了。

再去查看 Preview ,就能看到用大写标识出来的 AUTO_INCREMENT 。

右键表属性在physical options中可设置表类型MYISAM

设置字段默认值 :双击表,出现 column 列表,双击要设置的列的左边的灰色框,应该会弹出新的窗口,然后在新窗口上选择 standard   checks   ,里面有 default 的默认值

mysql in 子查询 效率慢 优化(转)

现在的CMS系统、博客系统、BBS等都喜欢使用标签tag作交叉链接,因此我也尝鲜用了下。但用了后发现我想查询某个tag的文章列表时速度很慢,达到5秒之久!百思不解(后来终于解决),我的表结构是下面这样的,文章只有690篇。

文章表article(id,title,content)
标签表tag(tid,tag_name)
标签文章中间表article_tag(id,tag_id,article_id)
其中有个标签的tid是135,我帮查询标签tid是135的文章列表
用以下语句时发现速度好慢,我文章才690篇
select id,title from article where id in(
select article_id from article_tag where tag_id=135
)
其中这条速度很快:select article_id from article_tag where tag_id=135
查询结果是五篇文章,id为428,429,430,431,432
我用写死的方式用下面sql来查文章也很快
select id,title from article where id in(
428,429,430,431,432
)
我在SqlServer中好像不会这样慢,不知MySQL怎样写好点,也想不出慢在哪里。

后来我找到了解决方法:

select id,title from article where id in(
select article_id from (select article_id from article_tag where tag_id=135) as tbt
)

 

 

其它解决方法:(举例)

mysql> select * from abc_number_prop where number_id in (select number_id from abc_number_phone where phone = '82306839');

为了节省篇幅,省略了输出内容,下同。

67 rows in set (12.00 sec)

只有67行数据返回,却花了12秒,而系统中可能同时会有很多这样的查询,系统肯定扛不住。用desc看一下(注:explain也可)

mysql> desc select * from abc_number_prop where number_id in (select number_id from abc_number_phone where phone = '82306839');
+—-+——————--+——————+——--+—————--+——-+———+————+———+————————--+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+——————--+——————+——--+—————--+——-+———+————+———+————————--+
| 1 | PRIMARY | abc_number_prop | ALL | NULL | NULL | NULL | NULL | 2679838 | Using where |
| 2 | DEPENDENT SUBQUERY | abc_number_phone | eq_ref | phone,number_id | phone | 70 | const,func | 1 | Using where; Using index |
+—-+——————--+——————+——--+—————--+——-+———+————+———+————————--+
2 rows in set (0.00 sec)

从上面的信息可以看出,在执行此查询时会扫描两百多万行,难道是没有创建索引吗,看一下

mysql>show index from abc_number_phone;
+——————+————+————-+————--+—————--+———--+————-+———-+——--+——+————+———+—————+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+——————+————+————-+————--+—————--+———--+————-+———-+——--+——+————+———+—————+
| abc_number_phone | 0 | PRIMARY | 1 | number_phone_id | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 0 | phone | 1 | phone | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 0 | phone | 2 | number_id | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 1 | number_id | 1 | number_id | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 1 | created_by | 1 | created_by | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 1 | modified_by | 1 | modified_by | A | 36879 | NULL | NULL | YES | BTREE | | |
+——————+————+————-+————--+—————--+———--+————-+———-+——--+——+————+———+—————+
6 rows in set (0.06 sec)

mysql>show index from abc_number_prop;
+—————--+————+————-+————--+—————-+———--+————-+———-+——--+——+————+———+—————+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+—————--+————+————-+————--+—————-+———--+————-+———-+——--+——+————+———+—————+
| abc_number_prop | 0 | PRIMARY | 1 | number_prop_id | A | 311268 | NULL | NULL | | BTREE | | |
| abc_number_prop | 1 | number_id | 1 | number_id | A | 311268 | NULL | NULL | | BTREE | | |
| abc_number_prop | 1 | created_by | 1 | created_by | A | 311268 | NULL | NULL | | BTREE | | |
| abc_number_prop | 1 | modified_by | 1 | modified_by | A | 311268 | NULL | NULL | YES | BTREE | | |
+—————--+————+————-+————--+—————-+———--+————-+———-+——--+——+————+———+—————+
4 rows in set (0.15 sec)

从上面的输出可以看出,这两张表在number_id字段上创建了索引的。

看看子查询本身有没有问题。

mysql> desc select number_id from abc_number_phone where phone = '82306839';
+—-+————-+——————+——+—————+——-+———+——-+——+————————--+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+————-+——————+——+—————+——-+———+——-+——+————————--+
| 1 | SIMPLE | abc_number_phone | ref | phone | phone | 66 | const | 6 | Using where; Using index |
+—-+————-+——————+——+—————+——-+———+——-+——+————————--+
1 row in set (0.00 sec)

没有问题,只需要扫描几行数据,索引起作用了。查询出来看看

mysql> select number_id from abc_number_phone where phone = '82306839';
+———--+
| number_id |
+———--+
| 8585 |
| 10720 |
| 148644 |
| 151307 |
| 170691 |
| 221897 |
+———--+
6 rows in set (0.00 sec)

直接把子查询得到的数据放到上面的查询中

mysql> select * from abc_number_prop where number_id in (8585, 10720, 148644, 151307, 170691, 221897);

67 rows in set (0.03 sec)

速度也快,看来MySQL在处理子查询的时候是不够好。我在MySQL 5.1.42 和 MySQL 5.5.19 都进行了尝试,都有这个问题。

搜索了一下网络,发现很多人都遇到过这个问题:

参考资料1:使用连接(JOIN)来代替子查询(Sub-Queries) mysql优化系列记录
http://blog.csdn.net/hongsejiaozhu/article/details/1876181
参考资料2:网站开发日记(14)-MYSQL子查询和嵌套查询优化
http://dodomail.iteye.com/blog/250199

根据网上这些资料的建议,改用join来试试。

修改前:select * from abc_number_prop where number_id in (select number_id from abc_number_phone where phone = '82306839');

修改后:select a.* from abc_number_prop a inner join abc_number_phone b on a.number_id = b.number_id where phone = '82306839';

mysql> select a.* from abc_number_prop a inner join abc_number_phone b on a.number_id = b.number_id where phone = '82306839';

67 rows in set (0.00 sec)

效果不错,查询所用时间几乎为0。看一下MySQL是怎么执行这个查询的

mysql>desc select a.* from abc_number_prop a inner join abc_number_phone b on a.number_id = b.number_id where phone = '82306839';
+—-+————-+——-+——+—————--+———--+———+—————--+——+————————--+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+————-+——-+——+—————--+———--+———+—————--+——+————————--+
| 1 | SIMPLE | b | ref | phone,number_id | phone | 66 | const | 6 | Using where; Using index |
| 1 | SIMPLE | a | ref | number_id | number_id | 4 | eap.b.number_id | 3 | |
+—-+————-+——-+——+—————--+———--+———+—————--+——+————————--+
2 rows in set (0.00 sec)

小结:当子查询速度慢时,可用JOIN来改写一下该查询来进行优化。

网上也有文章说,使用JOIN语句的查询不一定总比使用子查询的语句快。

参考资料3:改变了对Mysql子查询的看法
http://hi.baidu.com/yzx110/blog/item/e694f536f92075360b55a92b.html

 

 

mysql手册也提到过,具体的原文在mysql文档的这个章节:

I.3. Restrictions on Subqueries

13.2.8. Subquery Syntax

摘抄:

1)关于使用IN的子查询:

Subquery optimization for IN is not as effective as for the = operator or for IN(value_list) constructs.

A typical case for poor IN subquery performance is when the subquery returns a small number of rows but the outer query returns a large number of rows to be compared to the subquery result.

The problem is that, for a statement that uses an IN subquery, the optimizer rewrites it as a correlated subquery. Consider the following statement that uses an uncorrelated subquery:

The optimizer rewrites the statement to a correlated subquery:

If the inner and outer queries return M and N rows, respectively, the execution time becomes on the order of O(M×N), rather than O(M+N) as it would be for an uncorrelated subquery.

An implication is that an IN subquery can be much slower than a query written using an IN(value_list) construct that lists the same values that the subquery would return.

2)关于把子查询转换成join的:

The optimizer is more mature for joins than for subqueries, so in many cases a statement that uses a subquery can be executed more efficiently if you rewrite it as a join.

An exception occurs for the case where an IN subquery can be rewritten as a SELECT DISTINCT join. Example:

That statement can be rewritten as follows:

But in this case, the join requires an extra DISTINCT operation and is not more efficient than the subquery

from:http://www.cnblogs.com/xh831213/archive/2012/05/09/2491272.html

1 18 19 20 24