MySQL 性能调优——数据库结构优化

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/smartbetter/article/details/91881923

良好的数据库逻辑设计和物理设计是数据库获得高性能的基础。

数据库结构优化的目的:

  • 减少数据冗余(相同的数据在多个地方存在);
  • 尽量避免数据维护中出现更新,插入和删除异常(通过范式化设计解决);
    • 插入异常:如果表中的某个实体随着另一个实体而存在,也就是说如果缺少了某个实体,就无法表示另一个实体,这样设计出来的表就存在插入异常。
    • 更新异常:如果更改表中的某个实体的单独属性时,需要对多行进行更新,这样设计出来的表就存在更新异常。
    • 删除异常:如果删除表中的某一个实体则会导致其他实体的消失,这样设计出来的表就存在删除异常。
  • 节约数据存储空间;
  • 提高查询效率。

数据库设计的步骤:

  1. 需求分析:全面了解产品设计的存储需求、数据处理需求、数据的安全性和完整性有什么样需求;
  2. 逻辑设计:设计数据的逻辑存储结构,搞清楚数据实体之间的逻辑关系,解决数据冗余和数据维护异常;
  3. 物理设计:根据所使用的数据库(Oracle、SQLServer、MySQL、Redis)特点进行表结构设计;
  4. 维护优化:根据实际情况对索引、存储结构等进行优化。

1.逻辑设计

数据库逻辑设计一般按照的三范式进行设计:

范式 说明
第一范式 1、要求有主键,并且要求每一个字段原子性不可再分;
2、设计出来的表都是简单的二维表。
第二范式 1、满足第一范式的要求;
2、要求所有非主键字段完全依赖主键,不能产生部分依赖。
第三范式 1、满足第二范式的要求;
2、所有非主键字段和主键字段之间不能产生传递依赖(例如 学生编号表(学生id,学生姓名,班级编号,班级名称)中,班级名称字段存在冗余,因为班级名称字段没有直接依赖于主键,而是依赖于班级编号,班级编号依赖于学生编号,这就是传递依赖)。

看一个示例:

产品经理要求设计一个电商网站的数据库结构,需要具备功能:用户登录及用户管理、商品展示及商品管理、供应商管理、在线销售。

我们按三范式要求进行表设计:

用户登录及用户管理 表设计:
用户信息表(用户名, 密码, 手机号, 姓名, 创建时间, 更新时间), 用户名为主键

商品展示及商品管理 表设计:
商品信息表(商品id, 商品名称, 商品价格, 图书描述, 作者, 创建时间, 更新时间), 商品id为主键
分类信息表(分类id, 分类名称, 分类描述, 创建时间, 更新时间), 分类id为主键
商品分类关联表(商品id, 分类id, 创建时间, 更新时间), 商品id、分类id组成联合主键

在线销售 表设计:
订单表(订单id, 下单用户名, 下单日期, 支付金额, 物流单号), 订单id为主键
订单商品关联表(订单id, 订单商品分类id, 订单商品id, 下单数量), 订单id、订单商品分类id、订单商品id组成联合主键

假设我们要查询出每一个用户的订单总金额,SQL 应该怎么写呢?

select 下单用户名,sum(d.商品价格*b.下单数量)
from 订单表 a join 订单商品关联表 b on a.订单id=b.订单id
             join 商品分类关联表 c on c.商品id=b.商品id and c.分类id=b.分类id
             join 商品信息表 d on d.商品id=c.商品id
group by 下单用户名

一共关联了四张表,对于 MySQL,join 表越多,性能越差。

假设我们要查询出下单用户和订单详情,SQL 应该怎么写呢?

select a.订单id,e.下单用户名,e.手机号,d.商品名称,c.下单数量,d.商品价格
from 订单表 a join 订单商品关联表 b on a.订单id=b.订单id
             join 商品分类关联表 c on c.商品id=b.商品id and c.分类id=b.分类id
             join 商品信息表 d on d.商品id=c.商品id
             join 用户信息表 e on e.用户名=a.下单用户名

比上面那个还要多 join 一张表,完全符合范式化的设计有时并不能得到良好的 SQL 查询性能。

如果你的应用要求有非常好的查询性能,那就需要对上面的设计进行一些优化(反范式化设计)。

反范式化是针对范式化而言的,所谓反范式化就是为了性能和读取效率的考虑而适当的对数据库设计范式的要求进行违反,而允许存在少量的数据冗余,使用空间来换取时间。

在线销售 表设计:
订单表(订单id, 下单用户名, 下单日期, 支付金额, 物流单号), 订单id为主键
订单商品关联表(订单id, 订单商品分类id, 订单商品id, 下单数量), 订单id、订单商品分类id、订单商品id组成联合主键

在线销售 表设计 反范式化改造:
订单表(订单id, 下单用户名, 手机号, 下单日期, 支付金额, 物流单号, 订单金额), 订单id为主键
订单商品关联表(订单id, 订单商品分类id, 订单商品id, 下单数量, 下单商品单价), 订单id、订单商品分类id、订单商品id组成联合主键

此时再看下上面两个 SQL 改造后的写法:

查询出每一个用户的订单总金额:

select 下单用户名,sum(订单金额)
from 订单表
group by 下单用户名

查询出下单用户和订单详情:

select a.订单id,a.用户名,a.手机号,b.商品名称,b.商品单价,b.商品数量
from 订单表 a join 订单商品关联表 b on a.订单id=b.订单id

SQL 简化了好多,这里也可以看出,进行数据库设计的时候,也不能完全按照范式化的要求进行设计,同时也要考虑以后如何使用表。

下面看一下范式化设计和反范式化设计的优缺点:

- 优点 缺点
范式化设计 1、可以尽量的减少数据冗余;
2、范式化的更新操作比反范式化更快;
3、范式化的表通常比反范式化更小。
1、对于查询需要关联多个表,影响查询性能;
2、更难对查询进行索引优化。
反范式化设计 1、可以减少表的关联;
2、可以更好的进行索引优化。
1、存储数据容易和数据维护异常;
2、对数据的修改需要更多的物理成本。

在范式化设计的基础上进行反范式优化,这样才能设计出最符合性能和业务要求的数据库表结构。

2. 物理设计

物理设计涉及的内容:

  • 定义数据库、表及字段的命名规范;
  • 选择合适的存储引擎;
  • 为表中的字段选择合适的数据类型;
  • 建立数据库结构。

1、我们在定义数据库、表及字段的命名时,要遵守以下几个原则:

  • 可读性原则(比如使用下划线分割单词等);
  • 表意性原则;
  • 长名原则。

2、选择合适的存储引擎:参考 https://blog.csdn.net/smartbetter/article/details/91492332

3、数据类型的选择:

数据类型的选择对数据库性能有着很大的影响,过大的数据类型往往会浪费更多的内存和磁盘I/O。

数据类型的选择原则:

  • 当一个列可以选择多种数据类型时,应该优先考虑数字类型,其次是日期或二进制类型,最后是字符串类型。对于同样级别的数据类型,应该优先选择占用空间小的数据类型;

选择正确的整数类型:

列类型 存储空间 signed取值范围 unsigned(无符号)取值范围
tinyint 1字节 -128~127 0~255
smallint 2字节 -32768~32767 0~65535
mediumint 3字节 -8388608~8388607 0~16777215
int 4字节 -2147483648~2147483647 0~4294967295
bigint 8字节 -9223372036854775808~9223372036854775807 0~18446744073709551615

选择正确的整数类型:

列类型 存储空间 是否精确类型
float 4字节
double 8字节
decimal 每4字节存9个数字,小数点占1个字节

如何选择varchar和char类型:

列类型 存储空间 存储特点 适用场景
varchar 字符串字节长度+额外记录字符串长度的字节 1、用于存储变长字符串,只占用必要的存储空间;
2、列的最大长度小于255则只占用1个额外字节用于记录字符串长度;
3、列的最大长度大于255则要占用2个额外字节用于记录字符串长度;
1、字符串列的最大长度比平均长度大很多的场景;
2、字符串很少被更新的场景,更新太频繁容易产生存储碎片;
3、使用了多字节字符集存储字符串的场景;
char 字符串字节长度 1、char类型是定长的;
2、字符串存储在char类型的列中会删除末尾的空格;
3、char类型的最大宽度为255;
1、适合存储长度近似的值,比如MD5值;
2、适合存储短字符串;
3、适合存储经常被更新的字符串,避免产生存储碎片,获取更高的I/O性能;

varchar长度的选择问题:

  • 使用最小的符合需求的长度;
  • varchar(5) 和 varchar(200) 存储 ‘MySQL’ 字符串性能不同(MySQL 为了优化查询,在内存对字符串使用的是固定的宽度,宽度定义的太长会消耗更多的内存);

如何存储日期类型:

列类型 存储空间 存储特点 适用场景
datatime 8字节 1、以YYYY-MM-DD HH:MM:SS[.fraction]格式存储日期时间;
2、与时区无关;
3、时间范围1000-01-01 00:00:00到9999-12-31 23:59:59;
4、在行数据修改时可以自动更新值 (MySQL5.6及以上) ;
最常用的
timestamp 4字节 1、存储列由格林尼治时间1970年1月1日到当前时间的秒数;
2、以YYYY-MM-DD HH:MM:SS[.fraction]格式存储日期时间;
3、timestamp类型显示依赖于所指定的时区;
4、时间范围1970-01-01 00:00:01到2038-01-19 03:14:07;
5、在行数据修改时可以自动更新值;
data (MySQL5.6及以上) 3字节 1、以YYYY-MM-DD格式存储日期;
2、占用的字节数比使用字符串、datetime、int存储要少;
3、使用date还可以利用日期时间函数进行日期之间的计算;
4、时间范围1000-01-01到9999-12-31
time (MySQL5.6及以上) 3字节 1、以HH:MM:SS格式存储时间;

存储日期时间数据的注意事项:

  • 不要使用字符串类型来存储日期时间数据;
    • 日期时间类型通常比字符串占用的存储空间小;
    • 日期时间类型在进行查找过滤时可以利用日期来进行对比;
    • 日期时间类型有着丰富的处理函数,可以方便的对日期时间类型进行日期计算;
  • 使用int存储日期时间不如使用timestamp类型。

如何为 Innodb 选择主键?

  • 主键应该尽可能的小;
  • 主键应该是顺序增长的,可以增加数据的插入效率;
  • Innodb的主键和业务主键可以不同(Innodb的主键可以用自增id,业务主键增加唯一索引);
展开阅读全文

mysql数据库结构优化实例

08-20

今天看了一篇文章,介绍mysql数据库结构优化的,自己就写了例子,不如有没有什么问题的rnrn[b]users表[/b]rnrnuid(主键)rnusernamernpasswordrnrnrn[b]user_tags表(关系表)[/b]rnrnuid (主键)rntagidrn根据uid和tagid建立索引 uidtag(`uid`,`tagid`) //注意索引字段的顺序问题rnrnrnrn[b]tags表[/b]rnrntagid(主键)rntagname rntotal //此tag使用次数,用于删除标签时使用rn对tagname字段建立索引rn[color=#0000FF]rn以上表字段的类型暂不考虑,默认为最佳类型,且不同表里的同名字段类型及长度完全一样[/color]rnrn[b]一.用户添加标签[/b]rn 1.先从tags表里检查标签是否存在rn A.有则直接提取tagname,表示使用此标签的人数,再取tagidrn B.如果标签不存在,则添加标签,这时total字段值要为0不能是1,最后取tagidrn 2.根据上面提到的tagid和当前会员的uid,检查user_tags表里是否有此信息,如果已经存放此信息的,则不进行任何操作,如果无此记录则添加新记录,并更新tags表里的total字段+1,表示使用此标签的人数rnrn[b]二.显示用户自己的标签[/b]rn $result = SELECT * FROM tags WHERE tagid IN(SELECT tagid FROM user_tags WHERE uid=$uid); //$uid为当前用户的uidrnrn[b]三.删除标签(暂不考虑标签不存在情况)[/b]rn 1.根据传递过来的tagid值,来检查user_tags表里是否存在此信息,存在则删除rn 再检查total字段的值是否为1,为1则删除此标签信息(DELETE FROM tags WHERE tagid=$tagid),否则更新total字段减1.rn rn[b]四.更新标签名[/b]rn 先检测用户是否进行了标签名修改,如果没有的话,则直接跳过下面的操作rn 这里主要分两步,第一步是先更新原来的标签,第二步就是添加新标签rn 1.检查tags表的total字段是否为1,为1则直接删除,否则减1rn 2.检查tags表是否存在新的标签,如存在的话,更新total字段值加1,并取tagid.如不存放的,话则直接添加新标签,并将total字段设为1(这里取的新标签tagid保存在变量$newtagid里)rn 3.根据用户uid和原来的tagid值,来更新user_tags表里的tagid值,(UPDATE user_tags SET tagid=$newtagid WHERE uid=$uid AND tagid=$tagid)rn 论坛

MYSQL性能调优

06-09

1. 商业需求rn说明:rn不合理的需求导致资源的投入与产出成反比,开发人员有必要对产品提出的需求进行评估,对不影响大局或者可有可无的功能与产品进行协商,商讨是否可以放弃该功能或者适当修改功能。rnrn实例:rn产品提出论坛帖子总数实时更新,当前论坛情况是用户量庞大,同时帖子更新频繁。如果要做到实时,必须每次发帖后同时更新统计表数据(假设统计数据全部存储在统计表里面)。如果一秒内帖子产生很多,由于并发问题导致统计数据并不正确,由于如果有锁资源争用,造成性能下降。rnrn解决方案:rn跟产品讨论数据其实可以不那么实时,没有哪个无聊的人会发完帖子,盯着论坛的帖子数研究总数是否增加,即时不准确也不会影响用户的实际操作。把实时功能去掉后,可以做一个定时任务(SHELL或者用户触发皆可),系统每5分钟跑一次,更新掉论坛帖子数即可,这样最直接的结果是减少了大量由于更新产生的query语句。rn事实像类似DZ这样的开源项目都是不做实时的,甚至帖子的回复数都不是实时准确的,它是每发表一个回复,插入统计缓存,系统定时批量执行这些要更新的数据。rnrn思考:rn大家曾见过淘宝的分页,它并没有列出总共多少页,而是用户点击的附近几页,这样其实节省了select count(*) 的操作(innodb引擎做这个 跟myisam引擎做这个不在同一个概念,所以对于总数据庞大分页的东西其实可以修改分页方式,比如搜索结果)rnrnPS:rn无用功能堆积导致系统复杂rn开发中经常会遇到做了的一些功能不需要啦,导致出现无用的代码和无用的数据表,应该在确认之后,及时对这些功能下线,可以备份一次数据即可rnrn2. 系统架构rn说明:rn包括主从、读写分离、分布式散列存储、事务处理、引擎选用,存放数据、是否使用存储过程、视图、临时表、触发器等等rnrn3. 数据表(schema)设计rnSchema设计实质反映了一个项目的实际需求,是项目中的存储数据的一个体现,基本上我们看了数据表,大体知道这个项目在做什么。但在设计的同时需要考虑到基本业务逻辑所需要的QUERY操作以及由此产生的性能问题。rnrn1) 适度冗余,减少JOIN操作rn说明:rn这个其实是反范式的操作,schema设计时不能完全遵照范式,范式的目的是减少冗余。 但适当的冗余对性能是有不少的提升。rnrn实例:rn记录论坛版块最后的发帖人以及发帖时间,这个可以通过在帖子表里面进行查询(select * from posts where pid = ‘’ order by dateline desc) , 但如果在版块表设计两个字段last_post_member last_post_time, 则可以减少查询次数,一次取出数据。虽然更新(对于频繁的更新,我们同样使用定时任务操作)要导致数据表的写操作,但相对更新,查询显然更加频繁。rnrn2) 大表水平拆分rn说明:rn跟适当冗余相反,大表水平拆分则是根据表里面数据读取的频繁度将一张表分成多张。rnrn实例:rn大家见过许多开源项目,基本上用户数据存储在两张或者更多的表中,比如bbs_member (主表) bbs_memberfields(从表),主表存储读写比较多的字段(一般都是定长字段,比如username、password、groups),而从表存储的是读写相对少的字段(比如qq msn 以及存数容量比较大的数据,比如text类型的数据)rnrn3) 合适的数据类型rn说明:更小的数据类型让数据库以更小的空间存储相同的数据量,这样直接较少IO的消耗。 特别是对于要进行比较或者排序的字段应该选用更为迅速的字段类型,从而节省CPU的消耗。rnrn4) 创建合适的索引(单独做一节介绍)rnrn总结:rn所以我们在表的设计的时候,需要考虑到我们有多大的用户量(包括一个较为长远的考虑),考虑哪些query执行得更加频繁,从而给出相应的优化方案。rnrn4. 索引(index)设计rn1)索引类型:rnB-tree ()rnMyisam 主键就是聚集索引rn聚集索引(数据表的物理存储顺序和表的逻辑存储顺序一致)rn非聚集索引:。。。rnInnoDB表会包含一个聚集索引rn一般是按照下面的规则来设定聚集索引的:rn1,假如表包含PRIMARY KEY,InnoDB使用它作为聚集索引rn2,假如表没有定义PRIMARY KEY,InnoDB将第一个只包含NOT NULL属性列的UNIQUE index作为主键并且将它设置为聚集索引rn3,前两者都不满足的时候,mysql就增加一个隐藏的autocreamentrnrnHASH (仅仅memory和 ndb引擎支持)rnfull-text (仅仅myisam支持,并且只支持 char varchar text三种数据类型)rnr-treernrn2)索引用途:rn# 提高数据表检索效率rn# 降低数据排序成本(排序主要消耗cpu和内存, 对于分组操作同样是先排序后分组)rnrn2)如何判定 是否建立索引:rn# 只有在操作频繁的字段建立索引,绝不建不必要或者想当然的索引,这个在设计表的时候要能大致估计SQL要怎么写。rnrn# 唯一性太差的字段不建立索引,基本上当一条QUERY返回的数据占15%以上就不适合建立索引(通常的像像性别这样的字段绝对不建立索引)rnrn# 更新频繁的字段不建立索引。更新表数据的时候同时要更新索引数据,导致IO访问增大以及影响整个存储系统的消耗。(如果查询更新都较多的情况下,则要比较查询与更新的比例,当比例较大的时候,更新附带的成本是可以接受的)rnrn3) 单键索引和联合索引rn# 当where语句的过滤条件比较多的时候,考虑几个字段同时出现的频繁度,对频繁度出现较高的字段集建立联合索引。rnrn# 联合索引的缺点是多个字段同时存在,更新可能性更高,索引存储长度也越大。但就查询角度来讲这个因为它过滤掉更多的数据,所以效率更高。同时比在多个字段都建立单键索引效果好(因为mysql query optimizer需要将多个索引 index_merge 成本更高,有时候还会放弃其他索引)rnrn# 联合索引左前缀原则rn使用最左(leftmost)前缀。建立多列复合索引的时候,你实际上建立了MySQL可以使用的多个索引。复合索引可以作为多个索引使用,因为索引中 最左边的列集合都可以用于匹配数据行。这种列集合被称为”最左前缀”(它与索引某个列的前缀不同,那种索引把某个列的前面几个字符作为索引值)。rnrn假设你在表的state、city和zip数据列上建立了复合索引。索引中的数据行按照state/city/zip次序排列,因此它们也会自动地按照 state/city和state次序排列。这意味着,即使你在查询中只指定了state值,或者指定state和city值,MySQL也可以使用这个 索引。因此,这个索引可以被用于搜索如下所示的数据列组合:rnstate, city, ziprnstate, cityrnstaternrnMySQL不能利用这个索引来搜索没有包含在最左前缀的内容。例如,如果你按照city 或zip来搜索,就不会使用到这个索引。如果你搜索给定的 state和具体的ZIP代码(索引的1和3列),该索引也是不能用于这种组合值的,尽管MySQL可以利用索引来查找匹配的state从而缩小搜索的范 围。rnrn# 前缀索引(只使用某个字段前面部分内容作为索引键索引该字段)rn4) MYSQL索引限制rn# 索引键长度总和不超过1000字节rn# BLOB TEXT类型列只创建前缀索引rn# 不支持函数索引rn# 使用(!= <> )的时候, mysql不支持索引rn# 过滤字段使用了函数运算(如abs() ) 等,不支持索引rn# join语句中JOIN字段类型不一致,不支持索引rn# LIKE语句中以(‘%abc’)开始,不支持索引rn# 非等值查询时,不支持HASH索引rnrn5) JOIN语句优化rn# 永远用小结果集驱动大结果集(资源消耗存在较大区别外),比如A B联查,rnA过滤后 10rowsrnB过滤后 20rowsrnA作为驱动表,JOIN过滤则为10次rnB作为驱动表,JOIN过滤则为20次rn所以选择结果集小的作驱动表rnrn# join字段优化,保证每次查询节省资源rnrn6)ORDER BY、 GROUP BY、 DISTINCT优化rn原理都需要进行排序,除对字段索引外,需要去掉不必要的返回字段,节省内存(排序的原则)rnrn5. QUERY语句优化rn优化10原则:rn1)优化更需要的优化rn说明:执行对系统影响更大的QUERY,一般指的是高并发,执行更加频繁的SQL)rnrn2)定位优化对象性能瓶颈rn3)明确优化目标rn4)explainrn5)profulern6)小结果集驱动大结果集rn7)尽可能在索引中完成排序rn8)只取自己需要的字段rn9)仅仅使用最有效的过滤条件rn10)尽可能避免复杂的JOIN和子查询rnrn6. 实例分析rnDemo1:过度弱化query造成性能消耗rnTable1: users表(user_id, user_name, last_feed_time)rnTable2: feeds表(feed_id, user_id, feed_data, dateline)rn显然users表与feeds表是一对多的关系,现在要查询最近有动作的10个用户,同时在列表页要显示该用户最近24小时的动作。rnrn解决方法1:rn// 得到10个最近有动作的用户rn$sql = ‘SELECT user_id, user_namernFROM users ORDER BY last_feed_time DESC limit 10’;rnrn// PHP获得10条数据 $rsrnrn// 循环query查询对应ID的最近三个动作rnforeach($rs as $k=>$v) rn$sql = “SELECT feed_datarnFROM feeds WHERE user_id = ‘$v[‘user_id’]’ AND dateline <’’ ORDER BY dateline DESC “;rnrnrn解决方法2:rn// 同第一步rnrn// 查询10个用户最近24小时动作rn$sql = “SELECT user_id, feed_datarnFROM feeds WHERE user_id IN () AND dateline < ‘’ ORDER BY dateline DESC ”:rnrn// 数组组装rnrn总结:我们在开发中经常碰到一些二级栏目的列表页,但数据来自不同的数据表,通常的做法是循环里面执行query,殊不知这样增加了QUERY的次数,而每次QUERY都需要MYSQL进行解析,在这种情况下,宁愿QUERY复杂点或者在程序端复杂点,保证性能。而从另一个理论来讲,这种情况属于弱化了QUERY造成性能问题。rnrnDemo2:过度依赖query造成性能消耗rnTable1: users表(user_id, user_name)rnTable2: user_profile表(user_id, profile_data)rnTable3: users_group表(id, group_id, user_id, level)rn现在要查询群组ID为1的群组成员信息以及群主的详细信息。rn解决方法1:rn// 一次查询所有信息rnSELECT u.user_name, up.profile_datarnFROM users_group ug LEFT JOIN users u ON ug.user_id = u.user_id LEFT JOIN users_profile up ON u.user_id = up.user_idrnWHERE ug.group_id = ‘1’rnrn解决方法2:rn// 先查询群组下用户的基本信息rnSELECT u.user_id, u.user_name,rnFROM users_group ug LEFT JOIN users u ON ug.user_id = u.user_idrnWHERE ug.group_id = ‘1’rn// 查群主信息rnSELECT *rnFROM users_profile WHERE user_id = ‘’;rnrn总结:与上例相反,这个DEMO操作是增加一个query减少不必要的访问(只需要群主的详细信息,而profile存储的是数据类型比较大的数据,这样操作减少的是IO的访问)rnrnDemo3:小结果集驱动大结果集rnTable1: users表(user_id, user_name, sex)rnTable3: users_group表(id, group_id, user_id, level, join_time)rn现在要查询某个群组下面(id = 1)用户的名称和性别,按加入时间倒序取100-120条的记录rnrn解决方法1:rnSELECT u.user_id, u.username, u.sexrnFROM users_group ug LEFT JOIN users u ON ug.user_id = u.user_idrnWHERE ug.group_id = ‘1’ ORDER BY ug.join_time DESC LIMIT 100,120;rnrn解决方法2:rnSELECT u.user_id, u.username, u.sexrnFROM (rnSELECT user_idrnFROM user_grouprnWHERE user_group.group_id = 1rnORDER BY join_time DESCrnLIMIT 100,20) ug, userrnWHERE ug.user_id = user.user_id;rnrn总结:方法1参与join操作的是全部user_group中group_id = 1的数据、而方法2 参与join操作的数据仅仅是过滤过的20条数据。所以SQL优化永远记住小结果集驱动大结果集,节省的是CPU和IO的消耗。rn 论坛

没有更多推荐了,返回首页