function route(invokers){ var result = new java.util.ArrayList(invokers.size()); for(i =0; i < invokers.size(); i ++){ if("10.20.153.10".equals(invokers.get(i).getUrl().getHost())){ result.add(invokers.get(i)); } } return result; } (invokers));
Server 层包括连接器、查询缓存、解析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。
Mysql 查询流程
SQL 语句在 MySQL 中是如何执行的?
MySQL 整个查询执行过程,总的来说分为 6 个步骤:
连接器 - 客户端和 MySQL 服务器建立连接;连接器负责跟客户端建立连接、获取权限、维持和管理连接。
查询缓存 - MySQL 服务器首先检查查询缓存,如果命中缓存,则立刻返回结果。否则进入下一阶段。
分析器 - MySQL 服务器进行 SQL 解析:语法分析、词法分析。
优化器 - MySQL 服务器用优化器生成对应的执行计划。
执行器 - MySQL 服务器根据执行计划,调用存储引擎的 API 来执行查询。
返回结果 - MySQL 服务器将结果返回给客户端,同时缓存查询结果。
连接器
使用 MySQL 第一步自然是要连接数据库。连接器负责跟客户端建立连接、获取权限、维持和管理连接。
MySQL 客户端/服务端通信是半双工模式:即任一时刻,要么是服务端向客户端发送数据,要么是客户端向服务器发送数据。客户端用一个单独的数据包将查询请求发送给服务器,所以当查询语句很长的时候,需要设置 max_allowed_packet 参数。但是需要注意的是,如果查询实在是太大,服务端会拒绝接收更多数据并抛出异常。
MySQL 客户端连接命令形式为:mysql -h<主机> -P<端口> -u<用户名> -p<密码>。如果没有显式指定密码,会要求输入密码才能访问。
连接完成后,如果没有后续的动作,这个连接就处于空闲状态。可以执行 show processlist 命令查看当前有多少个客户端连接。客户端如果空闲太久,连接器就会自动将它断开。客户端连接维持时间是由参数 wait_timeout 控制的,默认值是 8 小时。如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: Lost connection to MySQL server during query。这时候如果你要继续,就需要重连,然后再执行请求了。
MySQL 使用基于成本的优化器,它尝试预测一个查询使用某种执行计划时的成本,并选择其中成本最小的一个。在 MySQL 可以通过查询当前会话的 last_query_cost 的值来得到其计算当前查询的成本。
1 2 3 4 5 6 7 8 9
mysql> select * from t_message limit 10; ...省略结果集
mysql> show status like 'last_query_cost'; +-----------------+-------------+ | Variable_name | Value | +-----------------+-------------+ | Last_query_cost | 6391.799000 | +-----------------+-------------+
结果集返回客户端是一个增量且逐步返回的过程。有可能 MySQL 在生成第一条结果时,就开始向客户端逐步返回结果集了。这样服务端就无须存储太多结果而消耗过多内存,也可以让客户端第一时间获得返回结果。需要注意的是,结果集中的每一行都会以一个满足 ① 中所描述的通信协议的数据包发送,再通过 TCP 协议进行传输,在传输过程中,可能对 MySQL 的数据包进行缓存然后批量发送。
Mysql 更新流程
MySQL 更新过程和 MySQL 查询过程类似,也会将流程走一遍。不一样的是:更新流程还涉及两个重要的日志模块,:redo log(重做日志)和 binlog(归档日志)。
索引是提高 MySQL 查询性能的一个重要途径,但过多的索引可能会导致过高的磁盘使用率以及过高的内存占用,从而影响应用程序的整体性能。应当尽量避免事后才想起添加索引,因为事后可能需要监控大量的 SQL 才能定位到问题所在,而且添加索引的时间肯定是远大于初始添加索引所需要的时间,可见索引的添加也是非常有技术含量的。
MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。
全文索引一般使用倒排索引实现,它记录着关键词到其所在文档的映射。
InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。
全文索引主要用来查找文本中的关键字,而不是直接与索引中的值相比较。
全文索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的 WHERE 语句的参数匹配。全文索引配合 match against 操作使用,而不是一般的 WHERE 语句加 LIKE。它可以在 CREATE TABLE,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 char、varchar,text 列上可以创建全文索引。值得一提的是,在数据量较大时候,先将数据放入一个没有全局索引的表中,然后再用 CREATE INDEX 创建全文索引,要比先为一张表建立全文索引然后再将数据写入的速度快很多。
1 2 3 4 5
CREATETABLE `table` ( `content` text CHARACTERNULL, ... FULLTEXT (content) )
SELECTCOUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity, COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity, COUNT(*) FROM payment;
【示例】如有索引 (a, b, c, d),查询条件 c > 3 and b = 2 and a = 1 and d < 4 与 a = 1 and c > 3 and b = 2 and d < 4 等顺序都是可以的,MySQL 会自动优化为 a = 1 and b = 2 and c > 3 and d < 4,依次命中 a、b、c、d。
然后,网络突然好了,哨兵因为之前已经选举出一个新主节点了,它就会把旧主节点降级为从节点(A),然后从节点(A)会向新主节点请求数据同步,因为第一次同步是全量同步的方式,此时的从节点(A)会清空掉自己本地的数据,然后再做全量同步。所以,之前客户端在过程 A 写入的数据就会丢失了,也就是集群产生脑裂数据丢失的问题。
defdel_large_set(): r = redis.StrictRedis(host='redis-host1', port=6379) large_set_key = 'xxx'# 要删除的大set的键名 cursor = '0' while cursor != 0: # 使用 sscan 命令,每次扫描集合中 100 个元素 cursor, data = r.sscan(large_set_key, cursor=cursor, count=100) for item in data: # 再用 srem 命令每次删除一个键 r.srem(large_size_key, item)
对于删除大 ZSet,使用 zremrangebyrank 命令,每次删除 top 100 个元素。
Python 代码:
1 2 3 4 5 6
defdel_large_sortedset(): r = redis.StrictRedis(host='large_sortedset_key', port=6379) large_sortedset_key='xxx' while r.zcard(large_sortedset_key)>0: # 使用 zremrangebyrank 命令,每次删除 top 100个元素 r.zremrangebyrank(large_sortedset_key,0,99)
Buffer Pool 用于加速数据的访问和修改,通过将热点数据缓存在内存的方法,最大限度地减少磁盘 IO,加速热点数据的读和写。
Buffer Pool 中数据以页为存储单位,其实现数据结构是以页为单位的单链表。
由于内存的空间限制,Buffer Pool 仅能容纳最热点的数据。Buffer Pool 使用最近最少使用算法(Least Recent Used,LRU)算法淘汰非热点数据页。
依据时间局部性原理与空间局部性原理,Buffer Pool 在存储当前活动数据页的时候,会以预读 Read-ahead 的方式缓存目标数据页临近的数据页。
预读机制带来预读失败的问题,InnoDB 采用分代机制解决预读失败问题:将 Buffer Pool 分为 New SubList 和 Old SubList 两部分,将最新读取的数据页置于 Old SubList 头部,Old SubList 中的数据再次被访问到才会置于 New SubList 头部;预读失败的冷数据将更快地从 Old SubList 中淘汰,而不会影响到 New SubList 中原有的热数据。
预读失败问题可以引申到缓冲池污染问题,InnoDB 采用时间窗口(Time Window)机制解决缓冲池污染问题:对于 Old SubList 中的数据页,必须在 Old SubList 中停留到达指定时间之后再次被访问到,才能转移到 New SubList 中,默认窗口大小是 1s。
对于 Buffer Pool 中数据的查询,InnoDB 直接读取返回;对于 Buffer Pool 中数据的修改,InnoDB 直接在 Buffer Pool 中修改,并将修改写入 redo Log 中,当数据页被 LRU 算法淘汰时写入磁盘,若持久化前系统崩溃,则在重启后使用 redo Log 进行恢复。
当执行的线程数量超过 CPU 数量时,线程之间会根据时间片轮询争夺 CPU 资源。如果一个线程的时间片用完了,或者是其它原因导致这个线程的 CPU 资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令,从而在线程切换后能恢复到正确的执行位置。各条线程间的计数器互不影响,独立存储,我们称这类内存区域为 “线程私有” 的内存。