思维碎片

这是一篇草稿了很多年的文章,算是一些记录吧


回复LX1 年前 {LX1 年前: 世界就是由计算机二进制语言组织起来的。不错。}二进制,包括其它的任何理论,都是一种抽象,而抽象必然伴随着信息的丢失。任何理论,都是对现象的描述,不是现象本身。我好像扯太远了

1 年前
已经不记得是初中还是小学时,生出的一个奇怪念头:每个人是否都是在做梦,从一生下来就在做梦,直到最后的醒来,就是死亡。


161221  写给iOS开发人员的一条原则,这些内容好像是不言而喻,应该是开发人员的必备意识

使用任何一个变量(包括对象、属性)时,要事先保证其有效性,尤其是通过调用各类接口返回的值,至少包括以下几种类型:

  • a. 网络接口(比如通过http返回并解析的json)
  • b. 调用操作系统api返回的值(含对象)
  • c. 通过类型转换返回的值,比如json
  • d. 一段时间未使用的系统资源,如socket, 打开的文件handle, 数据库连接对象

非法的使用无效变量后,进程(或线程)立即终止;如果是进程或UI线程异常终止,程序就必然崩溃了闪退。
(在windows程序、java程序、python程序,甚至javascript、php这类解释型程序里,都是如此,我没写过iOS程序,想必也是一样)


161205  BOM开会,

如何利用别人(外界)对我的认识、改善自己的工作.
我作为别人的“外界”,如何反馈出对他人的认识,以便让别人参考,客观评判、不加主观臆断

谁拿结果,谁主导做


程序开发中的命名空间。数据库(包括表前缀、表名、字段名)、变量等等,都有关。引入前缀的目的是避免冲突。而不是“看起来整齐”。事实上,这种所谓的整齐,同时意味着辨识度低,很容易看错。试想,在一大堆长得很像的名字里,一眼区分谁是谁,真有难度。举例:一个站点自用的一数据库,表前缀为了所谓的“整齐好看”,对每个表加个前缀。一个典型是当年动易CMS的数据库(在虚拟主机环境下,很多主机商限制只能有一个数据库,所以动易数据表可能要与别的应用共存在数据库里,所以这个表前缀某些时候还是有用的)。更无语的是,有人更进一步,前缀就是本数据库名,这实在是自找麻烦,掩耳盗铃。同样,对于变量命名,也是类似。相反,在一个庞大的程序代码里,存在相互包含(include),这种情况下,被包含的文件,里面的变量命名,非常有必要使用一定的前缀,以避免冲突。否则将成为调试的噩梦。


fengyqf
2月21日 23:12 来自 微博 weibo.com
哲学,是起源于古希腊的一门学问。哲理,只是名字上带个“哲”字的文学片段。


运营上成功的产品,未必在技术上成功,所以市面上一些成功的产品,未必是好的技术参考。如wordpress, discuz等


横向沟通协作,主动了解,主动提供 from 161029公司培训


痛点、亮点、弯道超车点。 网易考拉CEO总结的三个重要点 from 电视节目《总裁读书会》 161106

MySQL/MariaDB已被锁表运行中热复制为副本/innodb表错误Table xx doesn't exist in engine处理

从数据表热复制说起。

在执行特别慢的语句时,mysql经常会锁表。这时如果想并行执行另一个语句,但表被锁而该语句只能排队。这种情况下,希望能将被锁的表复制个副本,就可以“假”并行执行;然而杀死前语句又太可惜。所以,运行中热复制的技巧(奇技淫巧)还是有用的。

对于MyISAM表,直接复制三个文件到另一数据库中,即时生效,很方便。为了稳妥起见,最好先检查表看是否有异常;有时会因为写入而提醒表损坏,repair table 修复一下就好。

但对于InnoDB表,就要复杂一些。首先,step-0) 确保已通过MySQL服务的匹配文件,启用单文件选项 innodb_file_per_table=1 ,这样每个表会生成两个文件。但你并不能直接复制它们了,因为你将在新表上得到一条错误 Table xx doesn't exist in engine,所以这样不行。而是,step-1) 你应该在目标数据库里创建一张完全相同的表,然后 step-2) 修改新表丢弃其表空间,语句如 ALTER TABLE `xx` discard tablespace; 之后,step-3) 把源表的 .ibd文件复制过来覆盖新表的同名文件。step-4) 重新导入新表空间 ALTER TABLE `xx` import tablespace;  step-5) 最好也检查下表,至此新表正常可用。

C语言踩坑流水帐

语法与语言本身

todo

一些函数

scanf

scanf("%s",str) 并非想象那样把一个完整的字符串读取到str,比如包括空格字符的一句话;事实上,只要有空格就会终止。所以,不要指望用它从stdin中按行读取;可以使用fgets()实现读行,但注意它会带上行尾换行符。

 

 

SQL计算用户留存率,原理及流程拆解

问题及分析

根据用户记录,按一定时间周期,计算用户留存量、留存率。

留存率 = 留存量 / 初始量 * 100%

从某一个时间段作为起点,作为初始用户,一年后这些用户还有多少,两年后还有多少... 以此即可做计算。这里有个问题,就是这个起点的用户,是否也是从更早以前即存在、并留存到现在的;如果把本身即是“留存”而来的,留存率是不准确的。所以,通常要以用户首次出现开始计算。

简单来说,就是从新用户开始,计算其N一个时间段后留存;而每个时间段,都有新用户,所以这是个斜三角形的表。

以客户的销售记录为例,分步拆解计算原理。

已有原始数据表,包含了每个客户的销售记录,包括销售时间、销售数量、销售金额等。按年计算客户留存。

第一步,计算出初级表。

按年(yr)计算每个客户(cust)的统计信息,销量(sales)、销售金额(amount)。逻辑上,该结果表有个惟一键 (yr+cust),后续所有计算都从该表出发。

表结构如下

CREATE TABLE `yrept` (
  `cust` int(11) NOT NULL DEFAULT 0 COMMENT '客户',
  `yr` int(11) NOT NULL DEFAULT 0 COMMENT '年份',
  `sales` int(11) NOT NULL DEFAULT 0 COMMENT '销量',
  `amount` decimal(30,2) NOT NULL DEFAULT 0.00 COMMENT '金额',
  UNIQUE KEY `cust_yr` (`cust`,`yr`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
 COMMENT='每个客户cust在每个年份yr上的统计';
-- 数据表见附件
每个客户cust在每个年份yr上的统计

每个客户cust在每个年份yr上的统计

从原始数据表计算出一张每个时间段里每个用户的统计信息,

第二步,计算每个客户首次出现的年份。

SELECT cust,min(yr) as yr FROM `yrept` GROUP BY `cust`;

第三步,从原表yrept入手,扩展表,联入每个行对应客户的首次出现年份,预览数据。

原表yrept是每客户在各年份上的统计,那为该表增加一个字段,该行客户cust的首次出现的年份yr_1st ,语句如下

SELECT a.*,'|' as s,b.*,'|' as p, a.yr-b.yr_1st AS `later_yr`
FROM `yrept` a INNER JOIN (
    SELECT cust,min(yr) as yr_1st FROM `yrept` GROUP BY `cust`
) b ON a.cust=b.cust
WHERE 1
ORDER BY b.cust, b.yr_1st
;

另加一个计算列,相对于客户首次出现后的年数 later_yr。

汇总统计

上步结果表,关注其中的 cust, yr_1st, yr 三列,即是客户cust首次出现起,之后每年留存的客户信息(有对应记录即是留存)。对 yr_1st + yr 做汇总统计,即是每个年份新增客户数目、及之后每个年份的留存统计。

SELECT b.yr_1st,a.yr AS in_yr,count(*) as cnt
  ,count(DISTINCT a.cust) as unq_cust, SUM(a.sales) as sales 
  -- ,SUM(a.amount) as amount
FROM yrept a INNER JOIN (
    SELECT cust,min(yr) as yr_1st FROM yrept GROUP BY cust 
) b ON a.cust=b.cust 
WHERE 1 
GROUP BY b.yr_1st,in_yr 
ORDER BY b.yr_1st,in_yr
;

换个形式,以 将 yr 换成later_yr做统计,即客户cust首次出现起,N年后的留信息。事实这两种形式是等价的。

SELECT b.yr_1st,a.yr-b.yr_1st AS later_yr,count(*) as cnt
  ,count(DISTINCT a.cust) as unq_cust, SUM(a.sales) as sales
  -- ,SUM(a.amount) as amount
FROM yrept a INNER JOIN (
    SELECT cust,min(yr) as yr_1st FROM yrept GROUP BY cust
) b ON a.cust=b.cust
WHERE 1
GROUP BY b.yr_1st,later_yr
ORDER BY b.yr_1st,later_yr
;

以上两个结果,其中的 cnt 与unq_cust 两列,事实上也是等价的,就是惟一客户人数。上两条语句中,还有注释掉的一行是对总金额的计算,可以按销售金额来计算留存,有需要可选用。

按需要平摊成需要的形式

比如以yr_1st为行、按later_yr或in_year 分成多列展示。

计算留存率

观察上面平摊表,从新客户开始逐年的留存率的计算就很直观了。

如果对按右表整列求和,得到按年的所有新客户、逐年总留存率,可视为多年的新客户整体留存率,也是有意义的。虽然不太直观。

甚至,还可以对右表整列求和,并计算留率,可视为每年的所有客户(包括新客与留存客户)在之后每年的留存率。

postgres中执行DELETE ... LEFT/RIGHT JOIN

问题:有两张表 users, addrs,需要从users表中删除一些行,条件为在“addrs"表中没有对应id的行。

table-users

table-users

table-addrs

table-addrs

事实上很简单,只是个left join查询,SELECT a.*,'|' s,b.* FROM users a LEFT JOIN addrs b ON a.id=b.id;,预览如下:

overview-join

overview-join

然而,postgres本身不支持 DELETE ... LEFT JOIN ... 这样的语句,这点不像MySQL那样方便。

有一个曲线救国方法:

把上面LEFT JOIN的结果表视为一张中间表,把原users表、按主键 inner join 到中中间表,然后从users表中按删除指定行。语句如下

DELETE FROM users
USING users a LEFT JOIN addrs b ON a.id=b.id
WHERE users.id=a.id AND b.name IS NULL;

问题得解。

当然也可以使用子查询,从中间表中取出id主键,以子查询形式删除。不推荐。

前文用表及数据

CREATE TABLE users (
    id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY,
    name character varying(255)
);
ALTER TABLE users ADD CONSTRAINT users_pkey PRIMARY KEY (id);

CREATE TABLE addrs (
    id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY,
    addr character varying(255)
);
ALTER TABLE addrs ADD CONSTRAINT addrs_pkey PRIMARY KEY (id);

INSERT INTO "users" VALUES (1, 'Tom');
INSERT INTO "users" VALUES (2, 'Jarry');
INSERT INTO "users" VALUES (3, 'Alias');
INSERT INTO "users" VALUES (4, 'Bob');
INSERT INTO "users" VALUES (5, 'somebody');

INSERT INTO "addrs" VALUES (1, 'Luoyang');
INSERT INTO "addrs" VALUES (2, 'Shanghai');
INSERT INTO "addrs" VALUES (3, 'Hongkong');

EOF.

 

wordpress站点安全设置

这也是一篇草稿了好几年的文章,一时半会儿也不大可能继续完善了

wordpress本身安全性,可以通过安装一些插件实现,

Akismet,

防垃圾评论

Disable XML-RPC-API,

禁用xmlrpc协议的一些api,减少针对 xmlrpc.php 的攻击。其中:

Security Settings: 4个选项全部启用,尤其第一个默认未开启"Disable JSON REST API" 推荐打开,

Limit Login Attempts Reloaded,

防止暴力登录尝试。推荐允许重试4次,把拦截时间设置成60分钟或更长时间

Cron Events

管理wordpress的定时任务调度器,在羼弱服务器上,wp-cron.php 容易跑死php进程,降低定时任务的执行频率,可以改善这问题。

 

MySQL/MariaDB下索引基数cardinality的更新问题

起因与问题

使用MySQL做数据,有时会隐约感觉到一些语句执行速度极其慢,而理论上应该是很快的。通常使用phpMyAdmin作为客户端,在表结构页里可以方便的看到索引状态,对基数cardinalyty一知半解,隐约理解为惟一值个数。

但前两天写一条查询语句执行速度非常非常慢,看到一个索引的基数竟然是空的,而且明明应该有很多值。猜测MySQL出bug了,于是删除并重建了索引,基数正常了,语句也飞快跑完。于是稍多留意了一下索引基数。同一天,看到一个基数为1的索引,也是很多惟一值的字段,这也不正常。因为是MyISAM表,直接打包了对应的.frm, .MYD, MYI 三个文件,保留一个现场,有时间再做研究。

回顾了问题表生成语句,整理出一个简单化的重现过程,下面讨论。

问题重现

测试平台为 MySQL 5.5, MariaDB 10.3,其它版本应该也是类似。直接上语句

/* ************************************************* */
-- 一张数据表,并填充一些数据,稍微大一些
DROP  TABLE IF EXISTS `d_src` ;
CREATE TABLE `d_src` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `org` char(32) NOT NULL DEFAULT '',
 PRIMARY KEY (`id`),
 KEY `org` (`org`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;

INSERT INTO `d_src`(org) VALUES (md5('abcdefg'));
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;
INSERT INTO `d_src`(org) SELECT md5(concat(org,id,rand()*id)) FROM `d_src`;

-- 新建一张表,这里先使用 MyISAM 存储引擎,用来重现问题。插入数据,加字段及索引,UPDATE
DROP TABLE IF EXISTS `d_making`;
CREATE TABLE `d_making` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `org` varchar(50) NOT NULL DEFAULT '',
 `cnt` int(10) unsigned NOT NULL DEFAULT 0,
 PRIMARY KEY (`id`),
 KEY `org` (`org`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

insert into `d_making`(`org`)
SELECT `org` FROM `d_src` 
ORDER BY id desc;

ALTER TABLE `d_making` ADD org_fix varchar(50) NOT NULL DEFAULT '', ADD INDEX (org_fix);

UPDATE `d_making` SET org_fix=left(org,2 );

-- watch the index status, the Cardinality of Key org_fix is 1
SHOW index FROM d_making;

此时查看索引状态,注意org_fix索引的基数,竟然是1 !  删除并重建索引,再看

-- rebuild index and show key again, the Cardinality NOT 1 
ALTER TABLE `d_making` DROP INDEX `org_fix`, ADD INDEX `org_fix` (`org_fix`); 
SHOW index FROM d_making;

org_fix的基数应该是256左右(因为是原始数据是随机数,大概在256的附近)。

按上脚本,只d_making表改用InnoDB存储引擎,结果也类似,也许不是1,但也明显不对。

不过,在MySQL 8.0 (CentOS 8默认配置)下,得到了 258,比真实值256要大。

原因探求

最早注意到索引基数时,搜索过相关资料,但仍是一知半解。这次通过 "mysql cardinality rebuild index" 查询,找到stackoverflow上有类似问题,最终找到mysql官方手册的一段说明

Cardinality

An estimate of the number of unique values in the index. To update this number, run ANALYZE TABLE or (for MyISAM tables) myisamchk -a.

Cardinality is counted based on statistics stored as integers, so the value is not necessarily exact even for small tables. The higher the cardinality, the greater the chance that MySQL uses the index when doing joins.

理解下来,说是索引基数只是个估算值,并不一定可靠,可以用 ANALYZE TABLE 表 更新。事实上,删除并重建索引并不一定奏效。SQL语句执行时,优化器会依据索引基数决定是否使用索引,于是,基数不正确(过小)的索引,就不能被选用,于是这个索引事实上无效了。

结论

  1.  MySQL索引基数,可能不准确(过大过小都有可能),从而可能造成语句执行时忽略该索引(索引失效)。
  2.  要想让该值准确,得在表数据有改动后跑一遍 ANALYZE TABLE table_name

对使用者来说,这是个潜在的坑。手工运行SQL语句前,可以事先关注一下索引基数是否正常(或先 explain一下)。如果是在线生产环境,可能就要自求多福了,或者搞一大群的计划任务,跑ANALYZE TABLE. 不过,理论上,没有大面积UPDATE的字段的索引,这个问题不大。

后记

再深入一点,索引基数即是MySQL统计信息,关系型数据库的查询优化器要依据统计信息确定是否使用某个索引;而统计信息并非实时更新的。本问题的即是统计信息未及时更新的一个极端案例。也就是说,这个问题并非MySQL本身的缺陷,应该在关系型数据库中普遍存在。

英文中连续字母频次统计

这只是个无聊的小把戏。

最近又看到关于qwerty键盘布局的讨论,有提到好的键盘布局标准之一:把连续的按键分散在左右两手上,这样第一只手击键的同时,第二只手可以提前做好准备,提高效率。

于是从网上找了一批古典英文小说的txt电子版,写了个傻傻的脚本,统计其中连续两个字母的频次(忽略大小写),其中频度最高的前30项如下表。这30项共计占总频次的43.3%.

如前述,“是否可以把连续击键分散在两只手上”,即表格最后一列。从结果上看,分散与否各15项,所以qwerty在这个标准上优势似乎并不明显。

当然,这并不能证明qwerty键盘是不合理的;毕竟评估因素非常多。

序号 字母组 频次 占比 双手分散*
1 he 390103 4.04% Y
2 th 366573 3.80% Y
3 in 234621 2.43%
4 er 221732 2.30%
5 an 205618 2.13% Y
6 re 163779 1.70%
7 nd 148125 1.53% Y
8 ha 135536 1.40% Y
9 ou 133357 1.38%
10 ed 132822 1.38%
11 on 132233 1.37%
12 at 128740 1.33%
13 en 124127 1.29% Y
14 ng 117178 1.21% Y
15 hi 116461 1.21%
16 to 112860 1.17% Y
17 it 108636 1.13% Y
18 is 103327 1.07% Y
19 as 102013 1.06%
20 ar 99665 1.03%
21 es 98518 1.02%
22 te 96810 1.00%
23 or 94932 0.98% Y
24 le 92578 0.96% Y
25 st 92218 0.96%
26 of 91493 0.95% Y
27 se 87755 0.91%
28 ve 84620 0.88%
29 me 80898 0.84% Y
30 ea 77540 0.80%

使用的小说素材有:傲慢与偏见, 安娜卡列尼娜, 巴黎圣母院, 悲惨世界, 格列佛游记, 白衣女人, 飘, 呼啸山庄, 尤利西斯, 德伯家的苔丝, 爱玛, 白鲸, 黑骏马。

傻傻的脚本。脚本本身区分大小写的,整理统计结果时在excel里合并了大小写。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

fp=open('english_novels_all.txt')
raw=fp.read()
fp.close()

ch='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
for i in range(len(raw)-2):
  if raw[i] not in ch or raw[i+1] not in ch:
    continue
  k=raw[i:i+2]    # k=raw[i:i+2].lower()
  if k in st:
    st[k]+=1
  else:
    st[k]=1

fp=open('st_out.txt','w+')
for k in st:
  fp.write('%s %s\r\n'%(k,st[k]))

fp.close()

Adblock Plus 的个人设置

FireFox 扩展Adblock Plus

使用原则:只阻止过于反感的广告,其他广告一律放行;亦即,自带的过滤列表一律不激活。

“Adblock Plus 设置 - 高级” 设置页

创建和编辑您的过滤列表

//pos.baidu.com/
//static.mediav.com/js/