自己在写一个web,希望对数据库做全文检索。但是google了解到,由于中文分词的缘故,mysql只支持英文的全文搜索,想支持中文的,需要各种插件or实现一些比较复杂的机制,而买的虚拟主机并不支持这些复杂的东西。仔细想了下,因为自己需求的功能也比较简单,主要是2个字段的搜索,且数据量不大,即便增加几个字段,需要多运行几个select也不会对速度有太大影响,所以通过一些work around实现了需求。Step 1:用locate进行简单的搜索Locate可以判断子串是否在子乱 有两个column,一个name,一个description.所以可以用LOCATE0去判断是否关键字在其中出现了。 其实就是 SELECT * FROM table WHERE LOCATE(key, 'name')0 OR LOCATE(key, 'description);这样,我们就简单实现了对某个key在两个域的搜索Step 2:搜索多个关键字 通常,搜索都是有多个关键字,所以我们需要对每个关键字,执行下Step1的查询。(当然,也可以合成一个,这里偷懒每次只查询1个关键字) 然后,我们再将每次查询出的数组都合并,这样就得到了一个最终的集合。php代码如下:
创新互联公司凭借在网站建设、网站推广领域领先的技术能力和多年的行业经验,为客户提供超值的营销型网站建设服务,我们始终认为:好的营销型网站就是好的业务员。我们已成功为企业单位、个人等客户提供了做网站、网站设计服务,以良好的商业信誉,完善的服务及深厚的技术力量处于同行领先地位。
function selectlocate($tarcols,$skey){
$where ="";
$connector = " ";
global $count;
foreach($tarcols as $tarcol ){
$where .= $connector;
$where .= "LOCATE('$skey', $tarcol) != 0 ";
if($connector == " "){
$connector = " OR ";
}
}
$sql = "SELECT * FROM pets_table WHERE $where";
$result = mysql_query($sql);
$ret = Array();
while($item = mysql_fetch_array($result, MYSQL_ASSOC)){
$count ++;
$ret[] = $item;
}
return $ret;
}
Step 3:匹配的权重 上面Step2的结果,其实是无序的。通常,如果我们搜索一个字段:1.如果这个字段和关键字完全相同,那么一般来讲,可能这个结果应该是相关度最高的2.如果他只是其其中出现了一次,相关度就最低。3.如果他出现的次数比在其他row中出现的次数高,那么他的相关度就比2中的结果高 所以,搜索的时候依据这个顺序考虑权重,a.如果完全相等,权重为1000 b.如果出现1次,权重为10,出现n次c.权重为n*10每次搜索出来的结果附加上权重----》然后合并相同项----》并把权重累加 最后按权重排序,即可得到一个有排序的搜索结果。 以下是两种1关键字对应1个字段(上面的代码是1关键字多个字段)查询的代码(不包含合并两个数组的代码,相关的代码在Step4中),只需遍历每个关键字和字段,就能完成搜索
$count = 0;
function selectequal($col,$skey){
$connector = " ";
global $count;
$sql = "SELECT * FROM pets_table WHERE LOWER($col)=LOWER('$skey')";
$result = mysql_query($sql);
$ret = Array();
while($item = mysql_fetch_array($result, MYSQL_ASSOC)){
$count ++;
$item["weight"] = 1000;
$ret[] = $item;
}
return $ret;
}
function selectlocate($col,$skey){
global $count;
$sql = "SELECT *,(LENGTH(description) - LENGTH(REPLACE(description, '$skey', '')))/LENGTH('$skey') *10 as weight FROM pets_table WHERE LOCATE(LOWER('$skey'),LOWER($col))0";
$result = mysql_query($sql);
$ret = Array();
while($item = mysql_fetch_array($result, MYSQL_ASSOC)){
$count ++;
$ret[] = $item;
}
return $ret;
}
Step 4: 字段的权重 在我的需求中,显然name这个字段比description更重要,所以在匹配时,对name字段的结果应该有所倾斜,所以,又可以增加一个对字段的权重系数。1.如果是在name域的匹配,设系数为10;2.如果是在description匹配,设系数为1; 将Step 3每次计算得出的权重,再乘上这个系数,就可以得到一个新的,更有效的权重值。 最后按权重排序,即可得到一个最有相关度排序的搜索结果 其他的细节: 如果一个关键字已经满足了equal条件,那么再使用locate条件的时候会依然返回一个结果,所以在使用locate条件的时候,过滤掉equal的情况
点击(此处)折叠或打开
?php
$count = 0;
function selectequal($col,$val,$skey){
$connector = " ";
global $count;
$sql = "SELECT * FROM pets_table WHERE LOWER($col)=LOWER('$skey')";
$result = mysql_query($sql);
$ret = Array();
while($item = mysql_fetch_array($result, MYSQL_ASSOC)){
$count ++;
$item["weight"] = 1000*$val;
$ret[] = $item;
}
return $ret;
}
function selectlocate($col,$val,$skey){
global $count;
$sql = "SELECT *,(LENGTH(description) - LENGTH(REPLACE(description, '$skey', '')))/LENGTH('$skey') *10*$val as weight FROM pets_table WHERE LOCATE(LOWER('$skey'),LOWER($col))0 AND LOWER($col)!=LOWER('$skey')";
$result = mysql_query($sql);
$ret = Array();
while($item = mysql_fetch_array($result, MYSQL_ASSOC)){
$count ++;
$ret[] = $item;
}
return $ret;
}
function cleanarr($arr){
global $count;
$tmp = Array();
$tmpall = Array();
foreach($arr as $item){
if(array_key_exists($item['uid'], $tmp)){
$tmp[$item['uid']]+=$item["weight"];
}
else{
$tmp[$item['uid']] = $item["weight"];
$tmpall[$item['uid']] = $item;
}
}
//sort by weight in descending order
arsort($tmp);
$ret = Array();
//rebuild the return arary
$count = 0;
foreach($tmp as $k=$v){
$count++;
$tmpall[$k]['weight']=$v;
$ret[]=$tmpall[$k];
}
return $ret;
}
require_once("consvr.php");
$colshash = array("name"=10,"description"=1);
$ret = Array();
$keywords=explode(" ", $keywords);
$cols = array_keys($colshash);
foreach($keywords as $keyword){
foreach($colshash as $col=$val){
$ret = array_merge($ret,selectequal($col,$val, $keyword));
$ret = array_merge($ret,selectlocate($col,$val, $keyword));
}
}
$ret = cleanarr($ret);
$ret = array('msg' = "Success", 'count'=$count,'children' = $ret, 'query'="COMPLEX:NOT READABLE");
echo json_encode($ret);
mysql_close();
?
数据分库表扩容-数据不均匀问题 原创
2021-12-22 22:18:13

这是王姑娘的微博 
码龄10年
关注
假如前期分三个库,一个库两个表,项目火爆,数据量激增,进行扩容
增加了新的数据库表位,会导致旧的库表比新的库表数据量多,且容易出现超载情况

解决方式思想:
不同的库表位分配的概率不一样,性能好的机器和数据量少的机器提高分配几率,类似的中间件应用场景有nginx
类似这种:
Nginx常见的负载均衡策略
节点轮询(默认)
weight 权重配置
简介:weight和访问比率成正比,数字越大,分配得到的流量越高
场景:服务器性能差异大的情况使用
upstream lbs {
server 192.168.159.133:8080 weight=5;
server 192.168.159.133:8081 weight=10;
}
在分库表中的加权解决方式,目前想到的几种方案:
库表位可以使用对象形式,配置权重,避免数据倾斜、数据集中(思考中...)
编写算法,根据不同的,配置权重,不同的库表位配置不同的权重(思考中...)
加权配置,list重复添加出现的高频的库表位(更改速度最快)
例如:dbPrefixList.add("0"); dbPrefixList.add("1"); dbPrefixList.add("a");
这三个库是第一批增加的,已经到了900多万单表量。现在准备进行扩容,那么实现方式如下:
扩容库位b,c,d

/**
* 获取随机的前缀
* @return
*/
public static String getRandomDBPrefix(){
int index = random.nextInt(dbPrefixList.size());
return dbPrefixList.get(index);
}
这样在获取随机库位的时候,0,1,a获取到的概率会低点,相对进入的数据就会少些。更多数据会进入到b ,c,d中进行平衡。
分区介绍:
一、什么是分区?
所谓分区,就是将一个表分成多个区块进行操作和保存,从而降低每次操作的数据,提高性能。而对于应用来说则是透明的,从逻辑上看只有一张表,但在物理上这个表可能是由多个物理分区组成的,每个分区都是独立的对象,可以进行独立处理。
二、分区作用
1.可以逻辑数据分割,分割数据能够有多个不同的物理文件路径。
2.可以存储更多的数据,突破系统单个文件最大限制。
3.提升性能,提高每个分区的读写速度,提高分区范围查询的速度。
4.可以通过删除相关分区来快速删除数据
5.通过跨多个磁盘来分散数据查询,从而提高磁盘I/O的性能。
6.涉及到例如SUM()、COUNT()这样聚合函数的查询,可以很容易的进行并行处理。
7.可以备份和恢复独立的分区,这对大数据量很有好处。
三、分区能支持的引擎
MySQL支持大部分引擎创建分区,入MyISAM、InnoDB等;不支持MERGE和CSV等来创建分区。同一个分区表中的所有分区必须是同一个存储引擎。值得注意的是,在MySQL8版本中,MyISAM表引擎不支持分区。
四、确认MySQL支持分区
从MySQL5.1开始引入分区功能,可以如下方式查看是否支持:
老版本用:SHOW VARIABLES LIKE '%partition%';
新版本用:show plugins;
五、分区类型
1. RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区。
例如,可以将一个表通过年份划分成两个分区,2001 -2010年、2011-2020。
2. LIST分区:类似于RANGE分区,LIST是列值匹配一个离散值集合中的某个值来进行选择。
比如 根据字段 把值为1、3、5的放到一起,2、4、6的另外放到一起 等等...
3. HASH分区:基于用户定义的表达式的返回值来进行选择分区,该表达式使用将要插入到表中的这些行的列值来进行计算,这个函数必须产生非负整数值。
通过HASH运算来进行分区,分布的比较均匀
4. KEY分区:类似于按HASH分区,由MySQL服务器提供其自身的哈希函数。
按照KEY进行分区类似于按照HASH分区
六、使用分区注意事项
1. 如果表中存在primary key 或者 unique key 时,分区的列必须是paimary key或者unique key的一个组成部分,也就是说,分区函数的列只能从pk或者uk这些key中取子集
2. 如果表中不存在任何的paimary key或者unique key,则可以指定任何一个列作为分区列
3. 5.5版本前的RANGE、LIST、HASH分区要求分区键必须是int;MySQL5.5及以上,支持非整形的RANGE和LIST分区,即:range columns 和 list columns (可以用字符串来进行分区)。
七、分区命名
1. 分区的名字基本上遵循其他MySQL 标识符应当遵循的原则,例如用于表和数据库名字的标识符。应当注意的是,分区的名字是不区分大小写的。
2. 无论使用何种类型的分区,分区总是在创建时就自动的顺序编号,且从0开始记录。
八、 创建分区
1. RANGE分区:
解读:以上为 uuid小于5时放到p0分区下,uuid大于5且小于10放到p1分区下,uuid大于10且小于15放到p2分区下,uuid大于15 一直到最大值的存在p3分区下
2. LIST分区:
解读:以上为uuid 等于1/2/3/5时放到p0分区,7/9/10放到p1分区,11/15放到p2分区。当时用insert into时 如果uuid的值不存在p0/p1/p2分区时,则会插入失败而报错。
3. HASH分区:
HASH分区主要用来确保数据在预先确定数目的分区中平均分布。在RANGE分区和LIST分区中必须明确指定一个指定的列值或列值集合以指定应该保存在哪个分区中。而在HASH分区中,MySQL会自动完成这些工作,要做的只是基于将要被哈希的列值指定一个表达式,以及指定被分区的表将要被分割成的分区数量,如:
解读:MySQL自动创建3个分区,在执行insert into时,根据插入的uuid通过算法来自动分配区间。
注意:
(1) 由于每次插入、更新、删除一行,这个表达式都要计算一次,这意味着非常复杂的表达式可能会引起性能问题,尤其是在执行同时影响大量行的运算(例如批量插入)的时候。
(2) 最有效率的哈希函数是只对单个表列进行计算,并且它的值随列值进行一致的增大或减小,因为这考虑了在分区范围上的“修剪”。也就是说,表达式值和它所基于的列的值变化越接近,就越能有效地使用该表达式来进行HASH分区。
3.1:线性HASH分区
线性HASH分区在“PARTITION BY”子句中添加“LINEAR”关键字。
线性HASH分区的有点在于增加、删除、合并和拆分分区将变得更加快捷,有利于处理含有及其大量数据的表。它的缺点在于各个分区间数据的分布不大可能均衡。
4. KEY分区
类似于HASH分区,HASH分区允许用户自定义的表达式,而KEY分区则不允许使用用户自定义的表达式;HASH分区只支持整数分区,KEY分区支持除了blob和text类型之外的其他数据类型分区。
与HASH分区不同,创建KEY分区表的时候,可以不指定分区键,默认会选择使用主键或唯一键作为分区键,没有主键或唯一键,就必须指定分区键。
解读:根据分区键来进行分区
5. 子分区
子分区是分区表中,每个分区的再次分割,适合保存非常大量的数据。
解读:主分区使用RANGE按照年来进行分区,有3个RANGE分区。这3个分区中又被进一步分成了2个子分区,实际上,整个表被分成了3 * 2 = 6个分区。每个子分区按照天进行HASH分区。小于2017的放在一起,2017-2020的放在一起,大于2020的放在一起。
注意:
(1) 在MySQL5.1中,对于已经通过RANGE或LIST分区了的表在进行子分区是可能的。子分区既可以使用HASH分区,也可以使用KEY分区。这也被称为复合分区。
(2) 每个分区必须有相同数量的子分区。
(3) 如果在一个分区表上的任何分区上使用SUBPARTITION来明确定义任何子分区,那么就必须定义所有的子分区。
(4) 每个SUBPARTITION子句必须包含(至少)子分区的一个名字。
(5) 在每个子分区内,子分区的名字必须是惟一的,目前在整个表中,也要保持唯一。例如:
子分区可以用于特别大的表,可以在多个磁盘间分配数据和索引。例如:
九、MySQL分区处理NULL值的方式
十、分区管理概述
可以对分区进行添加、删除、重新定义、合并或拆分等管理操作。
① RANGE和LIST分区的管理
1. 删除分区语句如:alter table tbl_test drop partition p0;
注意:
(1) 当删除了一个分区,也同时删除了该分区中所有的数据。
(2) 可以通过show create table tbl_test;来查看新的创建表的语句。
(3) 如果是LIST分区的话,删除的数据不能新增进来,因为这些行的列值包含在已经删除了的分区的值列表中。
2. 添加分区语句如:alter table tbl_test add partition(partition p3 values less than(50));
注意:
(1) 对于RANGE分区的表,只可以添加新的分区到分区列表的最高端。
(2) 对于LIST分区的表,不能添加已经包含在现有分区值列表中的任意值。
3. 如果希望能不丢失数据的条件下重新定义分区,可以使用如下语句:
REORGANIZE会对分区的数据进行重构。
ALTER TABLE tbl_name REORGANIZE PARTITION partition_list INTO(partition_definitions)
(1) 拆分分区如:
ALTER TABLE tbl_name REORGANIZE PARTITION partition_list INTO(partition s0 values less than(5),partition s1 values less than(10));
或者如:
ALTER TABLE tbl_name REORGANIZE PARTITION p0 INTO(partition s0 values in(1,2,3), partition s1 values in(4,5));
(2) 合并分区如:ALTER TABLE tbl_name REORGANIZE PARTITION s0,s1 INTO(partition p0 values in(1,2,3,4,5));
4. 删除所有分区,但保留数据,形式:ALTER TABLE tbl_name remove partitioning;
② HASH和KEY分区的管理
1. 减少分区数量语句如:ALTER TABLE tbl_name COALESCE PARTITION 2;
2. 添加分区数量语句如:ALTER TABLE tbl_name add PARTITION partitions 2;
③ 其他分区管理语句
1. 重建分区:类似于先删除保存在分区中的所有记录,然后重新插入它们,可用于整理分区碎片。如:ALTER table tbl_name REBUILD PARTITION p2,p3;
2. 优化分区:如果从分区中删除了大量的行,或者对一个带有可变长度的行(也就是说,有VARCHAR,BLOB或TEXT类型的列)做了许多修改,可以使用 ALTER TABLE tbl_name OPTIMIZE PARTITION来收回没有使用的空间,并整理分区数据文件的碎片。如:ALTER TABLE tbl_name OPTIMIZE PARTITION p2,p3;
3. 分析分区:读取并保存分区的键分布,如:ALTER TABLE tbl_name ANALYZE PARTITION p2,p3;
4. 检查分区:检查分区中的数据或索引是否已经被破坏,如:ALTER TABLE tbl_name CHECK PARTITION p2,p3;
5. 修补分区:修补被破坏的分区,如:ALTER TABLE tbl_name REPAIR PARTITION p2,p3;
十、查看分区信息
1. 查看分区信息:select * from information_schema.partitions where table_schema='arch1' and table_name = 'tbl_test' G;
2. 查看分区上的数据:select * from tbl_test partition(p0);
3. 查看MySQL会操作的分区:explain partitions select * from tbl_test where uuid = 2;
十一、 局限性
1. 最大分区数目不能超过1024,一般建议对单表的分区数不要超过50个。
2. 如果含有唯一索引或者主键,则分区列必须包含在所有的唯一索引或者主键在内。
3. 不支持外键。
4. 不支持全文索引,对分区表的分区键创建索引,那么这个索引也将被分区。
5. 按日期进行分区很合适,因为很多日期函数可以用。但是对字符串来说合适的分区函数不太多。
6. 只有RANGE和LIST分区能进行子分区,HASH和KEY分区不能进行子分区。
7. 临时表不能被分区。
8. 分区表对于单条记录的查询没有优势。
9. 要注意选择分区的成本,没插入一行数据都需要按照表达式筛选插入的分区。
10. 分区字段尽量不要可以为null
你是说类似搜索关键字相关度排序的问题吧,我的想法是做分词表,按相关度分配权重,然后按权重通过多条查询关键字条件拼出结果来显示。比如你举的例子,我先查询包含3的数据,再查询不包含3的数据按升序排列,一起存到临时表,再对这个临时表进行输出。
Asynchronous Replication Automatic failover
其原理是在一条异步复制通道上配置多个可用复制源,当某个复制源不可用时(宕机、复制链路中断),且 slave 的 IO 线程尝试重连无效,自动根据权重选择新的源继续同步。
准备一个 MGR 集群和单实例,模拟复制链路切换,当 primary 故障,slave 自动切换到其他节点。dbdeployer deploy replication --topology=group 8.0.22 --single-primarydbdeployer deploy single 8.0.22
2. 在从机上建立指向 MGR 主节点的复制通道,
change master to master_user='msandbox',master_password='msandbox', master_host='127.0.0.1',master_auto_position=1,source_connection_auto_failover=1,master_port=23223,master_retry_count=6,master_connect_retry=10 for channel 'mgr-single';
在 master_retry_count 和 master_connect_retry 的设置上要考虑尝试重连多久才切换复制源。
3. 在从机上配置 asynchronous connection auto failover
配置 asynchronous connection auto failover 的两个函数:
asynchronous_connection_failover_add_source(channel-name,host,port,network-namespace,weight)
asynchronous_connection_failover_delete_source(channel-name,host,port,network-namespace)
权重值大的被优先级选择,可以配合MGR的选举权重配置 asynchronous_connection_failover 的权重。当 MGR 节点切换,异步复制也能切换到新的主节点。
SELECT asynchronous_connection_failover_add_source('mgr-single','127.0.0.1',23223,null,100); SELECT asynchronous_connection_failover_add_source('mgr-single','127.0.0.1',23224,null,80); SELECT asynchronous_connection_failover_add_source('mgr-single','127.0.0.1',23225,null,50);start slave for channel 'mgr-single';
4. 检查异步复制通道是否启用 failover。
mysql SELECT CHANNEL_NAME, SOURCE_CONNECTION_AUTO_FAILOVER FROM performance_schema.replication_connection_configuration; +--------------+---------------------------------+| CHANNEL_NAME | SOURCE_CONNECTION_AUTO_FAILOVER |+--------------+---------------------------------+| mgr-single | 1 |+--------------+---------------------------------+1 row in set (0.01 sec
5. 把 MGR 的 primary 节点 kill 掉,这个从节点会在尝试几轮重连失败后自动切换到次权重的复制源,其日志中会输出切换信息。
注意:当主节点故障,一旦复制链路成功 failover 后,在新的复制链路没有故障时,如果原主节点恢复,是不会回切的。如果当前复制链路发生故障,会再次选择权重高的进行切换