Emacs org-mode基础学习

什么是 org-mode

Org mode is for keeping notes, maintaining TODO lists, planning projects, and authoring documents with a fast and effective plain-text system.

这是官网的标语,我想这一句话,也概括了org-mode的功能.

入门

插入标题

*:一级标题

**:二级标题

...

注意,*号后面要留一个空格

插入链接

[[链接地址][title]]

段落

对于单个回车换行的文本,认为其属于同一个段落。

插入表格

任何以"|"字符的非空开头的都会被当作是表格的一部分.类似输入如下.(多利用TAB键,会自动补全)

| name | age |
|------+-----|
| yzy  | 25  |

表格还可以类似excel那样,可以通过公式,让某列的值是某个函数的结果.非常简单及强大.^_^

快速格式化以逗号,空格,tab等的文本为表格:
选中想要生成表格的内容,然后按:

C-c |

就会格式化为表格了

不同字体

*粗体*

/斜体/

+删除线+

_下划线_

下标: H_2 O

上标: E=mc^2

等宽字:  =git=  或者 ~git~

插入分隔线

五条或以上的”-“就会显示为分隔线

插入块

| 快捷键 | 标签                            |
| s      | #+begin_src … #+end_src         |
| e      | #+begin_example … #+end_example |
| q      | #+begin_quote … #+end_quote     |
| v      | #+begin_verse … #+end_verse     |
| c      | #+begin_center … #+end_center   |
| l      | #+begin_latex … #+end_latex     |
| L      | #+latex:                        |
| h      | #+begin_html … #+end_html       |
| H      | #+html:                         |
| a      | #+begin_ascii … #+end_ascii     |
| A      | #+ascii:                        |
| i      | #+index: line                   |
| I      | #+include: line                 |

插入列表

无序列表项以‘-’、‘+’或者‘*‘开头。

有序列表项以‘1.’或者‘1)’开头。

描述列表用‘::’将项和描述分开。

有序列表和无序列表都以缩进表示层级。只要对齐缩进,不管是换行还是分块都认为是处于当前列表项。

我的Emacs之路

我的Emacs学习之路

说起emacs,肯定会少不了和vi/vim的争论,这两者的所谓的神的编辑器(Emacs)和编辑器之神(Vim/vi)一直是我们这些Programmer的口舌之论.在此,我不多说哪个好哪个坏,我是一个从Vim到Emacs的使用者,只是说下自己使用两者的经历.(这不代表我完全脱离vim,偶尔在服务器上还是使用vim,自己的平时笔记什么的,基本都用Emacs来作为我主要的编辑器).

每个程序员,或多或少最先接触的是vi/vim,因为不少的Gnu/Linux发行版默认安装的就是vi或vim,而且在SSH到服务器上管理时,就更加可想而知了,所以说vi/vim使用者众多,并不是说是使用者自己非常愿意或者说是心甘情愿去学习的,而是因为几乎所有的*nix系统都默认安装的,所以使用者就多了.这就好比Windows系统默认安装了IE一样,在以前的IE霸主时代,代表着说浏览器就是说IE.可是,当有一天,你发现了一个全新的与IE不同的浏览器时,你才知道,原来这个世界,还有这么好用的浏览器,比如Chrome.有人说IE,代表着I’m Evil.试问下,同时真正接触Emacs和Vim两者的又有多少?真正去认真接触,去深入讨论比较的又有多少?

在<一代宗师>里,武功有三种境界,大概意思是:见自己,见天地,见众生.我想许多人还停留在见自己的阶段.不去见下天地,不去见下众生.既然是两大神器,为何都不去多点了解下,然后才去决定到底哪个编辑器适合自己.

使用Emacs,纯属偶然.之前也有打开过Emacs,但是发现它与vi的思维完全不同,emacs没有类似vim那样,有几种不同的模式,还有经常性地按下Esc键,有时候自己都不知道是处在什么模式,就连续按下Esc按好几次来确保自己是处在普通模式,以便可以移动或进入命令模式.从接触Linux起到现在,还是对vim没什么感觉,还买了本来看,但还是学不上手.

就在今天的清明节前几天,也不知道什么原因使自己心血来潮,突然间想接触下Emacs,这方面中文资料比较少,但经过这一个多星期的努力,不敢说精通,但至少上手了,而且感觉良好.至少比起自己以前用vim,更有感觉,而且已经将它作为了日常的主要编辑器.比我的感觉,就像是学了这么久MySQL,突然间接触到了PostgreSQL,那种感觉非常美妙.有人说,你这样子没有什么用吧,贪新厌旧,不如人家精通一个.但是,我却不这样子认为,其实所谓的精通,谁又能真正地精通软件?日日新,月月新的发展的社会和技术,特别是我们这软件行业.谁敢说精通Java?一个行业学到一定程度,就会有一个迷茫期,不知道如何进一步去提高,这时如果接触一些新的知识,转换下角度,也许发现我们以前觉得理所当然的东西,或者已经存在着更好的了,只是自己不愿意去学习而已,或者有那种先入为主的思想,既然我已经有了vim,而且也没有觉得有什么不好.我觉得,这就像开始说的,还在”见自己”的阶段,不去见见天地,怎么知道外面的世界是不是还有更精彩的.

自己也一直在追求一个Best For MySelf的编辑器,我经常要用Markdown,Todo写笔记,偶尔还要生成一下PDF和HTML等笔记,偶尔还要写写HTML,JS,Bash等脚本的东西,甚至写写电影观后感或读书笔记之类的,噢,还有要用表格来记录下自己的生活花费等.在接触Emacs之前,我大多是用Mou,Markdonw Pad,Macdown,Numbers, Excel来做表格,但总是觉得不满意,直到我遇到了Emacs,遇到了Emacs的org-mode,我才觉得,这才是我需要的.不用在各个软件之间来回切换,而且操作方式习惯等都还不同,而用Emacs,”加料不加价”,所有的操作方式都在同一个操作习惯下完成.不同在不同的软件之间来回切换,还有思考方式及操作方式的切换.让你专注于写文字,that’s all.

我对Emacs的初步感觉

优点

  • 无模式,输入即所见.这与vim不同,要不断按Esc来切换,可能我比较笨,总是记不得当前是什么模式,所以总要按Esc好几次.
  • 非常自然的光标移动,与*nix工具使用非常相似.比如在Bash的基础操作与Emacs是相同的(在Mac下更好,Mac的文本编辑操作与Emacs的非常类似),一想起Vim的HJKL的光标移动,我…
  • 非常方便的宏编辑.说真的,虽然vim里也有宏编辑,但基本没有用过,自从用了Emacs后,反而更有意识地倾向使用宏功能.
  • org-mode 这个可以说是神器中的神器.
  • Emacs自带有插件管理器,类似vim的vendle和ubuntu的apt软件包管理器.
  • 在Emacs里可以做非常多的事件.难怪有人说Emacs是一个伪装的操作系统.可以播放无损音乐,看PDF,看图片等更是不在话下.
  • 用一种操作习惯来做各种不同的事情.

缺点

  • 安装完比较多的插件后,启动有点慢.但还可以接受.

Ubuntu下的开发环境配置

Java Env

  1. /etc/profile.d/mybash.sh

     1  export JAVA_HOME="/home/dreameryzy/java/jdk1.8.0_05"
     2  export PATH=$JAVA_HOME/bin:$PATH
     3  export CLASSPATH=$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:.
     4  export M2_HOME=/home/dreameryzy/java/apache-maven-3.3.1
     5  export PATH=$M2_HOME/bin:$PATH
     6
     7  export LANG="en_US.UTF-8"
     8  export LANGUAGE="en_US.UTF-8"
     9  export LC_CTYPE="zh_CN.UTF-8"
    10  export LC_NUMERIC="zh_CN.UTF-8"
    11  export LC_TIME="zh_CN.UTF-8"
    12  export LC_COLLATE="zh_CN.UTF-8"
    13  export LC_MONETARY="zh_CN.UTF-8"
    14  export LC_MESSAGES="zh_CN.UTF-8"
    15  export LC_PAPER="zh_CN.UTF-8"
    16  export LC_NAME="zh_CN.UTF-8"
    17  export LC_ADDRESS="zh_CN.UTF-8"
    18  export LC_TELEPHONE="zh_CN.UTF-8"
    19  export LC_MEASUREMENT="zh_CN.UTF-8"
    20  export LC_IDENTIFICATION="zh_CN.UTF-8"
    21  export LC_ALL="zh_CN.UTF-8"
    
  2. ~/.bash_aliases

     1  alias g='git'
     2  alias gl='git pull --prune'
     3  alias glog="git log --graph --pretty=format:'%Cred%h%Creset %an: %s - %Creset %C(yellow)%d%Creset %Cgreen(%cr)%Creset' --abbrev-commit --date=relative"
     4  alias ga='git add'
     5  alias gadd='git add'
     6  alias gpull='git pull'
     7  alias grm='git rm'
     8  alias gf='git fetch'
     9  alias gp='git push'
    10  alias gd='git diff'
    11  alias gc='git commit'
    12  alias gpl='git pull'
    13  alias gcm='git commit -m'
    14  alias gca='git commit -a'
    15  alias gco='git checkout'
    16  alias gb='git branch'
    17  alias gs='git status -sb'
    18  alias grm="git status | grep deleted | awk '{print \$3}' | xargs git rm"
    19  alias gpp='git pull && git push'
    20  alias glol='log --graph --decorate --pretty=oneline --abbrev-commit'
    21  alias gppl='git log --pretty=format:%aN | sort | uniq -c | sort -rn'
    22  alias gu='git reset --hard HEAD^'
    23  alias guncommit='git reset --hard HEAD^'
    24  alias vim="emacs"
    

Install Git and Emacs

  1. Emacs

    1  sudo add-apt-repository ppa:cassou/emacs
    2  sudo apt-get update
    3  sudo apt-get install emacs24
    
    • 安装完Emacs后还要配置好相应的中文输入法问题,经过折腾,总结如下:(ubuntu14.04,fxcti-table-wubi fxcti-table-wubi-large)
      1. 下载*Unity Tweat Tool*这个配置工具
      2. 下载*Yahei Consolas Hybrid*这个字体,并进行安装
      3. 利用1)的工具,Fonts这个tab的配置将所有字体都设置为2)的字体并应用
      4. 还要注意的是设置下locale环境。这个locale设置已经放在Install Java那里了,可以参考一下看看。^_^
  2. Git

    1  sudo add-apt-repository ppa:git-core/ppa
    2  sudo apt-get update
    3  sudo apt-get install git
    

Install Maven

  1. .bashrc

    1  export M2_HOME=/home/dreameryzy/java/apache-maven-3.3.1
    2  export PATH=$M2_HOME/bin:$PATH
    

Install Google Chrome

Install Eclipse

  1. 创建Ubuntu的Unity启动Icon

     1  emacs  ~/.local/share/applications/opt_eclipse.desktop
     2
     3  [Desktop Entry]
     4  Type=Application
     5  Name=Eclipse
     6  Comment=Eclipse Integrated Development Environment
     7  Icon=** something like /opt/eclipse/icon.xpm **
     8  Exec= ** something like /opt/eclipse/eclipse **
     9  Terminal=false
    10  Categories=Development;IDE;Java;
    11  StartupWMClass=Eclipse
    12
    13  nautilus ~/.local/share/applications
    14
    15  finally drop to unity launcher, that's all.
    

Install ia32-libs On Ubuntu 14.04

1  sudo -i
2  cd /etc/apt/sources.list.d
3  echo "deb http://archive.ubuntu.com/ubuntu/ precise main restricted universe multiverse" >ia32-libs-raring.list
4  apt-get update
5  apt-get install ia32-libs
6  rm /etc/apt/sources.list.d/ia32-libs-raring.list
7  apt-get update
8  exit

[翻译]未初始化对象与对象初始化为null

原文

问题:

我正从事Java的工作.我通常如下设置一些对象:

1
2
3
4
5
6
7
8
9
10
11
public class Foo {
private SomeObject someName;
// do stuff
public void someMethod() {
if (this.someName != null) {
// do some stuff
}
}
}

问题是:someName在这个例子里等于null,我是否可以可靠地为所有对象假设为空检查未初始化对象,这是否准确?

最佳回答:
正确,在Java里所有引用类型的静态以及实例成员,没有显式地初始化的,都会被设为null.这个规则同样适用于数组成员.

从Java语言规范, 4.12.5部分可知:

1
2
3
4
5
6
7
变量的初始值
在程序里的每一个变量在使用之前都必须有一个值:
每个类变量,实例变量或者数据部分在被创建的时间会被初始化为一个默认值.
[...] 对于所有引用类型,默认值为null.

注意,以上规则不包括局部变量:它们必须显式地初始化,否则程序不会被编译.

[翻译]手把手教你配置流复制

原文

尽管许多人知道流复制,这篇博客是为初学者准备的,我将从必要条件和一些关于流复制的介绍开始。:-)

必要条件:

  1. 在各自的服务器上安装相同版本的PostgreSQL数据库。

  2. 配置”postgres”用户的无密码ssh认证(译注:如使用ssh的公私密匙)

  3. 生产环境的服务器必须在postgresql.conf文件配置通过设置archive_modearchive_command参数来开启WAL归档模式

  4. 生产环境的服务器和备份服务器之间应该一直拥有连接以让归档的WAL文件从生产环境的服务器传输到备份服务器.

  5. 明确地配置好你的备份机的环境和目录结构要与生产服务器的一样。

介绍

它是一个异步机制;所以备机是延迟于主服务器。但不同于其他复制方法,这个延迟是非常短的,可能是一个单事务的延迟,这取决于网速,数据库负载以及流复制配置。此外,在主机上对每个备机的负载是最小的,允许一个主服务器支持多个备机。

这个特性包含在PostgreSQL 9.0中,伴随的第二个数据库实例(译注:指stand-by是依赖于主服务器的二进制日志,在这期间会标记备机服务器只能接受只读请求)

这是实践步骤所需命令:

1.到主服务器,然后创建一个带有复制权限的replication用户。例如:

1
2
3
4
5
6
7
$ psql
Password for user postgres:
psql.bin (9.2.1) Type "help" for help.
postgres=# create user replication with replication password '<password>';

2.我们需要在主服务器的/opt/PostgreSQL92/data/(译注:就是postgresql的数据目录)目录修改postgresql.conf参数以及pg_hba.conf的验证参数。设置好连接及验证,以让备机服务器可以成功地连接到主服务器的replication这个假的数据库。

1
2
3
4
5
6
7
8
$ $EDITOR postgresql.conf
listen_addresses = '*'
$ $EDITOR pg_hba.conf
#备机服务器必须有超级用户身份的访问权限
host replication replication 10.176.0.0/16 md5

3.在主服务器上设置流复制相关的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$EDITOR postgresql.conf
#为了在备机服务器上开始“只读”查询,wal_level必须设置成“hot_standby”. 但是,如果你一直不会在stand-by模式下连接到备机,你可以选择“archive”
wal_level = hot_standby
#设置允许备机的最大并发连接数
max_wal_senders = 5
#为了防止主服务器在循环利用WAL段文件前移动备机服务器所要求的WAL段文件,设置保留在 pg_xlog 目录里最小的段文件数。
wal_keep_segments = 32
#在主服务器上开启WAL归档到一个归档目录以让备服务器获取。如果"wal_keep_segments"值足够大以保留备机所要求的WAL段文件数,这可能就不必要开启。
archive_mode = on
archive_command = 'cp %p <archive location>%f && scp %p postgres@10.176.112.189:<archive location>/%f'

注意:在 postgresql文件里修改以上参数时,要重启服务器。

4.在主服务器上重启postgres并检查参数是否生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
postgres=# show archive_command ;
archive_command
-----------------
cp %p /var/PG92_Archives/%f
(1 row)
postgres=# show archive_mode ;
archive_mode
-------------
on
(1 row)
postgres=# show wal_level ;
wal_level
------------
hot_standby
(1 row)
postgres=# show max_wal_senders ;
max_wal_senders
----------------
5
(1 row)
postgres=# show wal_keep_segments ;
wal_keep_segments
------------------
32

5.对主服务器的数据目录做一个基础备份。

1
2
3
4
5
6
7
8
9
10
11
$ psql ­c "SELECT pg_start_backup('label', true)"
$ cp /opt/PostgreSQL92/data/* backup/
$psql ­c "SELECT pg_stop_backup()"
-- 打包备份目录并传输到备机的数据目录下
$tar ­cvzf backup.tar backup/
$scp backup.tar postgres@10.176.112.189:/opt/PostgreSQL92/

6.移动备机的数据内容到其他的位置,解压备份的文件并复制解压后的数据到备机的数据目录。

7.在备服务器上像主服务器那样设置复制相关的参数,连接以及验证,以便让备服务器在主服务器宕机之后可以切换成主服务器。

8.在备服务器上开启只读查询。但如果在主服务器上的wal_level参数值是archive,那hot_standby就不需要更改(即是:off)

1
2
$ $EDITOR postgresql.conf
hot_standby = on

9.在备机服务器上创建一个恢复命令文件,以下参数对于流复制是必需的。、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ $EDITOR recovery.conf
# 指明是否开启服务器作为一个备机。在流复制里,这个参数必须要开启。
standby_mode = 'on'
# 指明用于备服务器连接到主服务器的连接字符串。
primary_conninfo = 'host=10.176.112.188 port=5432 user=replication
password=<password> application=<app_name>'
# 指定一个触发文件让备服务器感觉到它的时候就会停止流复制(即:故障转移)
trigger_file = '<any path="">' ===> 不要创建这个文件。当你想主从切换的时候才需要创建它。
# 指定一个命令从WAL归档中加载归档段文件。如果“wal_keep_segments”是一个足够大的数值以保留WAL段文件满足备机的要求,这可能不是必要的。但一个高负载情况下可能会导致段文件在备服务器完全同步之前就已经循环利用的,这时就要求你重新开始一个新的基础备份。
restore_command = 'cp <archive_location>%f "%p"'
</archive_location></any>

10.在备服务器上启动postgres。它就会开始流复制并且你会看到像以下的信息:

1
2
3
4
LOG: entering standby mode
LOG: consistent recovery state reached at 0/1D000078
LOG: record with zero length at 0/1D000078
LOG: streaming replication successfully connected to primary

11.你可以通过比较主服务器上的当前的WAL写位置与备服务器上的最新“接收/重做”的WAL位置来计算复制的延迟。它们各自可以通过在主服务器端使用pg_current_xlog_location函数来获取,在备服务器上通过pg_last_xlog_receive_location或者pg_last_xlog_replay_location来获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ psql ­c "SELECT pg_current_xlog_location()" ­h192.168.0.10
(primary host)
pg_current_xlog_location
0/2000000
(1 row)
$ psql ­c "select pg_last_xlog_receive_location()" ­h192.168.0.20
(standby host)
pg_last_xlog_receive_location
0/2000000
(1 row)
$ psql ­c "select pg_last_xlog_replay_location()" ­h192.168.0.20
(standby host)
pg_last_xlog_replay_location
0/2000000
(1 row)

12.其他检查流复制的方法有:最简单的方法是在备服务器上执行“select now()-pg_last_xact_replay_timestamp();”。pg_last_xact_replay_timestamp()函数给出在恢复期间最近的事务重做的时间戳,这些是在主服务器产生事务产生的提交或中止WAL记录的时间。如果在恢复期间没有事务重做,这个函数就会返回NULL。否则,如果恢复仍然在进行的话,它会一直递增。如果恢复已经完成,这时的值会保留在恢复期间中最近事务重做的值而不会变。当服务器已经正常启动并没有恢复,这个函数就返回NULL.你可以尝试在主服务器上做一些操作,然后检查这个函数的输出。
如果你想手工检查主备之间的延迟,那可以做以下步骤:

1
2
3
4
5
6
7
8
9
10
11
步骤1:在主服务器上使用以下命令来创建表。
create table stream_delay (tstamp timestamp without time zone );
insert into stream_delay select now();
步骤2: 在主服务器上使用调度工具cronjob每分钟执行以下命令。
update stream_delay set tstamp='now()';
步骤3: 通过在备服务器上检索"stream_delay"表来证实延迟。
它应该显示在主服务器上最近更新的时间。这个时间戳和备服务器的当前时间戳之差就是主服务器和备服务器之间的延迟。

你也可以通过使用ps命令来检查流复制的进展。显示的”LSNs”位置指明了备服务器已经写到了xlog的字节位置。

1
2
3
4
5
6
7
[primary] $ ps ­ef | grep sender
postgres 6879 6831 0 10:31 ? 00:00:00 postgres: wal sender
process postgres 127.0.0.1(44663) streaming 0/2000000
[standby] $ ps ­ef | grep receiver
postgres 6878 6872 1 10:31 ? receiver process streaming 0/2000000

谢谢大家,请让我知道我是否遗漏了什么。

[翻译]PostgreSQL中的log, xlog和clog

原文

译记:该文章开头还有一段内容就不翻译了。主要翻译这些log的主要内容。

pg_log

$PGDATA/pg_log是数据库运行活动日志的默认保存目录,它包括错误信息,查询日志以及启动/关闭数据库的信息。当PostgreSQL启动失败时,这里应该是你第一个应该查看的信息。一些Linux发行版以及其他的软件包管理系统会将这个日志目录移到某些地方,比如:/var/log/postgresql

你可以在pg_log目录里自由地删除、重命名、压缩或者移动文件而不会有什么不好的结果,只要Postgres用户仍然有权限写该目录。如果pg_log随着许多大文件而膨胀,你可能需要在postgresql.conf里减小你想记录日志的事件。

pg_xlog

$PGDATA/pg_xlog是PostgreSQL的事务日志。 这是一些二进制日志文件的集合,文件名类似00000001000000000000008E,它包含最近事务的一些描述数据。这些日志也被用于二进制复制。如果复制、归档或者PITR失败了,当归档正在恢复时,这个目录保存的数据库日志可能会膨胀数GB。这可能会导致你用完你的磁盘空间。不像pg_log,你不能自由地删除、移动或者压缩这个目录的文件。你甚至不能在没有符号链接到该目录的情况下移动这个目录。删除pg_xlog的文件可能会导致不可恢复的数据库损坏。

如果你发现自己处在这样的情况:你发现有100G大小的文件在pg_xlog目录并且数据也启动不了,并且你已经禁止归档/复制并且尝试清理磁盘空间等任何其他的方式,请做以下两个步骤:

  1. pg_xlog目录里移动文件到一个备份磁盘或者共享网络驱动器中,也不要删除它们,并且
  2. 移动一些最老的文件,直到足够允许PostgreSQL启动起来。

pg_clog

$PGDATA/pg_clog包含了事务的元数据。这种日志用于告诉PostgreSQL哪个事务已经完成、哪个还没有完成。clog是比较小的并且没有任何理由会膨胀,所以,你应该没有任何理由去碰触它。在任何时候你都不应该从pg_clog里删除文件,如果你这样子做,还不如完全地删除整个数据库目录。缺少clog是不可恢复的。请注意,这意味着,如果你在$PGDATA目录里备份文件,你应该确定同时包含pg_clogpg_xlog,否则你可能会发现你的备份是不可用的。

[翻译]高效使用PostgreSQL索引

原文

在Postgres里有许多种索引类型,也有不同的方式来使用它们。在本文中,我们概述一下可用的索引类型,并解释不同的使用和维护最常见的索引类型的方式:B树。

索引是一种从表中检索的行数相对较小的有效方式。如果从一个表中检索的行数相对较小时索引是非常有用的(例如:按条件来检索行——WHERE子句选择)。对于免排序的,B树索引也是非常有用的。

索引类型

Postgres支持许多不同的索引类型:

  • B树对于当你执行CREATE INDEX时是默认的索引类型。实际上,所有数据库都有一些B树索引。字母B代表是Balanced(平衡),这个大意是,两边的树的数据量是大致相同的。因此,必须遍历去找行的层次数总是在相同的范围内。B树索引可以有效地用于等值和范围查询,并且也可以用于检索NULL值。B树的设计与缓存可以很好地工作,即使只是缓存部分。

  • 哈希索引只在等值比较时才有用,但是你几乎从来没有想使用它们,因为它们不是事务安全的,崩溃后需要手工重建,并且是不会被复制到从库的,因此,比B树索引的优势是相当小的。

  • 当一个索引必须映射多个值到一行时通用反转索引(GIN,Generalized Inverted Indexes)(通用逆向索引、广义倒排索引)是非常有用的,然而当一行有一个单键值时,B树索引会做一些优化。GIN对于索引数组值以及实现全文搜索是比较好的。

  • 通用搜索树(广义搜索树)(GiST,Generalized Search Tree)索引允许你建立普通平衡树结构,也能用于等值和范围比较之外的操作。它们用于索引几何数据类型,也可用于全文搜索。

本文是关于如何最有效地使用默认的B树索引。GIN和GiST索引使用例子,请参考扩展包说明。

为什么我的查询没有用上索引?

有许多原因导致为什么Postgres查询计划没有选择使用索引。大多数的时候,查询计划器是正确的,即使它不是那么明显地为什么会这样。
如果相同的查询有几次会使用索引扫描,但其他时候不使用索引扫描,这是没问题的。从表中检索到的行数可以基于特定的常数值的查询检索而变化。(我注:即不同的常量值,会导致不同的结果行数)。因此,例如,查询计划器对于查询select * from foo where bar = 1使用索引可能是正确的,但对于查询select * from foo where bar = 2却可能不使用索引,这发生于对于bar值为2的行有比较多的话。发生这种情况时,顺序扫描实际上是最有可能比索引扫描快得多,因此,查询计划器实际上是判断正确的,这时顺序扫描的性能代价相比索引扫描更低。

部分索引

部分索引包含表数据的一个子集。它是一个带有WHERE子句的索引。这个概念是为了提高索引的效率,减少索引的大小。小的索引占更少的存储空间,易于维护,扫描更快。

例如,假设你允许用户评论你的网站,从而设置标记布尔值为真(true)。然后你批量处理标标记的评论。你可能想创建一个类似这样子的索引:

1
CREATE INDEX articles_flagged_created_at_index ON articles(created_at) WHERE flagged IS TRUE;

这个索引将会是相当小的, 在复杂查询中也能使用其他索引来结合它一起使用。

表达式索引

表达式索引对于匹配一些函数或修改的数据是有用的。Postgres允许你索引函数结果,以便搜索变得像通过原始数据值搜索一样有效。例如,你可能要求用户保存他们的邮箱登录地址,但你想大小写不敏感的认证。在这种情况下可以保存邮件地址,但是搜索上使用WHERE lower(email) = '<lowercased-email>'。在这种查询下,唯一使用索引的方式是通过表达式索引,例如这样:

1
CREATE INDEX users_lower_email ON users(lower(email));

另一个常见例子是,查找给定日期的行,这里我们已经保存时间戳在一个datatime字段,但是想通过转换的date值来查找他们。一个像这样子的索引:

1
CREATE INDEX articles_day ON articles ( date(published_at) )

可以用在包含WHERE date(articles.created_at) = date('2011-03-07')的查询中使用。

唯一索引

唯一索引保证表不会有超过1行的相同值的行。创建唯一索引的有利的两个原因:数据完整和性能。在唯一索引上查找通常是非常快的。
在数据完整性方面,使用模型类的validates_uniqueness_of并不是真正保证唯一的,因为这可能在并发用户时会创建无效的记录。
因此,你应该问题在数据库级别创建约束——通过索引或者唯一约束。

唯一索引和唯一约束之间有点差别。唯一索引可以想成是更低级别,因为表达式索引和部分索引不能创建唯一约束。表达式上的部分唯一索引就有可能。

多列索引

虽然Postgres已经能够创建多列索引,重要的是要理解这样子做的意义。Postgres查询计划器通过位图索引扫描(bitmap index scan)在一条多列查询中有能力结合和使用多个单列索引。通常,你可以在覆盖查询条件的每个列上创建索引并且大部分情况下Postgres将会使用到它们,所以在你创建一个多列索引之前做一个基准测试并证明创建一个多列索引是有效的。正如之前一样,索引是有代价的,并且一个多列索引仅能在查询引用的列是与创建索引时的列的顺序是一样的才会被优化,虽然多个单列索引提供大量的查询的性能改进。

然而在某些情况下,一个多列索引显然是有意义的。一个在列(a,b)上的索引能够用在查询包含WHERE a = x AND b = y,或者查询仅使用WHERE a = x的情况,但是不会用于查询使用WHERE b = y的情况。所以,如果这匹配到你的应用程序的查询模式,多列索引就是值得考虑的。也要注意在本例中创建一个独立的索引(我注:我理解是在这种情况下,创建多个单列索引)是多余的。

B树和排序

B树索引项默认按升序排序保存的。在某些情况下,它可以为索引提供一个不同的排序顺序。比如当你正分页显示文章的情况,首先按最近发布的来排序。我们在articles表上可能有一列published_at。对于未发表的文章,published_at的值是NULL。在这种情况下,我们可以创建一个这样子的索引:

1
CREATE INDEX articles_published_at_index ON articles(published_at DESC NULLS LAST);

在Postgre 9.2及之后版本,值得注意的是索引并不总是需要去查找表的,我们可以提供一切需要从索引得到的数据。(也就是,没有索引的列是不感兴趣的)。这个特性叫“只读索引扫描”。

由于我们会按published_at排序以及限制结果数来查询表,我们可以从创建同样的顺序的索引中得到一些好处。Postgres会以正确的顺序在索引中找到这些行,然后去数据块里检索数据。如果索引不是排序的,Postgres就有一个好机会来顺序读取数据块并且排序结果。

这种技巧主要是当你要求相关的单列索引并且“null在排序的最后”的行为上,否则的话顺序已经是可用的,索引扫描可以在任何方向扫描。当用一个多列索引时来对付一个当查询要求一个混合排序时,例如:a ASC, b DESC,它会变得更加的相关。

管理和维护索引

在Postgres里索引并不拥有所有的行数据。即使使用索引来查询和匹配查找到的行,Postgres还会到磁盘中去取行数据。另外,行可见性信息(在MVCC文章中讨论)是不保存到索引的,因此,Postgres必须去磁盘取到这些信息。有了这一点,您可以看到在某些情况下如何使用索引并没有真正意义。索引必须足够选择性地减少磁盘的查找,这才是值得的使用索引的。例如,在一个足够大的表里按一个主键查找,这就很好地利用索引:代替匹配查询条件的顺序扫描,Postgres能够在索引里查找到目标行,然后从磁盘里选择性地取出它们。对于非常小的表,例如,一张查找城市的表,索引可能是不可取,即使你是通过城市名来搜索。在那种情况下,Postgres可能决定忽略索引而支持顺序扫描。Postgres在一些会命中很大一部分表数据的查询上将决定执行顺序扫描。如果你在那些列上有索引,它将会是一个永不被使用的死索引——并且 索引并不是“免费”的:他们在存储和维护方面是有代价的。

当调优一条查询并且了解索引是非常有意义的,从没有在你的开发机上尝试过的话。是否使用索引决定于许多因素,包括Postgres服务器配置、表的数据、索引和查询。例如,在你的开发机上带有一小部分的测试数据的表上尝试使一条查询用上索引是会令你挫败的:Postgres会决定数据集是如此小以致不值得使用索引的额外的“读”开销,然后从磁盘取数据。随机 I/O 比顺序 I/O 是非常慢的,所以,顺序扫描的代价比通过索引读取然后从磁盘查找数据的随机I/O更少。进行索引调优应该在生产或在暂存环境中完成,尽可能在生产环境中。在Heroku 的Postgres数据库平台,可以很容易地复制你的生产数据库到一个不同的环境。

当你准备在你的生产数据库上应用一个索引时,请记住创建索引会锁表并阻塞写操作。对于大表,这可能意味着你的网站是停机几个小时。幸运的是,Postgres允许你CREATE INDEX CONCURRENTLY(并发创建索引),这会导致花更多的时间来建索引,但是不要求锁住写锁。正常的CREATE INDEX命令要求一个锁来锁住写操作,但允许读。最终,在之后一段时间,索引会变得碎片和未优化,如果在表中的行经常更新或删除就特别容易这样。在这种情况下,就可能需要执行一个REINDEX命令来平衡及优化你的索引了。然而,要谨慎重建索引会在父表中获得写锁。有个策略在在线的网站来实现相同的结果就是并发地在相同的表和列但有一个不同的名称的索引,然后,删除旧的索引并且重新命名新的索引。这个过程可能持续比较久,但不要求在在线的(活跃)表有任何长久执行的锁。当准备创建B树索引时Postgres提供了许多灵活性来优化你特定的使用情况,也有许多选项来管理你应用程序不断增长的数据库。这些建议应该会帮你保持你的数据库健康,以及你的查询非常爽快。

PostgreSQL中代替MySQL的内存表方法

今天在群里发现有人问PostgreSQL里,有没有类似MySQL的内存表(Memory引擎)。

其实在PostgreSQL里,就只有一种表:堆表。不像MySQL那种提供那么多种的表类型(这个是由引擎来决定的,比如InnoDB的是索引组织表等)。

替代方法

1) 使用RAM Disk。创建一个表空间到Ram Disk上,然后建表的时候指定表空间到该 Ram Disk即可。

2) 如果需要创建那种全局可见的临时表,建表时可以使用UNLOGGED选项来创建表。这种表性能比一般的快。

实战使用 Ram Disk 作为“临时表”

步骤1:创建一个Ram Disk

1
sudo mount tmpfs /home/postgres/ramdisk/ -t tmpfs

步骤2:更改拥有者为Postgres

1
sudo chown postgres:postgres /home/postgres/ -R

步骤3:创建Ram的表空间

1
create tablespace ramdis location '/home/postgres/ramdisk';

步骤4:创建一个表,并指定表空间到Ram Disk的表空间

1
2
3
test=# create table ram (id serial, name varchar(40), age int) TABLESPACE ramdis;
CREATE TABLE
test=#

注意

有人担心重启电脑后,Ram disk 的表空间不存在了,会不会影响PostgreSQL服务器的启动。这其实是不用担心的,本人测试过,关闭PostgreSQL,然后删除Ram Disk 的表空间目录,然后再启动PostgreSQL,完全是没问题的。

不过,创建在该表空间的表就不能操作了。典型的错误提示如下:

1
2
3
test=# select * from ram;
ERROR: could not open file "pg_tblspc/16718/PG_9.4_201409291/16389/16729": No such file or directory
test=#

删除该表就OK了。

集群中Session解决方案之Spring Session

什么是 Spring Session

主要功能如下:

  • 从任意的环境中访问一个会话 (例如. web, messaging infrastructure, 等等)
  • WEB 环境下
  • 供应商中立的情况下支持集群
  • 可插拔策略可以确定 session id
  • WebSocket 活跃的时候可以简单的保持 HttpSession

主要特性

  • API and implementations (i.e. Redis) for managing a user’s session
  • HttpSession
  • Clustered Sessions
  • Multiple Browser Sessions
  • RESTful APIs
  • 支持WebSocket

实现原理

因为规范里的 HttpSession 以及 HttpServletRequest 都是一种接口,所以可以通过实现该接口来处理我们自定义的逻辑。

Spring Session里的自定义实现HttpServletRequest的逻辑代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
public SessionRepositoryRequestWrapper(HttpServletRequest original) {
super(original);
}
public HttpSession getSession() {
return getSession(true);
}
public HttpSession getSession(boolean createNew) {
// create an HttpSession implementation from Spring Session
}
// ... other methods delegate to the original HttpServletRequest ...
}

HttpServletRequestWrapperHttpServletRequest实现的一种包装器,可通过继承它来实现自己的主要逻辑代码,而不用直接实现HttpServletRequest接口来写大部分复杂的代码,只需要关注自己想要覆盖逻辑代码即可。
在这里,因为我们想要实现的是Session共享机制,所以这里只加入了处理Session的业务逻辑即可。
Spring Session默认情况下只有两种Session策略。一种是使用Map来实现,一种是使用Redis来实现。

如果我们想要实现自定义的Session持久化机制,我们可以实现该接口,然后添加到Session策略实现方式即可。

包装完自定义的HttpServletRequest后,就可使用了。方法是通过 Filter 来将Spring包装的HttpServletRequest代替容器原有的即可。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class SessionRepositoryFilter implements Filter {
public doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
SessionRepositoryRequestWrapper customRequest =
new SessionRepositoryRequestWrapper(httpRequest);
chain.doFilter(customRequest, response, chain);
}
// ...
}

Maven

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<type>pom<type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>

Hello World(以Spring为基础,其他框架,也可以参考该思路作小修改即可)

web.xml 添加以下 Filter

1
2
3
4
5
6
7
8
9
10
11
12
13
<filter>
<filter-name>spring-session</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>springSession</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>spring-session</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

spring.xml 添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--这里添加的是Redis,因为使用的是Spring里自带的RedisSession策略 -->
<bean id="v2redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="10.0.0.40" p:port="6379" p:use-pool="true" p:database="8" />
<bean id="stringRedisSerializer"
class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean id="v2redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="v2redisConnectionFactory"
p:keySerializer-ref="stringRedisSerializer"
p:valueSerializer-ref="stringRedisSerializer"
p:hashKeySerializer-ref="stringRedisSerializer"
p:hashValueSerializer-ref="stringRedisSerializer" />
<!-- 这里的是为了下面的 Session策略过滤器提供构造函数传入的参数,因为Session过滤器要依赖该对象来构造,所以创建一个先 -->
<bean name="redisOperationsSessionRepository" class="org.springframework.session.data.redis.RedisOperationsSessionRepository">
<constructor-arg ref="v2redisConnectionFactory"></constructor-arg>
</bean>
<!-- 这个是Session策略过滤器,即将容器原有的Session持久化机制,代替为SpringRedis持久化Session机制。 -->
<!-- 注意,这个名字与 web.xml里的targetBean的下value是要一致的。 -->
<bean name="springSession" class="org.springframework.session.web.http.SessionRepositoryFilter">
<constructor-arg ref="redisOperationsSessionRepository"></constructor-arg>
</bean>

使用上跟普通使用 Session 的方式是一样的。

打完收工。

[翻译]PostgreSQL中的死锁

原文

在讨论死锁之前,让我们看一下锁的类型以及它们在PostgreSQL中的获取方法。

锁的类型:

  • 表级锁 以及
  • 行级锁

表级锁:

  • AcessShareLock(访问共享锁):通过在一张表或多张表一条 SELECT 语句检索数据时就会自动获取。这个模式会阻塞在同一张表下的 ALTER TABLEDROP TABLE 以及 VACUUM (AccessExclusiveLock,访问排他锁)操作。

  • RowShareLock (行共享锁):通过一条SELECT...FOR UPDATE子句自动获取。它会在同一张表上阻塞并发的ExclusiveLock(排他锁)以及AccessExclusiveLock(访问排他锁)。

  • RowExclusiveLock (行排他锁):通过UPDATEINSERT,或者 DELETE命令自动获取。它会在同一张表上阻塞ALTER TABLEDROP TABLE,VACUUMCREATE INDEX命令。(ShareLock[共享锁],ShareRowExclusiveLock[共享行排他锁],ExclusiveLock[排他锁],AccessExclusiveLock[访问排他锁])。

  • ShareLock(共享锁): 通过CREATE INDEX命令自动获取。它会在同一张表上阻塞INSERT, UPDATE, DELETE, ALTER TABLE, DROP TABLE, 以及 VACUUM命令.(RowExclusiveLock[行排他锁], ShareRowExclusiveLock[共享行排他锁], ExclusiveLock[排他锁], 以及 AccessExclusiveLock[访问排他锁])

  • ShareRowExclusiveLock(共享行排他锁):这个锁模式与ExclusiveLock(排他锁)是一样的,但是它允许获取并发的 RowShareLock(行共享锁)

  • ExclusiveLock(排他锁):”每个事务在它的事务ID的整个时间里都会持有一个Exclusive Lock(排他锁)”。如果一个事务发现它需要特别地等待另一个事务,它就会尝试地在另一个事务ID上获取 Share Lock(共享锁)。这仅当另一个事务结束并释放它自己的锁时才会成功。(注意,冲突)。Exclusive Lock(排他锁)会在同一张表上阻塞INSERT, UPDATE, DELETE,CREATE INDEX, ALTER TABLE,DROP TABLE, SELECT...FOR UPDATE 以及 VACUUM命令。

  • AccessExclusiveLock(访问排他锁):通过ALTER TABLE, DROP TABLE, 或者 VACUUM命令来修改表时自动获取。从开始在表上获取锁时,会阻塞任何并发命令或者其他锁模式。

行级锁:

行级锁有两种类型:共享锁和排他锁。不要混淆锁的命名,你可以通过在视图pg_locks中的列lock_type来区分是表级锁,还是行级锁。

  • Exclusive lock(排他锁):当通过UPDATEDELETE命中行时就会自动获取该锁。锁会一直被持有,直到一个事务提交或回滚了。为了手动获取exclusive-lock(排他锁),可以使用SELECT FOR UPDATE

  • Share-Lock(共享锁):当通过SELECT...FOR SHARE命中行时就会自动获取该锁。

注意:在这两种情况下的行级锁,数据检索是一点也不会影响的。行级锁会阻塞“写”(即,“写”会阻塞“写”)。

死锁:

现在到死锁了,你已经知道锁的模式以及获取这些锁的方法,有些情况下事务会陷入死锁中。我相信应用程序设计是导致死锁的罪魁祸首。死锁大多数是由于ExclusiveLock(排他锁)例如UPDATEDELETE操作导致的。

什么是死锁?

进程A持有对象X的锁,并且正等待对象Y的锁。进程B持有对象Y的锁,并且正等待对象X的锁。在这时,这两个进程就会进入所谓的“死锁”,每个进程都想获取另一个进程持有的锁。在这个状态下,他们都在永远等待对方释放锁。他们之一必须放弃并释放自己拥有的锁。现在,死锁检测器就会检测到并且允许一个进程成功提交事务,而另一个则进行回滚。

为了解决死锁,要用这样一个方法来设计应用程序——任何事务“UPDATE”或“DELETE”都应该成功地取得表的所有权。通过SHARE UPDATE EXCLUSIVE 模式或者SELECT...FOR UPDATE,又或者ACCESS EXCLUSIVE 模式来锁表并且完成事务。在这个模型下,死锁检测器永远不会抛出它已经检测到一个EXCLUSIVE LOCK(排他锁)。

你可通过这个办法来解决上面的图的情况,你会看到死锁检测器永远不会抛出错误。

锁查询语句

1
\set locks 'SELECT w.locktype AS waiting_locktype,w.relation::regclass AS waiting_table,w.transactionid, substr(w_stm.current_query,1,20) AS waiting_query,w.mode AS waiting_mode,w.pid AS waiting_pid,other.locktype AS other_locktype,other.relation::regclass AS other_table,other_stm.current_query AS other_query,other.mode AS other_mode,other.pid AS other_pid,other.granted AS other_granted FROM pg_catalog.pg_locks AS w JOIN pg_catalog.pg_stat_activity AS w_stm ON (w_stm.procpid = w.pid) JOIN pg_catalog.pg_locks AS other ON ((w.\"database\" = other.\"database\" AND w.relation = other.relation) OR w.transactionid = other.transactionid) JOIN pg_catalog.pg_stat_activity AS other_stm ON (other_stm.procpid = other.pid) WHERE NOT w.granted AND w.pid <> other.pid;;'

关于锁的信息链接

http://www.postgresql.org/docs/9.0/static/sql-lock.html
http://developer.postgresql.org/pgdocs/postgres/explicit-locking.html

希望你有一个关于PostgreSQL锁的概念了。 希望在之后的博文中可以再次见到你。:)

—Raghav

By Raghavendra


我附:
现在的PG有8种表级锁了,除了上面作者提到的7种,还有一种是:

  • SHARE UPDATE EXCLUSIVE 锁

与”Share update exclusive,Share,Share row ,exclusive,exclusive,Access exclusive”模式冲突,这种模式保护一张表不被并发的模式更改和VACUUM;

“Vacuum(without full), Analyze ”和 “Create index concurrently”命令会获得这种类型锁。

资料:

http://www.postgresql.org/docs/9.0/static/explicit-locking.html