【转】不可不知的Python模块: collections

Python作为一个“内置电池”的编程语言,标准库里面拥有非常多好用的模块。比如今天想给大家 介绍的 collections 就是一个非常好的例子。

基本介绍

我们都知道,Python拥有一些内置的数据类型,比如str, int, list, tuple, dict等, collections模块在这些内置数据类型的基础上,提供了几个额外的数据类型:

  • namedtuple(): 生成可以使用名字来访问元素内容的tuple子类
  • deque: 双端队列,可以快速的从另外一侧追加和推出对象
  • Counter: 计数器,主要用来计数
  • OrderedDict: 有序字典
  • defaultdict: 带有默认值的字典

namedtuple()

namedtuple主要用来产生可以使用名称来访问元素的数据对象,通常用来增强代码的可读性, 在访问一些tuple类型的数据时尤其好用。

举个栗子

# -*- coding: utf-8 -*-
"""
比如我们用户拥有一个这样的数据结构,每一个对象是拥有三个元素的tuple。
使用namedtuple方法就可以方便的通过tuple来生成可读性更高也更好用的数据结构。
"""
from collections import namedtuple

websites = [
    ('Sohu', 'http://www.google.com/', u'张朝阳'),
    ('Sina', 'http://www.sina.com.cn/', u'王志东'),
    ('163', 'http://www.163.com/', u'丁磊')
]

Website = namedtuple('Website', ['name', 'url', 'founder'])

for website in websites:
    website = Website._make(website)
    print website


# Result:
Website(name='Sohu', url='http://www.google.com/', founder=u'\u5f20\u671d\u9633')
Website(name='Sina', url='http://www.sina.com.cn/', founder=u'\u738b\u5fd7\u4e1c')
Website(name='163', url='http://www.163.com/', founder=u'\u4e01\u78ca')

deque

deque其实是 double-ended queue 的缩写,翻译过来就是双端队列,它最大的好处就是实现了从队列 头部快速增加和取出对象: .popleft().appendleft() 。

你可能会说,原生的list也可以从头部添加和取出对象啊?就像这样:

l.insert(0, v)
l.pop(0)

但是值得注意的是,list对象的这两种用法的时间复杂度是 O(n) ,也就是说随着元素数量的增加耗时呈 线性上升。而使用deque对象则是 O(1) 的复杂度,所以当你的代码有这样的需求的时候, 一定要记得使用deque。

作为一个双端队列,deque还提供了一些其他的好用方法,比如 rotate 等。

举个栗子

# -*- coding: utf-8 -*-
"""
下面这个是一个有趣的例子,主要使用了deque的rotate方法来实现了一个无限循环
的加载动画
"""
import sys
import time
from collections import deque

fancy_loading = deque('>--------------------')

while True:
    print '\r%s' % ''.join(fancy_loading),
    fancy_loading.rotate(1)
    sys.stdout.flush()
    time.sleep(0.08)

# Result:

# 一个无尽循环的跑马灯
------------->-------

Counter

计数器是一个非常常用的功能需求,collections也贴心的为你提供了这个功能。

举个栗子

# -*- coding: utf-8 -*-
"""
下面这个例子就是使用Counter模块统计一段句子里面所有字符出现次数
"""
from collections import Counter

s = '''A Counter is a dict subclass for counting hashable objects. It is an unordered collection where elements are stored as dictionary keys and their counts are stored as dictionary values. Counts are allowed to be any integer value including zero or negative counts. The Counter class is similar to bags or multisets in other languages.'''.lower()

c = Counter(s)
# 获取出现频率最高的5个字符
print c.most_common(5)


# Result:
[(' ', 54), ('e', 32), ('s', 25), ('a', 24), ('t', 24)]

OrderedDict

在Python中,dict这个数据结构由于hash的特性,是无序的,这在有的时候会给我们带来一些麻烦, 幸运的是,collections模块为我们提供了OrderedDict,当你要获得一个有序的字典对象时,用它就对了。

举个栗子

# -*- coding: utf-8 -*-
from collections import OrderedDict

items = (
    ('A', 1),
    ('B', 2),
    ('C', 3)
)

regular_dict = dict(items)
ordered_dict = OrderedDict(items)

print 'Regular Dict:'
for k, v in regular_dict.items():
    print k, v

print 'Ordered Dict:'
for k, v in ordered_dict.items():
    print k, v


# Result:
Regular Dict:
A 1
C 3
B 2
Ordered Dict:
A 1
B 2
C 3

defaultdict

我们都知道,在使用Python原生的数据结构dict的时候,如果用 d[key] 这样的方式访问, 当指定的key不存在时,是会抛出KeyError异常的。

但是,如果使用defaultdict,只要你传入一个默认的工厂方法,那么请求一个不存在的key时, 便会调用这个工厂方法使用其结果来作为这个key的默认值。

# -*- coding: utf-8 -*-
from collections import defaultdict

members = [
    # Age, name
    ['male', 'John'],
    ['male', 'Jack'],
    ['female', 'Lily'],
    ['male', 'Pony'],
    ['female', 'Lucy'],
]

result = defaultdict(list)
for sex, name in members:
    result[sex].append(name)

print result

# Result:
defaultdict(<type 'list'>, {'male': ['John', 'Jack', 'Pony'], 'female': ['Lily', 'Lucy']})

每日一记:(2016-12-02)Python:ContextManager(上下文管理器)

今天的日志就来说说python的一个优雅的语法糖——ContextManager(上下文管理器)

1. 那么什么叫ContextManager

上下文管理器的任务是 – 代码块执行前准备,代码块执行后收拾。其中代码块执行后包括了正常退出和产生异常。
当然这些功能可以用try-except语句实现,但是如果使用try-except代码就显得不那么优雅。而且经常会忘记会产生异常这种情况。

2. 如何使用上下文管理器?

先给个例子吧!
>>> with open('test.txt', 'w') as writer:
...     writer.write("aaaa")
...     writer.write('bbbb')
其实就是with语句啦,使用的时候也是很简单的说。当然也可以没有as之后的内容单独一个with open()也是可以的。

3. 自定义上下文管理器

要实现上下文管理器,必须实现两个方法 – 一个负责进入语句块的准备操作,另一个负责离开语句块的善后操作。
要实现上下文管理器,需要实现两个魔法方法__enter__以及__exit__(双下划线作为前缀及后缀)。

当一个对象被用作上下文管理器时:

__enter__ 方法将在进入代码块前被调用。

__exit__ 方法则在离开代码块之后被调用(即使在代码块中遇到了异常)。

下面是上下文管理器的一个例子,我们通过例子看一下__enter__、__exit__都是什么时候被调用的吧

>>> class PypixContextManagerDemo:
...     def __enter__(self):
...             print 'Entering the block'
...     def __exit__(self, *unseted):
...             print 'Exiting the block'
...
>>> with PypixContextManagerDemo():
...     print 'In the block'
...
  Entering the block
  In the block
  Exiting the block
这里有一个疑问:这上面都是对于类来说实现了上下文管理器,但是对于open(处理文件的函数)他就是一个函数,为啥也能用上下文管理器呢?
上面的例子还有几点不完善的地方:
  • 没有传递任何参数。
  • 在此没有使用“as”关键词。
  • 稍后我们将讨论__exit__方法的参数设置。
...     def __init__(self, filename, mode):
...             self.filename = filename
...             self.mode = mode
...     def __enter__(self):
...             self.openedFile = open(self.filename, self.mode)
...             return self.openedFile
...     def __exit__(self, *unseted):
...             self.openedFile.close()
...
>>> with PypixOpen(filename, mode) as writer:
...     writer.write("Hello world from our new Context Manager")
那么,上面解决了输入参数和as之后赋值问题,还有一个异常处理的问题,还没有解决呢?

如何处理异常

如果语句块内部发生了异常,__exit__方法将被调用,而异常将会被重新抛出(re-raised)。当处理文件写入操作时,大部分时间你肯定不希望隐藏这些异常,所以这是可以的。而对于不希望重新抛出的异常,我们可以让__exit__方法简单的返回True来忽略语句块中发生的所有异常(大部分情况下这都不是明智之举)。
完备的__exit__函数签名应该是这样的:
def __exit__(self,exc_type,exc_val,exc_tb)
__exit__方法中给的参数有exc_type, exc_val, exc_tb,那么就可以进行异常处理了。(exc_type是异常类型,exc_val是异常的值和exc_tb是异常的回溯信息)
调用上下文管理器的 __exit__() 方法,如果 with结构中产生异常,那么该异常的 type、value 和 traceback 会作为参数传给 __exit__(),否则传三个 None

  • 如果 with_suite 产生异常,并且__exit__() 的返回值等于 False,那么这个异常将被重新抛出到上层
  • 如果 with_suite 产生异常,并且__exit__() 的返回值等于 True,那么这个异常就被忽略,继续执行后面的代码
通过上面的内容我们可以自己进行构造__exit__()函数了。
例:
>>> def __exit__(self,exc_type,exc_val,exc_tb):
...     self.file.close()
...     if exc_type:
...             print exc_type, exc_val, exc_tb
...             return False
...     return True

4. contextmanager装饰器

@contextmanager

contextlib模块的contextmanager装饰器可以更方便的实现上下文管理器。

任何能够被yield关键词分割成两部分的函数,都能够通过装饰器装饰的上下文管理器来实现。任何在yield之前的内容都可以看做在代码块执行前的操作,而任何yield之后的操作都可以放在exit函数中。

from contextlib import contextmanager
@contextmanager
def listTrans(alist):
    thecopy=list(alist)
    yield thecopy
    alist[:]=thecopy
alist=[]
with listTrans(alist) as working:
    working.append(1)
    working.append(2)
print alist
 

yield返回的值相当于__enter__的返回值。

要注意的是,这不是异常安全的写法,也就是说,当出现异常时,yield后的语句是不会执行的,想要异常安全,可用try捕捉异常:

from contextlib import contextmanager
@contextmanager
def listTrans(alist):
    thecopy=list(alist)
    try:
        yield thecopy
    except RuntimeError:
        pass
    alist[:]=thecopy
alist=[]
with listTrans(alist) as working:
    working.append(1)
    working.append(2)
    raise RuntimeError
nested与closing

contextlib模块还有两个好玩的方法:nested,closing。

nested:用来更方便的减少嵌套写法:

当要嵌套的写上下文管理器时:

with open('toReadFile', 'r') as reader: 
    with open('toWriteFile', 'w') as writer: 
        writer.writer(reader.read())

python2.7后nested就过时了:

with open('fileToRead.txt', 'r') as reader,open('fileToWrite.txt', 'w') as writer: 
        writer.write(reader.read())


closing(object):创建上下文管理器,在执行过程离开with语句时自动执行object.close():

class Door(object) :
    def open(self) :
        print 'Door is opened'
    def close(self) :
        print 'Door is closed'
with contextlib.closing(Door()) as door :
    door.open()

5. 其他

Python定义了几个上下文管理器来支持简单的线程同步,提示文件或其他对象的关闭,以及更简单地操作主动小数算术上下文。

每日一记:(2016-12-01)Python特殊语法:filter、map、reduce、lambda

Python特殊语法:filter、map、reduce、lambda
filter(function, sequence)
对sequence中的item依次执行function(item),将执行结果为True的item组成一个List/String/Tuple(取决于sequence的类型)返回
例:
>>> def f(x):return x%2 != 0 and x%3 != 0
>>> filter(f, range(2, 25))
  [5, 7, 11, 13, 17, 19, 23]

>>> def f(x):return x != 'a'
>>> filter(f, 'abcdef')
  'bcdef'
map(function, sequence) :
对sequence中的item依次执行function(item),见执行结果组成一个List返回
例:
>>> def cube(x): return x*x*x
>>> map(cube, range(1,11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>> def cube(x):return x+x
>>> map(cube, 'abcde')
  ['aa', 'bb', 'cc', 'dd', 'ee']
另外map也支持多个sequence,这就要求function也支持相应数量的参数输入:
例:
>>> def add(x, y):return x+y
>>> map(add, range(8), range(8))
  [0, 2, 4, 6, 8, 10, 12, 14]
reduce(function, sequence, starting_value)
对sequence中的item顺序迭代调用function,如果有starting_value,还可以作为初始值调用。注意reduce只接受两个参数的函数,因为他要前后迭代。
例:
可以用来对List求和:
>>> def add(x, y):return x+y
>>> reduce(add, range(1,11))
  55 (注:1+2+3+4+5+6+7+8+9+10)
>>> reduce(add, range(1,11), 20)
  75 (注:1+2+3+4+5+6+7+8+9+10+20)
lambda
python的官方文档是这么定义的:
lambda [arguments]: expression

等价于

def name(arguments):return expression

 

1 python lambda会创建一个函数对象,但不会把这个函数对象赋给一个标识符,而def则会把函数对象赋值给一个变量。

2 python lambda它只是一个表达式,而def则是一个语句。

 
如果你在列表推导式里用到python lambda,我感觉意义不是很大,因为python lambda它会创建一个函数对象,但马上又给丢弃了,因为你没有使用它的返回值,即那个函数对象。
也正是由于lambda只是一个表达式,它可以直接作为python 列表或python 字典的成员,比如:

>>> info = [lambda a: a**3, lambda b: b**3]
>>> info
  [<function <lambda> at 0x01C79AB0>, <function <lambda> at 0x01C79A70>]
在这个地方没有办法用def语句直接代替。因为def是语句,不是表达式不能嵌套在里面。
像if或for或print这种语句就不能用于lambda中,lambda一般只用来定义简单的函数。
下面举几个python lambda的例子吧
单个参数的:
>>> g = lambda x:x*2
>>> print g(3)
  6

多个参数的:
>>> m = lambda x,y,z:(x-y)*z
>>> print m(3,1,2)
  4
 

lambda只是一个表达式,函数体比def简单很多。

lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。

lambda表达式是起到一个函数速写的作用。允许在代码内嵌入一个函数的定义。

>>> f = lambda x,y,z: x+y+x
>>> f(1,2,3)
  4
 
>>> f = lambda x,y,z: x+y+z
>>> f(1,2,3)
  6
lambda表达式也可以用在def函数中。
>>> def action(x):
...     return lambda y:x+y
>>> a = action(2)
>>> a(22)
  24
利用lambda实现了简单的闭包。

这里定义了一个action函数,返回了一个lambda表达式。其中lambda表达式获取到了上层def作用域的变量名x的值。

a是action函数的返回值,a(22),即是调用了action返回的lambda表达式。

这里也可以把def直接写成lambda形式。如下

>>> b = lambda x: lambda y: x+y
>>> a = b(3)
>>> a(2)
  5
>>> (b(2))(2)
  4
我们也可以把filter map reduce 和lambda结合起来用,函数就可以简单的写成一行。
例如:
>>> l = 'learning:x:503:503::/home/learning:/bin/bash'
>>> map(lambda kmpath:str.strip(kmpath), str.split(l, ':'))
['learning', 'x', '503', '503', '', '/home/learning', '/bin/bash']

>>> filter(lambda x:x!='', map(lambda kmpath:str.strip(kmpath), str.split(l, ':')))
['learning', 'x', '503', '503', '/home/learning', '/bin/bash']

>>> filter(lambda x:x, map(lambda kmpath:str.strip(kmpath), str.split(l, ':')))
['learning', 'x', '503', '503', '/home/learning', '/bin/bash']
map,reduce,filter中的function都可以用lambda表达式来生成!map(function,sequence)把sequence中的值当参数逐个传给function,返回一个包含函数执行结果的list。

每天进步一点点之Python 魔术方法指南【转】

转自http://blog.csdn.net/GarfieldEr007/article/details/51321882

  • 入门

  • 构造和初始化

  • 构造定制类
    • 用于比较的魔术方法
    • 用于数值处理的魔术方法
  • 表现你的类

  • 控制属性访问

  • 创建定制序列

  • 反射

  • 可以调用的对象

  • 会话管理器

  • 创建描述器对象

  • 持久化对象

  • 总结

  • 附录

介绍

此教程为我的数篇文章中的一个重点。主题是魔术方法。 什么是魔术方法?他们是面向对象的Python的一切。他们是可以给你的类增加”magic”的特殊方法。他们总是被双下划线所包围(e.g. __init__ 或者 __lt__)。然而他们的文档却远没有提供应该有的内容。Python中所有的魔术方法均在Python官方文档中有相应描述,但是对于他们的描述比较混乱而且组织比较松散。很难找到有一个例子(也许他们原本打算的很好,在开始语言参考中有描述很详细,然而随之而来的确是枯燥的语法描述等等)。

所以,为了修补我认为Python文档应该修补的瑕疵,我决定给Python中的魔术方法提供一些用平淡的语言和实例驱使的文档。我在开始已经写了数篇博文,现在在这篇文章中对他们进行总结。

我希望你能够喜欢这篇文章。你可以将之当做一个教程,一个补习资料,或者一个参考。本文章的目的仅仅是为Python中的魔术方法提供一个友好的教程。

构造和初始化

每个人都知道一个最基本的魔术方法, __init__ 。通过此方法我们可以定义一个对象的初始操作。然而,当我调用 x = SomeClass() 的时候, __init__并不是第一个被调用的方法。实际上,还有一个叫做 __new__ 的方法,来构造这个实例。然后给在开始创建时候的初始化函数来传递参数。在对象生命周期的另一端,也有一个 __del__ 方法。我们现在来近距离的看一看这三个方法:

__new__(cls, [...) __new__ 是在一个对象实例化的时候所调用的第一个方法。它的第一个参数是这个类,其他的参数是用来直接传递给 __init__ 方法。__new__ 方法相当不常用,但是它有自己的特性,特别是当继承一个不可变的类型比如一个tuple或者string。我不希望在 __new__ 上有太多细节,因为并不是很有用处,但是在 Python文档 中有详细的阐述。

__init__(self, […) 此方法为类的初始化方法。当构造函数被调用的时候的任何参数都将会传给它。(比如如果我们调用 x = SomeClass(10, 'foo')),那么 __init__ 将会得到两个参数10和foo。 __init__ 在Python的类定义中被广泛用到。

__del__(self) 如果 __new__ 和 __init__ 是对象的构造器的话,那么 __del__ 就是析构器。它不实现语句 del x (以上代码将不会翻译为 x.__del__())。它定义的是当一个对象进行垃圾回收时候的行为。当一个对象在删除的时需要更多的清洁工作的时候此方法会很有用,比如套接字对象或者是文件对象。注意,如果解释器退出的时候对象还存存在,就不能保证 __del__ 能够被执行,所以 __del__ can’t serve as a replacement for good coding practices ()~~~~~~~

放在一起的话,这里是一个 __init__ 和 __del__ 实际使用的例子。

from os.path import join

class FileObject:
    '''给文件对象进行包装从而确认在删除时文件流关闭'''

    def __init__(self, filepath='~', filename='sample.txt'):
        #读写模式打开一个文件
        self.file = open(join(filepath, filename), 'r+')

    def __del__(self):
        self.file.close()
        del self.file

让定制的类工作起来

使用Python的魔术方法的最大优势在于他们提供了一种简单的方法来让对象可以表现的像内置类型一样。那意味着你可以避免丑陋的,违反直觉的,不标准的的操作方法。在一些语言中,有一些操作很常用比如:

if instance.equals(other_instance):
    # do something

在Python中你可以这样。但是这会让人迷惑且产生不必要的冗余。相同的操作因为不同的库会使用不同的名字,这样会产生不必要的工作。然而有了魔术方法的力量,我们可以定义一个方法(本例中为 __eq__ ),就说明了我们的意思:

if instance == other_instance:
        #do something

这只是魔术方法的功能的一小部分。它让你可以定义符号的含义所以我们可以在我们的类中使用。就像内置类型一样。

用于比较的魔术方法

Python对实现对象的比较,使用魔术方法进行了大的逆转,使他们非常直观而不是笨拙的方法调用。而且还提供了一种方法可以重写Python对对象比较的默认行为(通过引用)。以下是这些方法和他们的作用。

__cmp__(self, other) __cmp__ 是最基本的用于比较的魔术方法。它实际上实现了所有的比较符号(<,==,!=,etc.),但是它的表现并不会总是如你所愿(比如,当一个实例与另一个实例相等是通过一个规则来判断,而一个实例大于另外一个实例是通过另外一个规则来判断)。如果 self < other 的话 __cmp__应该返回一个负数,当 self == other 的时候会返回0 ,而当 self > other 的时候会返回正数。通常最好的一种方式是去分别定义每一个比较符号而不是一次性将他们都定义。但是 __cmp__ 方法是你想要实现所有的比较符号而一个保持清楚明白的一个好的方法。

__eq__(self, other) 定义了等号的行为, == 。

__ne__(self, other) 定义了不等号的行为, != 。

__lt__(self, other) 定义了小于号的行为, < 。

__gt__(self, other) 定义了大于等于号的行为, >= 。

举一个例子,创建一个类来表现一个词语。我们也许会想要比较单词的字典序(通过字母表),通过默认的字符串比较的方法就可以实现,但是我们也想要通过一些其他的标准来实现,比如单词长度或者音节数量。在这个例子中,我们来比较长度实现。以下是实现代码:

class Word(str):
'''存储单词的类,定义比较单词的几种方法'''

    def __new__(cls, word):
        # 注意我们必须要用到__new__方法,因为str是不可变类型
        # 所以我们必须在创建的时候将它初始化
        if ' ' in word:
            print "Value contains spaces. Truncating to first space."
            word = word[:word.index(' ')] #单词是第一个空格之前的所有字符
        return str.__new__(cls, word)

    def __gt__(self, other):
        return len(self) > len(other)
    def __lt__(self, other):
        return len(self) < len(other)
    def __ge__(self, other):
        return len(self) >= len(other)
    def __le__(self, other):
        return len(self) <= len(other)

现在,我们创建两个 Words 对象(通过使用 Word('foo') 和 Word('bar') 然后通过长度来比较它们。注意,我们没有定义 __eq__ 和 __ne__ 方法。这是因为将会产生一些怪异的结果(比如 Word('foo') == Word('bar') 将会返回true)。这对于测试基于长度的比较不是很有意义。所以我们退回去,用 str内置来进行比较。

现在你知道你不必定义每一个比较的魔术方法从而进行丰富的比较。标准库中很友好的在 functiontols 中提供给我们一个类的装饰器定义了所有的丰富的比较函数。如果你只是定义 __eq__ 和另外一个(e.g. __gt____lt__,etc.)这个特性仅仅在Python 2.7中存在,但是你如果有机会碰到的话,那么将会节省大量的时间和工作量。你可以通过在你定义的类前放置 @total_ordering 来使用。

数值处理的魔术方法

如同你在通过比较符来比较类的实例的时候来创建很多方法,你也可以定义一些数值符号的特性。系紧你的安全带,来吧,这里有很多内容。为了组织方便,我将会把数值处理的方法来分成五类:一元操作符,普通算数操作符,反射算数操作符(之后会详细说明),增量赋值,和类型转换。

一元操作符和函数

仅仅有一个操作位的一元操作符和函数。比如绝对值,负等。

__pos__(self) 实现正号的特性(比如 +some_object)

__neg__(self) 实现负号的特性(比如 -some_object)

__abs__(self) 实现内置 abs() 函数的特性。

__invert__(self) 实现 ~ 符号的特性。为了说明这个特性。你可以查看 Wikipedia中的这篇文章

普通算数操作符

现在我们仅仅覆盖了普通的二进制操作符:+,-,*和类似符号。这些符号大部分来说都浅显易懂。

__add__(self, other) 实现加法。 __sub__(self, other) 实现减法。 __mul__(self, other) 实现乘法。 __floordiv__(self, other) 实现 // 符号实现的整数除法。 __div__(self, other) 实现 / 符号实现的除法。 __truediv__(self, other) 实现真除法。注意只有只用了 from __future__ import division 的时候才会起作用。 __mod__(self, other) 实现取模算法 % __divmod___(self, other) 实现内置 divmod() 算法 __pow__ 实现使用 ** 的指数运算__lshift__(self, other) 实现使用 << 的按位左移动 __rshift__(self, other) 实现使用 >> 的按位左移动 __and__(self, other) 实现使用 & 的按位与__or__(self, other) 实现使用 | 的按位或 __xor__(self, other) 实现使用 ^ 的按位异或

反运算

下面我将会讲解一些反运算的知识。有些概念你可能会认为恐慌或者是陌生。但是实际上非常简单。以下是一个例子:

some_object + other

这是一个普通的加法运算,反运算是相同的,只是把操作数调换了位置:

other + some_object

所以,除了当与其他对象操作的时候自己会成为第二个操作数之外,所有的这些魔术方法都与普通的操作是相同的。大多数情况下,反运算的结果是与普通运算相同的。所以你可以你可以将 __radd__ 与 __add__ 等价。

__radd__(self, other) 实现反加 __rsub__(self, other) 实现反减 __rmul__(self, other) 实现反乘 __rfloordiv__(self, other) 实现 // 符号的反除__rdiv__(self, other) 实现 / 符号的反除 __rtruediv__(self, other) 实现反真除,只有当 from __future__ import division 的时候会起作用__rmod__(self, other) 实现 % 符号的反取模运算 __rdivmod__(self, other) 当 divmod(other, self) 被调用时,实现内置 divmod() 的反运算 __rpow__ 实现 ** 符号的反运算 __rlshift__(self, other) 实现 << 符号的反左位移 __rrshift__(self, other) 实现 >> 符号的反右位移 __rand__(self, other) 实现& 符号的反与运算 __ror__(self, other) 实现 | 符号的反或运算 __xor__(self, other) 实现 ^ 符号的反异或运算

增量赋值

Python也有大量的魔术方法可以来定制增量赋值语句。你也许对增量赋值已经很熟悉,它将操作符与赋值来结合起来。如果你仍然不清楚我在说什么的话,这里有一个例子:

x = 5
x += 1 # in other words x = x + 1

__iadd__(self, other) 实现赋值加法 __isub__(self, other) 实现赋值减法 __imul__(self, other) 实现赋值乘法 __ifloordiv__(self, other) 实现 //= 的赋值地板除 __idiv__(self, other) 实现符号 /= 的赋值除 __itruediv__(self, other) 实现赋值真除,只有使用 from __future__ import division 的时候才能使用 __imod_(self, other) 实现符号 %= 的赋值取模 __ipow__ 实现符号 **= 的赋值幂运算 __ilshift__(self, other) 实现符号 <<= 的赋值位左移__irshift__(self, other) 实现符号 >>= 的赋值位右移 __iand__(self, other) 实现符号 &= 的赋值位与 __ior__(self, other) 实现符号 |= 的赋值位或__ixor__(self, other) 实现符号 |= 的赋值位异或

类型转换魔术方法

Python也有很多的魔术方法来实现类似 float() 的内置类型转换特性。 __int__(self) 实现整形的强制转换 __long__(self) 实现长整形的强制转换__float__(self) 实现浮点型的强制转换 __complex__(self) 实现复数的强制转换 __oct__(self) 实现八进制的强制转换 __hex__(self) 实现二进制的强制转换 __index__(self) 当对象是被应用在切片表达式中时,实现整形强制转换,如果你定义了一个可能在切片时用到的定制的数值型,你应该定义__index__ (详见PEP357) __trunc__(self) 当使用 math.trunc(self) 的时候被调用。 __trunc__ 应该返回数值被截取成整形(通常为长整形)的值__coerce__(self, other) 实现混合模式算数。如果类型转换不可能的话,那么 __coerce__ 将会返回 None ,否则他将对 self 和 other 返回一个长度为2的tuple,两个为相同的类型。

表现你的类

如果有一个字符串来表示一个类将会非常有用。在Python中,有很多方法可以实现类定义内置的一些函数的返回值。 __str__(self) 定义当 str() 调用的时候的返回值 __repr__(self) 定义 repr() 被调用的时候的返回值。 str() 和 repr() 的主要区别在于 repr() 返回的是机器可读的输出,而 str() 返回的是人类可读的。 __unicode__(self) 定义当 unicode() 调用的时候的返回值。 unicode() 和 str() 很相似,但是返回的是unicode字符串。注意,如a果对你的类调用 str() 然而你只定义了 __unicode__() ,那么将不会工作。你应该定义 __str__() 来确保调用时能返回正确的值。

__hash__(self) 定义当 hash() 调用的时候的返回值,它返回一个整形,用来在字典中进行快速比较 __nonzero__(self) 定义当 bool() 调用的时候的返回值。本方法应该返回True或者False,取决于你想让它返回的值。

控制属性访问

许多从其他语言转到Python的人会抱怨它缺乏类的真正封装。(没有办法定义私有变量,然后定义公共的getter和setter)。Python其实可以通过魔术方法来完成封装。我们来看一下:

__getattr__(self, name) 你可以定义当用户试图获取一个不存在的属性时的行为。这适用于对普通拼写错误的获取和重定向,对获取一些不建议的属性时候给出警告(如果你愿意你也可以计算并且给出一个值)或者处理一个 AttributeError 。只有当调用不存在的属性的时候会被返回。然而,这不是一个封装的解决方案。 __setattr__(self, name, value) 与 __getattr__ 不同, __setattr__ 是一个封装的解决方案。无论属性是否存在,它都允许你定义对对属性的赋值行为,以为这你可以对属性的值进行个性定制。但是你必须对使用 __setattr__ 特别小心。之后我们会详细阐述。 __delattr__ 与__setattr__ 相同,但是功能是删除一个属性而不是设置他们。注意与 __setattr__ 相同,防止无限递归现象发生。(在实现 __delattr__ 的时候调用 delself.name 即会发生) __getattribute__(self, name) __getattribute__ 与它的同伴 __setattr__ 和 __delattr__ 配合非常好。但是我不建议使用它。只有在新类型类定义中才能使用 __getattribute__ (在最新版本Python中所有的类都是新类型,在老版本中你可以通过继承 object 来制作一个新类。这样你可以定义一个属性值的访问规则。有时也会产生一些帝归现象。(这时候你可以调用基类的 __getattribute__ 方法来防止此现象的发生。)它可以消除对__getattr__ 的使用,如果它被明确调用或者一个 AttributeError 被抛出,那么当实现 __getattribute__ 之后才能被调用。此方法是否被使用其实最终取决于你的选择。)我不建议使用它因为它的使用几率较小(我们在取得一个值而不是设置一个值的时候有特殊的行为是非常罕见的。)而且它不能避免会出现bug。

在进行属性访问控制定义的时候你可能会很容易的引起一个错误。考虑下面的例子。

def __setattr__(self, name, value):
    self.name = value
    #每当属性被赋值的时候, ``__setattr__()`` 会被调用,这样就造成了递归调用。
    #这意味这会调用 ``self.__setattr__('name', value)`` ,每次方法会调用自己。这样会造成程序崩溃。

def __setattr__(self, name, value):
    self.__dict__[name] = value  #给类中的属性名分配值
    #定制特有属性

Python的魔术方法非常强大,然而随之而来的则是责任。了解正确的方法去使用非常重要。

所以我们对于定制属性访问权限了解了多少呢。它不应该被轻易的使用。实际上,它非常强大。但是它存在的原因是:Python 不会试图将一些不好的东西变得不可能,而是让它们难以实现。自由是至高无上的,所以你可以做任何你想做的。以下是一个特别的属性控制的例子(我们使用 super 因为不是所有的类都有 __dict__ 属性):

class AccessCounter:
    '''一个包含计数器的控制权限的类每当值被改变时计数器会加一'''

    def __init__(self, val):
        super(AccessCounter, self).__setattr__('counter', 0)
        super(AccessCounter, self).__setattr__('value', val)

    def __setattr__(self, name, value):
        if name == 'value':
            super(AccessCounter, self).__setattr__('counter', self.counter + 1)
    #如果你不想让其他属性被访问的话,那么可以抛出 AttributeError(name) 异常
        super(AccessCounter, self).__setattr__(name, value)

    def __delattr__(self, name):
        if name == 'value':
            super(AccessCounter, self).__setattr__('counter', self.counter + 1)
        super(AccessCounter, self).__delattr__(name)]

创建定制的序列

有很多方法让你的Python类行为可以像内置的序列(dict, tuple,list, string等等)。这是目前为止我最喜欢的魔术方法,因为它给你很搞的控制权限而且让很多函数在你的类实例上工作的很出色。但是在开始之前,需要先讲一些必须条件。

必须条件

现在我们开始讲如何在Python中创建定制的序列,这个时候该讲一讲协议。协议(Protocols)与其他语言中的接口很相似。它给你很多你必须定义的方法。然而在Python中的协议是很不正式的,不需要明确声明实现。事实上,他们更像一种指南。

我们为什么现在讨论协议?因为如果要定制容器类型的话需要用到这些协议。首先,实现不变容器的话有一个协议:实现不可变容器,你只能定义__len__ 和 __getitem__ (一会会讲更多)。可变容器协议则需要所有不可变容器的所有另外还需要 __setitem__ 和 __delitem__ 。最终,如果你希望你的对象是可迭代的话,你需要定义 __iter__ 会返回一个迭代器。迭代器必须遵循迭代器协议,需要有 __iter__ (返回它本身) 和 next 。

容器后的魔法

这些是容器使用的魔术方法。 __len__(self) 然会容器长度。对于可变不可变容器都需要有的协议的一部分。 __getitem__(self, key) 定义当一个条目被访问时,使用符号 self[key] 。这也是不可变容器和可变容器都要有的协议的一部分。如果键的类型错误和 KeyError 或者没有合适的值。那么应该抛出适当的 TypeError 异常。 __setitem__(self, key, value) 定义当一个条目被赋值时的行为,使用 self[key] = value 。这也是可变容器和不可变容器协议中都要有的一部分。 __delitem__(self, key) 定义当一个条目被删除时的行为(比如 del self[key])。这只是可变容器协议中的一部分。当使用一个无效的键时应该抛出适当的异常。 __iter__(self) 返回一个容器的迭代器。很多情况下会返回迭代器,尤其是当内置的 iter() 方法被调用的时候,或者当使用 for x in Container 方式循环的时候。迭代器是他们本身的对象,他们必须定义返回 self 的 __iter__ 方法。 __reversed__(self) 实现当reversed() 被调用时的行为。应该返回列表的反转版本。 __contains__(self, item) 当调用 in 和 not in 来测试成员是否存在时候 __contains__ 被定义。你问为什么这个不是序列协议的一部分?那是因为当 __contains__ 没有被定义的时候,Python会迭代这个序列并且当找到需要的值时会返回 True。 __concat__(self, other) 最终,你可以通过 __concat__ 来定义当用其他的来连接两个序列时候的行为。当 + 操作符被调用时候会返回一个 self 和other.__concat__ 被调用后的结果产生的新序列。

一个例子

在我们的例子中,让我们看一看你可能在其他语言中 用到的函数构造语句的实现(比如 Haskell)。

class FunctionalList:
'''一个封装了一些附加魔术方法比如 head, tail, init, last, drop, 和take的列表类。
'''

def __init__(self, values=None):
if values is None:
    self.values = []
else:
    self.values = values

def __len__(self):
    return len(self.values)

def __getitem__(self, key):
    #如果键的类型或者值无效,列表值将会抛出错误
    return self.values[key]

def __setitem__(self, key, value):
    self.values[key] = value

def __delitem__(self, key):
    del self.values[key]

def __iter__(self):
    return iter(self.values)

def __reversed__(self):
    return reversed(self.values)

def append(self, value):
    self.values.append(value)
def head(self):
    return self.values[0]
def tail(self):
    return self.values[1:]
def init(self):
    #返回一直到末尾的所有元素
    return self.values[:-1]
def last(self):
    #返回末尾元素
    return self.values[-1]
def drop(self, n):
    #返回除前n个外的所有元素
    return self.values[n:]
def take(self, n):
    #返回前n个元素
    return self.values[:n]

反射

你可以通过魔术方法控制控制使用 isinstance() 和 issubclass() 内置方法的反射行为。这些魔术方法是:

__instancecheck__(self, instance)

检查一个实例是不是你定义的类的实例

__subclasscheck__(self, subclass)

检查一个类是不是你定义的类的子类

这些方法的用例似乎很少,这也许是真的。我不会花更多的时间在这些魔术方法上因为他们并不是很重要,但是他们的确反应了Python 中的面向对象编程的一些基本特性:非常容易的去做一些事情,即使并不是很必须。这些魔术方法看起来并不是很有用,但是当你需要的时候你会很高兴有这种特性。

可以调用的对象

你也许已经知道,在Python中,方法也是一种高等的对象。这意味着他们也可以被传递到方法中就像其他对象一样。这是一个非常惊人的特性。 在Python中,一个特殊的魔术方法可以让类的实例的行为表现的像函数一样,你可以调用他们,将一个函数当做一个参数传到另外一个函数中等等。这是一个非常强大的特性让Python编程更加舒适甜美。 __call__(self, [args...])

允许一个类的实例像函数一样被调用。实质上说,这意味着 x() 与 x.__call__() 是相同的。注意 __call__ 参数可变。这意味着你可以定义 __call__为其他你想要的函数,无论有多少个参数。

__call__ 在那些类的实例经常改变状态的时候会非常有效。调用这个实例是一种改变这个对象状态的直接和优雅的做法。用一个实例来表达最好不过了:

class Entity:
'''调用实体来改变实体的位置。'''

def __init__(self, size, x, y):
    self.x, self.y = x, y
    self.size = size

def __call__(self, x, y):
    '''改变实体的位置'''
    self.x, self.y = x, y

会话管理

在Python 2.5中,为了代码利用定义了一个新的关键词 with 语句。会话控制在Python中不罕见(之前是作为库的一部分被实现),直到 PEP343 被添加后。它被成为一级语言结构。你也许之前看到这样的语句:

with open('foo.txt') as bar:
# perform some action with bar

回话控制器通过包装一个 with 语句来设置和清理行为。回话控制器的行为通过两个魔术方法来定义: __enter__(self) 定义当使用 with 语句的时候会话管理器应该初始块被创建的时候的行为。注意 __enter__ 的返回值被 with 语句的目标或者 as 后的名字绑定。 __exit__(self, exception_type,exception_value, traceback) 定义当一个代码块被执行或者终止后会话管理器应该做什么。它可以被用来处理异常,清除工作或者做一些代码块执行完毕之后的日常工作。如果代码块执行成功, exception_type , exception_value , 和 traceback 将会是 None 。否则的话你可以选择处理这个异常或者是直接交给用户处理。如果你想处理这个异常的话,确认 __exit__ 在所有结束之后会返回 True 。如果你想让异常被会话管理器处理的话,那么就这样处理。

__enter 和 __exit__ 对于明确有定义好的和日常行为的设置和清洁工作的类很有帮助。你也可以使用这些方法来创建一般的可以包装其他对象的会话管理器。以下是一个例子。

class Closer:
'''通过with语句和一个close方法来关闭一个对象的会话管理器'''

def __init__(self, obj):
    self.obj = obj

def __enter__(self):
    return self.obj # bound to target

def __exit__(self, exception_type, exception_val, trace):
    try:
        self.obj.close()
    except AttributeError: # obj isn't closable
        print 'Not closable.'
        return True # exception handled successfully

以下是一个使用 Closer 的例子,使用一个FTP链接来证明(一个可关闭的套接字):

>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
...     conn.dir()
...
>>> conn.dir()
>>> with Closer(int(5)) as i:
...     i += 1
...
Not closable.
>>> i
6

你已经看到了我们的包装器如何静默的处理适当和不适当的使用行为。这是会话管理器和魔术方法的强大功能。

创建对象的描述器

描述器是通过得到,设置,删除的时候被访问的类。当然也可以修改其他的对象。描述器并不是鼓励的,他们注定被一个所有者类所持有。当创建面向对象的数据库或者类,里面含有相互依赖的属性时,描述器将会非常有用。一种典型的使用方法是用不同的单位表示同一个数值,或者表示某个数据的附加属性(比如坐标系上某个点包含了这个点到远点的距离信息)。

为了构建一个描述器,一个类必须有至少 __get__ 或者 __set__ 其中一个,并且 __delete__ 被实现。让我们看看这些魔术方法。 __get__(self,instance, owner) 定义当描述器的值被取得的时候的行为, instance 是拥有者对象的一个实例。 owner 是拥有者类本身。 __set__(self, instance,value) 定义当描述器值被改变时候的行为。 instance 是拥有者类的一个实例 value 是要设置的值。 __delete__(self, instance) 定义当描述器的值被删除的行为。instance 是拥有者对象的实例。 以下是一个描述器的实例:单位转换。

class Meter(object):
'''Descriptor for a meter.'''

    def __init__(self, value=0.0):
    self.value = float(value)
    def __get__(self, instance, owner):
    return self.value
    def __set__(self, instance, value):
    self.value = float(value)

class Foot(object):
    '''Descriptor for a foot.'''

    def __get__(self, instance, owner):
    return instance.meter * 3.2808
    def __set__(self, instance, value):
    instance.meter = float(value) / 3.2808

class Distance(object):
    '''Class to represent distance holding two descriptors for feet and
    meters.'''
    meter = Meter()
    foot = Foot()

储存你的对象

如果你接触过其他的 Pythoner,你可能已经听说过 Pickle 了, Pickle 是用来序列化 Python 数据结构的模块,在你需要暂时存储一个对象的时候(比如缓存),这个模块非常的有用,不过这同时也是隐患的诞生地。

序列化数据是一个非常重要的功能,所以他不仅仅拥有相关的模块( Pickle , cPickle ),还有自己的协议以及魔术方法,不过首先,我们先讨论下关于序列化内建数据结构的方法。

Pickling: 简单例子

让我们深入研究 Pickle,比如说你现在需要临时储存一个字典,你可以把它写入到一个文件里,并且要小心翼翼的确保格式正确,之后再用 exec() 或者处理文件输入来恢复数据,实际上这是很不安全的,如果你使用文本存储了一些重要的数据,任何方式的改变都可能会影响到你的程序,轻则程序崩溃,重则被恶意程序利用,所以,让我们用 Pickle 代替这种方式:

import pickle

data = {'foo': [1, 2, 3],
        'bar': ('Hello', 'world!'),
        'baz': True}
jar = open('data.pkl', 'wb')
pickle.dump(data, jar) # write the pickled data to the file jar
jar.close()

嗯,过了几个小时之后,我们需要用到它了,只需把它 unpickle 了就行了:

import pickle

pkl_file = open('data.pkl', 'rb') # connect to the pickled data
data = pickle.load(pkl_file) # load it into a variable
print data
pkl_file.close()

正如你期望的,数据原封不动的回来了!

同时要给你一句忠告: pickle 并不是很完美, Pickle 文件很容易被不小心或者故意损坏, Pickle 文件比纯文本文件要稍微安全一点,但是还是可以被利用运行恶意程序。 Pickle 不是跨版本兼容的(译注:最近刚好在 《Python Cookbook》上看到相关讨论,书中描述的 Pickle 是跨版本兼容的,此点待验证),所以尽量不要去分发 Pickle 过的文本,因为别人并不一定能够打开。不过在做缓存或者其他需要序列化数据的时候, Pickle 还是很有用处的。

序列化你自己的对象

Pickle 并不是只支持内建数据结果,任何遵循 Pickle 协议的类都可以,Pickle 协议为 Python 对象规定了4个可选方法来自定义 Pickle 行为(对于 C 扩展的 cPickle 模块会有一些不同,但是这并不在我们的讨论范围内):

__getinitargs__(self)

如果你希望在逆序列化的同时调用 __init__ ,你可以定义 __getinitargs__ 方法,这个方法应该返回一系列你想被 __init__ 调用的参数,注意这个方法只对老样式的类起作用。

__getnewargs__(self)

对于新式的类,你可以定义任何在重建对象时候传递到 __new__ 方法中的参数。这个方法也应该返回一系列的被 __new__ 调用的参数。

__getstate__(self)

你可以自定义当对象被序列化时返回的状态,而不是使用 __dict 方法,当逆序列化对象的时候,返回的状态将会被 __setstate__ 方法调用。

__setstate__(self, state)

在对象逆序列化的时候,如果 __setstate__ 定义过的话,对象的状态将被传给它而不是传给 __dict__ 。这个方法是和 __getstate__ 配对的,当这两个方法都被定义的时候,你就可以完全控制整个序列化与逆序列化的过程了。

例子

我们以 Slate 为例,这是一段记录一个值以及这个值是何时被写入的程序,但是,这个 Slate 有一点特殊的地方,当前值不会被保存。

import time

class Slate:
    '''Class to store a string and a changelog, and forget its value when
    pickled.'''

    def __init__(self, value):
        self.value = value
        self.last_change = time.asctime()
        self.history = {}

    def change(self, new_value):
        # Change the value. Commit last value to history
        self.history[self.last_change] = self.value
        self.value = new_value
        self.last_change = time.asctime()

    def print_changes(self):
        print 'Changelog for Slate object:'
        for k, v in self.history.items():
            print '%s\t %s' % (k, v)

    def __getstate__(self):
        # Deliberately do not return self.value or self.last_change.
        # We want to have a "blank slate" when we unpickle.
        return self.history

    def __setstate__(self, state):
        # Make self.history = state and last_change and value undefined
        self.history = state
        self.value, self.last_change = None, None

结论

这份指南的希望为所有人都能带来一些知识,即使你是 Python 大牛或者对于精通于面向对象开发。如果你是一个 Python 初学者,阅读这篇文章之后你已经获得了编写丰富,优雅,灵活的类的知识基础了。如果你是一个有一些经验的 Python 程序员,你可能会发现一些能让你写的代码更简洁的方法。如果你是一个 Python 大牛,可能会帮助你想起来一些你已经遗忘的知识,或者一些你还没听说过的新功能。不管你现在有多少经验,我希望这次对于 Python 特殊方法的旅程能够带给你一些帮助(用双关语真的很不错 XD)(译注: 这里的双关在于标题为 Magic Methods 这里是 神奇的旅程 ,不过由于中英语序的问题,直译略显头重脚轻,所以稍微变化了下意思,丢掉了双关的含义)。

附录:如何调用魔术方法

一些魔术方法直接和内建函数相对,在这种情况下,调用他们的方法很简单,但是,如果是另外一种不是特别明显的调用方法,这个附录介绍了很多并不是很明显的魔术方法的调用形式。

魔术方法 调用方式 解释
__new__(cls [,…]) instance = MyClass(arg1, arg2) __new__ 在创建实例的时候被调用
__init__(self [,…]) instance = MyClass(arg1, arg2) __init__ 在创建实例的时候被调用
__cmp__(self, other) self == other, self > other, 等。 在比较的时候调用
__pos__(self) +self 一元加运算符
__neg__(self) -self 一元减运算符
__invert__(self) ~self 取反运算符
__index__(self) x[self] 对象被作为索引使用的时候
__nonzero__(self) bool(self) 对象的布尔值
__getattr__(self, name) self.name # name 不存在 访问一个不存在的属性时
__setattr__(self, name, val) self.name = val 对一个属性赋值时
__delattr__(self, name) del self.name 删除一个属性时
__getattribute(self, name) self.name 访问任何属性时
__getitem__(self, key) self[key] 使用索引访问元素时
__setitem__(self, key, val) self[key] = val 对某个索引值赋值时
__delitem__(self, key) del self[key] 删除某个索引值时
__iter__(self) for x in self 迭代时
__contains__(self, value) value in self, value not in self 使用 in 操作测试关系时
__concat__(self, value) self + other 连接两个对象时
__call__(self [,…]) self(args) “调用”对象时
__enter__(self) with self as x: with 语句环境管理
__exit__(self, exc, val, trace) with self as x: with 语句环境管理
__getstate__(self) pickle.dump(pkl_file, self) 序列化
__setstate__(self) data = pickle.load(pkl_file) 序列化

希望这个表格对你对于什么时候应该使用什么方法这个问题有所帮助。

from: http://pycoders-weekly-chinese.readthedocs.io/en/latest/issue6/a-guide-to-pythons-magic-methods.html

 

 

 

每天进步一点点之Redis知识点总结

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。

Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

第一章Redis简介

1.1 Redis 简介

Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。

Redis 与其他 key – value 缓存产品有以下三个特点:

Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。

Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。

Redis支持数据的备份,即master-slave模式的数据备份。

1.2 Redis 优势

性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。

丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。

原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。

丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。

1.3 Redis与其他key-value存储有什么不同?

Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。

Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。

第二章 Redis 配置

Redis 的配置文件位于 Redis 安装目录下,文件名为 redis.conf。

2.1 读取配置:

你可以通过 CONFIG 命令查看或设置配置项。

语法

Redis CONFIG 命令格式如下:

 redis 127.0.0.1:6379> CONFIG GET CONFIG_SETTING_NAME

实例

127.0.0.1:6379> CONFIG GET loglevel
1) "loglevel"
2) "notice"

使用 * 号获取所有配置项:

 127.0.0.1:6379> CONFIG GET *
  1) "dbfilename"
  2) "dump.rdb"
  3) "requirepass"
  4) ""
  5) "masterauth"
  6) ""
  7) "unixsocket"
  8) ""
  9) "logfile"
 10) ""
 11) "pidfile"
......

2.2 编辑配置

你可以通过修改 redis.conf 文件或使用 CONFIG set 命令来修改配置。

语法

CONFIG SET 命令基本语法:

 redis 127.0.0.1:6379> CONFIG SET CONFIG_SETTING_NAME NEW_CONFIG_VALUE

实例

127.0.0.1:6379> CONFIG SET loglevel "notice"
OK
127.0.0.1:6379> CONFIG GET loglevel
1) "loglevel"
2) "notice"

2.3 参数说明

redis.conf 配置项说明如下:

  1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程

daemonize no

  1. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定

pidfile /var/run/redis.pid

  1. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字

port 6379

  1. 绑定的主机地址

bind 127.0.0.1

5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能

timeout 300

  1. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose

loglevel verbose

  1. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null

logfile stdout

  1. 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id

databases 16

  1. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

save <seconds> <changes>

Redis默认配置文件中提供了三个条件:

save 900 1

    save 300 10

    save 60 10000

分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

 

  1. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大

rdbcompression yes

  1. 指定本地数据库文件名,默认值为dump.rdb

dbfilename dump.rdb

  1. 指定本地数据库存放目录

dir ./

  1. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步

slaveof <masterip> <masterport>

  1. 当master服务设置了密码保护时,slav服务连接master的密码

masterauth <master-password>

  1. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭

requirepass foobared

  1. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息

maxclients 128

  1. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区

maxmemory <bytes>

  1. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no

appendonly no

  1. 指定更新日志文件名,默认为appendonly.aof

appendfilename appendonly.aof

  1. 指定更新日志条件,共有3个可选值:
    no:表示等操作系统进行数据缓存同步到磁盘(快)
    always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
    everysec:表示每秒同步一次(折衷,默认值)

appendfsync everysec

 

  1. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)

vm-enabled no

  1. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享

vm-swap-file /tmp/redis.swap

  1. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0

vm-max-memory 0

  1. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值

vm-page-size 32

  1. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。

vm-pages 134217728

  1. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4

vm-max-threads 4

  1. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

glueoutputbuf yes

  1. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法

hash-max-zipmap-entries 64

    hash-max-zipmap-value 512

  1. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)

activerehashing yes

  1. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

include /path/to/local.conf

第三章 Redis 数据类型

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

3.1 String(字符串)

string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。

string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。

string类型是Redis最基本的数据类型,一个键最大能存储512MB。

实例

127.0.0.1:6379> SET name "runoob"
OK
127.0.0.1:6379> GET name
"runoob"

在以上实例中我们使用了 Redis 的 SET 和 GET 命令。键为 name,对应的值为 runoob

注意:一个键最大能存储512MB。

Redis 字符串命令

下表列出了常用的 redis 字符串命令:

序号 命令及描述
1 SET key value
设置指定 key 的值
2 GET key
获取指定 key 的值。
3 GETRANGE key start end
返回 key 中字符串值的子字符
4 GETSET key value
将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
5 GETBIT key offset
对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
6 MGET key1 [key2..]
获取所有(一个或多个)给定 key 的值。
7 SETBIT key offset value
对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
8 SETEX key seconds value
将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
9 SETNX key value
只有在 key 不存在时设置 key 的值。
10 SETRANGE key offset value
用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。
11 STRLEN key
返回 key 所储存的字符串值的长度。
12 MSET key value [key value …]
同时设置一个或多个 key-value 对。
13 MSETNX key value [key value …]
同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
14 PSETEX key milliseconds value
这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
15 INCR key
将 key 中储存的数字值增一。
16 INCRBY key increment
将 key 所储存的值加上给定的增量值(increment) 。
17 INCRBYFLOAT key increment
将 key 所储存的值加上给定的浮点增量值(increment) 。
18 DECR key
将 key 中储存的数字值减一。
19 DECRBY key decrement
key 所储存的值减去给定的减量值(decrement) 。
20 APPEND key value
如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。

3.2 Hash(哈希)

Redis hash 是一个键值对集合。

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

实例

127.0.0.1:6379> HMSET user:1 username runoob password runoob points 200
OK
127.0.0.1:6379> HGETALL user:1
1) "username"
2) "runoob"
3) "password"
4) "runoob"
5) "points"
6) "200"

以上实例中 hash 数据类型存储了包含用户脚本信息的用户对象。 实例中我们使用了 Redis HMSET, HGETALL 命令,user:1 为键值。

每个 hash 可以存储 232 -1 键值对(40多亿)。

Redis hash 命令

下表列出了 redis hash 基本的相关命令:

序号 命令及描述
1 HDEL key field2 [field2]
删除一个或多个哈希表字段
2 HEXISTS key field
查看哈希表 key 中,指定的字段是否存在。
3 HGET key field
获取存储在哈希表中指定字段的值。
4 HGETALL key
获取在哈希表中指定 key 的所有字段和值
5 HINCRBY key field increment
为哈希表 key 中的指定字段的整数值加上增量 increment 。
6 HINCRBYFLOAT key field increment
为哈希表 key 中的指定字段的浮点数值加上增量 increment 。
7 HKEYS key
获取所有哈希表中的字段
8 HLEN key
获取哈希表中字段的数量
9 HMGET key field1 [field2]
获取所有给定字段的值
10 HMSET key field1 value1 [field2 value2 ]
同时将多个 field-value (域-值)对设置到哈希表 key 中。
11 HSET key field value
将哈希表 key 中的字段 field 的值设为 value 。
12 HSETNX key field value
只有在字段 field 不存在时,设置哈希表字段的值。
13 HVALS key
获取哈希表中所有值
14 HSCAN key cursor [MATCH pattern] [COUNT count]
迭代哈希表中的键值对。

3.3 List(列表)

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

实例

127.0.0.1:6379> lpush runoob redis
(integer) 1
127.0.0.1:6379> lpush runoob mongodb
(integer) 2
127.0.0.1:6379> lpush runoob memcache
(integer) 3
127.0.0.1:6379> lrange runoob 0 10
1) "memcache"
2) "mongodb"
3) "redis"

列表最多可存储 232 – 1 元素 (4294967295, 每个列表可存储40多亿)。

Redis 列表命令

下表列出了列表相关的基本命令:

序号 命令及描述
1 BLPOP key1 [key2 ] timeout
移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
2 BRPOP key1 [key2 ] timeout
移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
3 BRPOPLPUSH source destination timeout
从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
4 LINDEX key index
通过索引获取列表中的元素
5 LINSERT key BEFORE|AFTER pivot value
在列表的元素前或者后插入元素
6 LLEN key
获取列表长度
7 LPOP key
移出并获取列表的第一个元素
8 LPUSH key value1 [value2]
将一个或多个值插入到列表头部
9 LPUSHX key value
将一个或多个值插入到已存在的列表头部
10 LRANGE key start stop
获取列表指定范围内的元素
11 LREM key count value
移除列表元素
12 LSET key index value
通过索引设置列表元素的值
13 LTRIM key start stop
对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
14 RPOP key
移除并获取列表最后一个元素
15 RPOPLPUSH source destination
移除列表的最后一个元素,并将该元素添加到另一个列表并返回
16 RPUSH key value1 [value2]
在列表中添加一个或多个值
17 RPUSHX key value
为已存在的列表添加值

3.4 Set(集合)

Redis的Set是string类型的无序集合。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

sadd 命令

添加一个string元素到,key对应的set集合中,成功返回1,如果元素已经在集合中返回0,key对应的set不存在返回错误。

 sadd key member

实例

 127.0.0.1:6379> sadd runoob redis
(integer) 1
127.0.0.1:6379> sadd runoob memcache
(integer) 1
127.0.0.1:6379> sadd runoob mongodb
(integer) 1
127.0.0.1:6379> sadd runoob redis
(integer) 0
127.0.0.1:6379> smembers runoob
1) "redis"
2) "memcache"
3) "mongodb"

注意:以上实例中 rabitmq 添加了两次,但根据集合内元素的唯一性,第二次插入的元素将被忽略。

集合中最大的成员数为 232 – 1(4294967295, 每个集合可存储40多亿个成员)。

Redis 集合命令

下表列出了 Redis 集合基本命令:

序号 命令及描述
1 SADD key member1 [member2]
向集合添加一个或多个成员
2 SCARD key
获取集合的成员数
3 SDIFF key1 [key2]
返回给定所有集合的差集
4 SDIFFSTORE destination key1 [key2]
返回给定所有集合的差集并存储在 destination 中
5 SINTER key1 [key2]
返回给定所有集合的交集
6 SINTERSTORE destination key1 [key2]
返回给定所有集合的交集并存储在 destination 中
7 SISMEMBER key member
判断 member 元素是否是集合 key 的成员
8 SMEMBERS key
返回集合中的所有成员
9 SMOVE source destination member
将 member 元素从 source 集合移动到 destination 集合
10 SPOP key
移除并返回集合中的一个随机元素
11 SRANDMEMBER key [count]
返回集合中一个或多个随机数
12 SREM key member1 [member2]
移除集合中一个或多个成员
13 SUNION key1 [key2]
返回所有给定集合的并集
14 SUNIONSTORE destination key1 [key2]
所有给定集合的并集存储在 destination 集合中
15 SSCAN key cursor [MATCH pattern] [COUNT count]
迭代集合中的元素

3.5 Zset(sorted set:有序集合)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

zset的成员是唯一的,但分数(score)却可以重复。

zadd 命令

添加元素到集合,元素在集合中存在则更新对应score

 zadd key score member

实例

127.0.0.1:6379> zadd runoob 0 redis
(integer) 1
127.0.0.1:6379> zadd runoob 0 mongodb
(integer) 1
127.0.0.1:6379> zadd runoob 0 rabitmq
(integer) 1
127.0.0.1:6379> zadd runoob 0 rabitmq
(integer) 0
127.0.0.1:6379> zrangebyscore runoob
(error) ERR wrong number of arguments for 'zrangebyscore' command
127.0.0.1:6379> zrangebyscore runoob 0 1000
1) "mongodb"
2) "rabitmq"
3) "redis"

Redis 有序集合命令

下表列出了 redis 有序集合的基本命令:

序号 命令及描述
1 ZADD key score1 member1 [score2 member2]
向有序集合添加一个或多个成员,或者更新已存在成员的分数
2 ZCARD key
获取有序集合的成员数
3 ZCOUNT key min max
计算在有序集合中指定区间分数的成员数
4 ZINCRBY key increment member
有序集合中对指定成员的分数加上增量 increment
5 ZINTERSTORE destination numkeys key [key …]
计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中
6 ZLEXCOUNT key min max
在有序集合中计算指定字典区间内成员数量
7 ZRANGE key start stop [WITHSCORES]
通过索引区间返回有序集合成指定区间内的成员
8 ZRANGEBYLEX key min max [LIMIT offset count]
通过字典区间返回有序集合的成员
9 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT]
通过分数返回有序集合指定区间内的成员
10 ZRANK key member
返回有序集合中指定成员的索引
11 ZREM key member [member …]
移除有序集合中的一个或多个成员
12 ZREMRANGEBYLEX key min max
移除有序集合中给定的字典区间的所有成员
13 ZREMRANGEBYRANK key start stop
移除有序集合中给定的排名区间的所有成员
14 ZREMRANGEBYSCORE key min max
移除有序集合中给定的分数区间的所有成员
15 ZREVRANGE key start stop [WITHSCORES]
返回有序集中指定区间内的成员,通过索引,分数从高到底
16 ZREVRANGEBYSCORE key max min [WITHSCORES]
返回有序集中指定分数区间内的成员,分数从高到低排序
17 ZREVRANK key member
返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
18 ZSCORE key member
返回有序集中,成员的分数值
19 ZUNIONSTORE destination numkeys key [key …]
计算给定的一个或多个有序集的并集,并存储在新的 key 中
20 ZSCAN key cursor [MATCH pattern] [COUNT count]
迭代有序集合中的元素(包括元素成员和元素分值)

第四章 Redis 命令

4.1 Redis命令

Redis 命令用于在 redis 服务上执行操作。

要在 redis 服务上执行命令需要一个 redis 客户端。Redis 客户端在我们之前下载的的 redis 的安装包中。

语法

Redis 客户端的基本语法为:

learning@mobile-safe:~$ redis-cli
127.0.0.1:6379>

实例

以下实例讲解了如何启动 redis 客户端:

启动 redis 客户端,打开终端并输入命令 redis-cli。该命令会连接本地的 redis 服务。

learning@mobile-safe:~$ redis-cli
127.0.0.1:6379> ping
PONG

在以上实例中我们连接到本地的 redis 服务并执行 PING 命令,该命令用于检测 redis 服务是否启动。

在远程服务上执行命令

如果需要在远程 redis 服务上执行命令,同样我们使用的也是 redis-cli 命令。

语法

 $ redis-cli -h host -p port -a password

实例

以下实例演示了如何连接到主机为 127.0.0.1,端口为 6379 ,密码为 mypass 的 redis 服务上。

$redis-cli -h 127.0.0.1 -p 6379 -a "mypass"
redis 127.0.0.1:6379>
redis 127.0.0.1:6379> PING

PONG

4.2 redis的键

Redis keys 命令

下表给出了与 Redis 键相关的基本命令:

序号 命令及描述
1 DEL key
该命令用于在 key 存在时删除 key。
2 DUMP key
序列化给定 key ,并返回被序列化的值。
3 EXISTS key
检查给定 key 是否存在。
4 EXPIRE key seconds
为给定 key 设置过期时间。
5 EXPIREAT key timestamp
EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
6 PEXPIRE key milliseconds
设置 key 的过期时间以毫秒计。
7 PEXPIREAT key milliseconds-timestamp
设置 key 过期时间的时间戳(unix timestamp) 以毫秒计
8 KEYS pattern
查找所有符合给定模式( pattern)的 key 。
9 MOVE key db
将当前数据库的 key 移动到给定的数据库 db 当中。
10 PERSIST key
移除 key 的过期时间,key 将持久保持。
11 PTTL key
以毫秒为单位返回 key 的剩余的过期时间。
12 TTL key
以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。
13 RANDOMKEY
从当前数据库中随机返回一个 key 。
14 RENAME key newkey
修改 key 的名称
15 RENAMENX key newkey
仅当 newkey 不存在时,将 key 改名为 newkey 。
16 TYPE key
返回 key 所储存的值的类型。

第五章 Redis HyperLogLog

5. 1 Redis HyperLogLog简介

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

5.2 什么是基数?

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

5.3 实例

以下实例演示了 HyperLogLog 的工作过程:

127.0.0.1:6379> pfadd runoobkey mongodb
(integer) 1
127.0.0.1:6379> pfadd runoobkey mysql
(integer) 1
127.0.0.1:6379> pfadd runoobkey redis
(integer) 1
127.0.0.1:6379> pfcount runoobkey
(integer) 3

5.4 Redis HyperLogLog 命令

下表列出了 redis HyperLogLog 的基本命令:

序号 命令及描述
1 PFADD key element [element …]
添加指定元素到 HyperLogLog 中。
2 PFCOUNT key [key …]
返回给定 HyperLogLog 的基数估算值。
3 PFMERGE destkey sourcekey [sourcekey …]
将多个 HyperLogLog 合并为一个 HyperLogLog

第六章 Redis 发布订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

Redis 客户端可以订阅任意数量的频道。

实例

127.0.0.1:6379> SUBSCRIBE redisChat
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1
# 在发送者publish消息之后会显示下列结果
1) "message"  
2) "redisChat"
3) "this is message"

下列是发布者发送消息的过程:

127.0.0.1:6379> PUBLISH redisChat 'this is message'
  (integer) 1
127.0.0.1:6379>

Redis 发布订阅命令

下表列出了 redis 发布订阅常用命令:

序号 命令及描述
1 PSUBSCRIBE pattern [pattern …]
订阅一个或多个符合给定模式的频道。
2 PUBSUB subcommand [argument [argument …]]
查看订阅与发布系统状态。
3 PUBLISH channel message
将信息发送到指定的频道。
4 PUNSUBSCRIBE [pattern [pattern …]]
退订所有给定模式的频道。
5 SUBSCRIBE channel [channel …]
订阅给定的一个或多个频道的信息。
6 UNSUBSCRIBE [channel [channel …]]
指退订给定的频道。

第7章 Redis 事务

这里的事务跟mysql中的事务是一样的,都是为了保证数据的原子性。

Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:

  • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务。
  • 命令入队。
  • 执行事务。

实例

以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:

127.0.0.1:6379> MULTI
  OK
127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
  QUEUED
127.0.0.1:6379> GET book-name
  QUEUED
127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
  QUEUED
127.0.0.1:6379> SMEMBERS tag
  QUEUED
127.0.0.1:6379> EXEC
  1) OK
  2) "Mastering C++ in 21 days"
  3) (integer) 3
  4) 1) "Mastering Series"
     2) "Programming"
     3) "C++"

Redis 事务命令

下表列出了 redis 事务的相关命令:

序号 命令及描述
1 DISCARD
取消事务,放弃执行事务块内的所有命令。
2 EXEC
执行所有事务块内的命令。
3 MULTI
标记一个事务块的开始。
4 UNWATCH
取消 WATCH 命令对所有 key 的监视。
5 WATCH key [key …]
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

第八章 Redis 脚本

Redis 脚本使用 Lua 解释器来执行脚本。 Reids 2.6 版本通过内嵌支持 Lua 环境。执行脚本的常用命令为 EVAL

语法

Eval 命令的基本语法如下:

 127.0.0.1:6379> EVAL script numkeys key [key ...] arg [arg ...]

实例

以下实例演示了 redis 脚本工作过程:

127.0.0.1:6379> EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
  1) "key1"
  2) "key2"
  3) "first"
  4) "second"

Redis 脚本命令

下表列出了 redis 脚本常用命令:

序号 命令及描述
1 EVAL script numkeys key [key …] arg [arg …]
执行 Lua 脚本。
2 EVALSHA sha1 numkeys key [key …] arg [arg …]
执行 Lua 脚本。
3 SCRIPT EXISTS script [script …]
查看指定的脚本是否已经被保存在缓存当中。
4 SCRIPT FLUSH
从脚本缓存中移除所有脚本。
5 SCRIPT KILL
杀死当前正在运行的 Lua 脚本。
6 SCRIPT LOAD script
将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。

第九章 Redis 连接

Redis 连接命令主要是用于连接 redis 服务。

实例

以下实例演示了客户端如何通过密码验证连接到 redis 服务,并检测服务是否在运行:

redis 127.0.0.1:6379> AUTH "password"
OK
redis 127.0.0.1:6379> PING
PONG

那么,这个密码是怎么设置的呢?

我们可以通过 redis 的配置文件设置密码参数,这样客户端连接到 redis 服务就需要密码验证,这样可以让你的 redis 服务更安全。

实例

我们可以通过以下命令查看是否设置了密码验证:

127.0.0.1:6379> CONFIG GET requirepass
  1) "requirepass"
  2) ""

默认情况下 requirepass 参数是空的,这就意味着你无需通过密码验证就可以连接到 redis 服务。

你可以通过以下命令来修改该参数:

127.0.0.1:6379> CONFIG set requirepass "runoob"
  OK

这个时候在进行操作的时候就需要先输入密码了:

127.0.0.1:6379> CONFIG get requirepass
  (error) NOAUTH Authentication required.

输入密码后我们就可以看到requirepass的具体值了:

127.0.0.1:6379> CONFIG GET requirepass
  1) "requirepass"
  2) "runoob"

如果要将密码去除,只需要把requirepass设置为空就好了

127.0.0.1:6379> CONFIG SET requirepass ""
  OK

Redis 连接命令

下表列出了 redis 连接的基本命令:

序号 命令及描述
1 AUTH password
验证密码是否正确
2 ECHO message
打印字符串
3 PING
查看服务是否运行
4 QUIT
关闭当前连接
5 SELECT index
切换到指定的数据库

Redis 客户端连接

Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自客户端的连接,当一个连接建立后,Redis 内部会进行以下一些操作:

  • 首先,客户端 socket 会被设置为非阻塞模式,因为 Redis 在网络事件处理上采用的是非阻塞多路复用模型。
  • 然后为这个 socket 设置 TCP_NODELAY 属性,禁用 Nagle 算法
  • 然后创建一个可读的文件事件用于监听这个客户端 socket 的数据发送

最大连接数

在 Redis2.4 中,最大连接数是被直接硬编码在代码里面的,而在2.6版本中这个值变成可配置的。

maxclients 的默认值是 10000,你也可以在 redis.conf 中对这个值进行修改。

127.0.0.1:6379> CONFIG GET maxclients
  1) "maxclients"
  2) "10000"

实例

以下实例我们在服务启动时设置最大连接数为 100000:

redis-server --maxclients 100000

客户端命令

S.N. 命令 描述
1 CLIENT LIST 返回连接到 redis 服务的客户端列表
2 CLIENT SETNAME 设置当前连接的名称
3 CLIENT GETNAME 获取通过 CLIENT SETNAME 命令设置的服务名称
4 CLIENT PAUSE 挂起客户端连接,指定挂起的时间以毫秒计
5 CLIENT KILL 关闭客户端连接

第十章 Redis 服务器

Redis 服务器命令主要是用于管理 redis 服务。

可以通过以下的命令对redis服务器进行设置:

Redis 服务器命令

下表列出了 redis 服务器的相关命令:

序号 命令及描述
1 BGREWRITEAOF
异步执行一个 AOF(AppendOnly File) 文件重写操作
2 BGSAVE
在后台异步保存当前数据库的数据到磁盘
3 CLIENT KILL [ip:port] [ID client-id]
关闭客户端连接
4 CLIENT LIST
获取连接到服务器的客户端连接列表
5 CLIENT GETNAME
获取连接的名称
6 CLIENT PAUSE timeout
在指定时间内终止运行来自客户端的命令
7 CLIENT SETNAME connection-name
设置当前连接的名称
8 CLUSTER SLOTS
获取集群节点的映射数组
9 COMMAND
获取 Redis 命令详情数组
10 COMMAND COUNT
获取 Redis 命令总数
11 COMMAND GETKEYS
获取给定命令的所有键
12 TIME
返回当前服务器时间
13 COMMAND INFO command-name [command-name …]
获取指定 Redis 命令描述的数组
14 CONFIG GET parameter
获取指定配置参数的值
15 CONFIG REWRITE
对启动 Redis 服务器时所指定的 redis.conf 配置文件进行改写
16 CONFIG SET parameter value
修改 redis 配置参数,无需重启
17 CONFIG RESETSTAT
重置 INFO 命令中的某些统计数据
18 DBSIZE
返回当前数据库的 key 的数量
19 DEBUG OBJECT key
获取 key 的调试信息
20 DEBUG SEGFAULT
让 Redis 服务崩溃
21 FLUSHALL
删除所有数据库的所有key
22 FLUSHDB
删除当前数据库的所有key
23 INFO [section]
获取 Redis 服务器的各种信息和统计数值
24 LASTSAVE
返回最近一次 Redis 成功将数据保存到磁盘上的时间,以 UNIX 时间戳格式表示
25 MONITOR
实时打印出 Redis 服务器接收到的命令,调试用
26 ROLE
返回主从实例所属的角色
27 SAVE
异步保存数据到硬盘
28 SHUTDOWN [NOSAVE] [SAVE]
异步保存数据到硬盘,并关闭服务器
29 SLAVEOF host port
将当前服务器转变为指定服务器的从属服务器(slave server)
30 SLOWLOG subcommand [argument]
管理 redis 的慢日志
31 SYNC
用于复制功能(replication)的内部命令

第十一章 Redis 数据备份与恢复

Redis SAVE 命令用于创建当前数据库的备份。

语法

redis Save 命令基本语法如下:

redis 127.0.0.1:6379> SAVE

实例

redis 127.0.0.1:6379> SAVE 
OK

该命令将在 redis 安装目录中创建dump.rdb文件。

恢复数据

如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。获取 redis 目录可以使用 CONFIG 命令,如下所示:

redis 127.0.0.1:6379> CONFIG GET dir
  1) "dir"
  2) "/usr/local/redis/bin"

以上命令 CONFIG GET dir 输出的 redis 安装目录为 /usr/local/redis/bin。

Bgsave

创建 redis 备份文件也可以使用命令 BGSAVE,该命令在后台执行。

实例

127.0.0.1:6379> BGSAVE
  Background saving started

 

第十二章 Redis 性能测试

Redis 性能测试是通过同时执行多个命令实现的。

语法

redis 性能测试的基本命令如下:

 redis-benchmark [option] [option value]

实例

以下实例同时执行 10000 个请求来检测性能:

redis-benchmark -n 10000

  PING_INLINE: 141043.72 requests per second
  PING_BULK: 142857.14 requests per second
  SET: 141442.72 requests per second
  GET: 145348.83 requests per second
  INCR: 137362.64 requests per second
  LPUSH: 145348.83 requests per second
  LPOP: 146198.83 requests per second
  SADD: 146198.83 requests per second
  SPOP: 149253.73 requests per second
  LPUSH (needed to benchmark LRANGE): 148588.42 requests per second
  LRANGE_100 (first 100 elements): 58411.21 requests per second
  LRANGE_300 (first 300 elements): 21195.42 requests per second
  LRANGE_500 (first 450 elements): 14539.11 requests per second
  LRANGE_600 (first 600 elements): 10504.20 requests per second
  MSET (10 keys): 93283.58 requests per second

redis 性能测试工具可选参数如下所示:

序号 选项 描述 默认值
1 -h 指定服务器主机名 127.0.0.1
2 -p 指定服务器端口 6379
3 -s 指定服务器 socket
4 -c 指定并发连接数 50
5 -n 指定请求数 10000
6 -d 以字节的形式指定 SET/GET 值的数据大小 2
7 -k 1=keep alive 0=reconnect 1
8 -r SET/GET/INCR 使用随机 key, SADD 使用随机值
9 -P 通过管道传输 <numreq> 请求 1
10 -q 强制退出 redis。仅显示 query/sec 值
11 –csv 以 CSV 格式输出
12 -l 生成循环,永久执行测试
13 -t 仅运行以逗号分隔的测试命令列表。
14 -I Idle 模式。仅打开 N 个 idle 连接并等待。

实例

以下实例我们使用了多个参数来测试 redis 性能:

redis-benchmark -h 127.0.0.1 -p 6379 -t set,lpush -n 10000 -q

  SET: 146198.83 requests per second
  LPUSH: 145560.41 requests per second

以上实例中主机为 127.0.0.1,端口号为 6379,执行的命令为 set,lpush,请求数为 10000,通过 -q 参数让结果只显示每秒执行的请求数。

第十三章 Redis 管道技术

Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:

  • 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
  • 服务端处理命令,并将结果返回给客户端。

Redis 管道技术

Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。

实例

查看 redis 管道,只需要启动 redis 实例并输入以下命令:

$(echo -en "PING\r\n SET runoobkey redis\r\nGET runoobkey\r\nINCR visitor\r\nINCR visitor\r\nINCR visitor\r\n"; sleep 10) | nc localhost 6379

+PONG
+OK
redis
:1
:2
:3

以上实例中我们通过使用 PING 命令查看redis服务是否可用, 之后我们们设置了 runoobkey 的值为 redis,然后我们获取 runoobkey 的值并使得 visitor 自增 3 次。

在返回的结果中我们可以看到这些命令一次性向 redis 服务提交,并最终一次性读取所有服务端的响应

管道技术的优势

管道技术最显著的优势是提高了 redis 服务的性能。

第十四章 Redis 分区

分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集。

分区的优势

  • 通过利用多台计算机内存的和值,允许我们构造更大的数据库。
  • 通过多核和多台计算机,允许我们扩展计算能力;通过多台计算机和网络适配器,允许我们扩展网络带宽。

分区的不足

redis的一些特性在分区方面表现的不是很好:

  • 涉及多个key的操作通常是不被支持的。举例来说,当两个set映射到不同的redis实例上时,你就不能对这两个set执行交集操作。
  • 涉及多个key的redis事务不能使用。
  • 当使用分区时,数据处理较为复杂,比如你需要处理多个rdb/aof文件,并且从多个实例和主机备份持久化文件。
  • 增加或删除容量也比较复杂。redis集群大多数支持在运行时增加、删除节点的透明数据平衡的能力,但是类似于客户端分区、代理等其他系统则不支持这项特性。然而,一种叫做presharding的技术对此是有帮助的。

分区类型

Redis 有两种类型分区。 假设有4个Redis实例 R0,R1,R2,R3,和类似user:1,user:2这样的表示用户的多个key,对既定的key有多种不同方式来选择这个key存放在哪个实例中。也就是说,有不同的系统来映射某个key到某个Redis服务。

范围分区

最简单的分区方式是按范围分区,就是映射一定范围的对象到特定的Redis实例。

比如,ID从0到10000的用户会保存到实例R0,ID从10001到 20000的用户会保存到R1,以此类推。

这种方式是可行的,并且在实际中使用,不足就是要有一个区间范围到实例的映射表。这个表要被管理,同时还需要各 种对象的映射表,通常对Redis来说并非是好的方法。

哈希分区

另外一种分区方法是hash分区。这对任何key都适用,也无需是object_name:这种形式,像下面描述的一样简单:

  • 用一个hash函数将key转换为一个数字,比如使用crc32 hash函数。对key foobar执行crc32(foobar)会输出类似93024922的整数。
  • 对这个整数取模,将其转化为0-3之间的数字,就可以将这个整数映射到4个Redis实例中的一个了。93024922 % 4 = 2,就是说key foobar应该被存到R2实例中。注意:取模操作是取除的余数,通常在多种编程语言中用%操作符实现。

 

MySQL数据库迁移【转】

MySQL数据库迁移(数据文件直接迁移)

在今年10月下旬的时候,公司的服务器需要迁移,其中涉及到了MySQL数据库迁移。查看了一下MySQL数据文件的大小,接近60G的大小(实际数据并没用那么多)。由于服务器上业务需要,要尽量减少服务器迁移时的损失。所以迁移时间选在了晚上零点开始,而且要尽量减少迁移所用的时间。

在迁移之前有三种方案:

  1. 数据库直接导出,拷贝文件到新服务器,在新服务器上导入。
  2. 使用【MySQL GUI Tools】中的 MySQLMigrationTool。
  3. 数据文件和库表结构文件直接拷贝到新服务器,挂载到同样配置的MySQL服务下。

我在我的电脑上用虚拟机测试后,选中了占用时间最少的第三种方案。下面是三种方案的对比:

 

第一种方案的优点:会重建数据文件,减少数据文件的占用空间。
第一种方案的缺点:时间占用长。(导入导出都需要很长的时间,并且导出后的文件还要经过网络传输,也要占用一定的时间。)

第二种方案的优点:设置完成后传输无人值守
第二种方案的缺点:

    1. 设置繁琐。
    2. 传输中网络出现异常,不能及时的被发现,并且会一直停留在数据传输的状态不能被停止,如不仔细观察不会被发现异常。
    3. 传输相对其他fang时间长。
    4. 异常后很难从异常的位置继续传输。

第三种方案的优点:时间占用短,文件可断点传输。操作步骤少。(绝大部分时间都是在文件的网络传输)
第三种方案的缺点:可能引起未知问题,暂时未发现。

下面介绍一下第三种方案d迁移步骤:

  1. 保证Mysql版本一致,安装配置基本一致(注意:这里的数据文件和库表结构文件都指定在同一目录data下)
  2. 停止两边的Mysql服务(A服务器–迁移–>B服务器)
  3. 删除B服务器Mysql的data目录下所有文件
  4. 拷贝A服务器Mysql的data目录下除了ib_logfile.err之外的文件到B服务器data下
  5. 启动B服务器的Mysql服务,检测是否发生异常

迁移完成后,服务启动正常,未发现其他异常问题。

data文件夹文件列表如下:

 

 

备注:经测试,源mysql的安装目录及数据文件目录 可以与 目标Mysql的安装目录及数据文件目录 不一致。

此时,只需要拷贝您所需移动的dbname(如上:pa、testdb)及’mysql’和’ibdata1’,即可。

每天进步一点点之PHP基础总结【转】

  • PHP 代表 PHP: Hypertext Preprocessor
  • PHP 文件可包含文本、HTML、JavaScript代码和 PHP 代码
  • PHP 代码在服务器上执行,结果以纯 HTML 形式返回给浏览器
  • PHP 文件的默认文件扩展名是 “.php”

PHP能做什么

  • PHP 可以生成动态页面内容
  • PHP 可以创建、打开、读取、写入、关闭服务器上的文件
  • PHP 可以收集表单数据
  • PHP 可以发送和接收 cookies
  • PHP 可以添加、删除、修改您的数据库中的数据
  • PHP 可以限制用户访问您的网站上的一些页面
  • PHP 可以加密数据

安装

如果您的服务器不支持 PHP,您必须:

  1. 安装 Web 服务器
  2. 安装 PHP
  3. 安装数据库,比如 MySQL
    官方 PHP 网站(PHP.net)有 PHP 的安装说明: http://php.net/manual/en/install.php

集成开发环境推荐 WAMP:http://www.wampserver.com/

PHP基本语法

PHP 脚本可以放在文档中的任何位置。

PHP 脚本以 <?php 开始,以 ?> 结束:

<!DOCTYPE html>
<html>
<body>

<h1>我的第一个 PHP 页面</h1>

<?php
echo "Hello World!";

// 这是一行注释

/*
这是
多行
注释
*/

?>

</body>
</html>

PHP 中的每个代码行都必须以分号结束

两种在浏览器输出文本的基础指令:echo 和 print。

PHP变量

变量以 $ 符号开始,后面跟着变量的名称

PHP 没有声明变量的命令。

变量在您第一次赋值给它的时候被创建:

PHP 是一门弱类型语言
PHP 会根据变量的值,自动把变量转换为正确的数据类型。

在强类型的编程语言中,我们必须在使用变量前先声明(定义)变量的类型和名称。

PHP变量的作用域

PHP 有四种不同的变量作用域:

  • local
  • global
  • static
  • parameter

局部和全局作用域

在所有函数外部定义的变量,拥有全局作用域。除了函数外,全局变量可以被脚本中的任何部分访问,要在一个函数中访问一个全局变量,需要使用 global 关键字。

在 PHP 函数内部声明的变量是局部变量,仅能在函数内部访问:

<?php
$x=5; // 全局变量

function myTest()
{
      $y=10; // 局部变量
      echo "<p>测试变量在函数内部:<p>";
      echo "变量 x 为: $x";
      echo "<br>";
      echo "变量 y 为: $y";
} 

myTest();

echo "<p>测试变量在函数外部:<p>";
echo "变量 x 为: $x";
echo "<br>";
echo "变量 y 为: $y";
?>

在函数内调用函数外定义的全局变量,我们需要在函数中的变量前加上 global 关键字:

<?php
$x=5;
$y=10;

function myTest()
{
      global $x,$y;
      $y=$x+$y;
}

myTest();
echo $y; // 输出 15
?>

PHP 将所有全局变量存储在一个名为 $GLOBALS[index] 的数组中。 index 保存变量的名称。这个数组可以在函数内部访问,也可以直接用来更新全局变量。

<?php
$x=5;
$y=10;

function myTest()
{
      $GLOBALS['y']=$GLOBALS['x']+$GLOBALS['y'];
} 

myTest();
echo $y;
?>

Static 作用域

当一个函数完成时,它的所有变量通常都会被删除。然而,有时候您希望某个局部变量不要被删除,可以使用 static关键字

<?php

function myTest()
{
      static $x=0;
      echo $x;
      $x++;
}

myTest();
myTest();
myTest();

?>

参数作用域

参数是通过调用代码将值传递给函数的局部变量。

参数是在参数列表中声明的,作为函数声明的一部分:

<?php

function myTest($x)
{
      echo $x;
}

myTest(5);

?>

PHP echo 和 print 语句

  • echo – 可以输出一个或多个字符串
  • print – 只允许输出一个字符串,返回值总为 1

提示:echo 输出的速度比 print 快, echo 没有返回值,print有返回值1。
echo 和 print 都是一个语言结构,使用的时候可以不用加括号,也可以加上括号: echo 或 echo() print print()。

<?php
echo "<h2>PHP is fun!</h2>";
echo "Hello world!<br>";
echo "I'm about to learn PHP!<br>";
echo "This", " string", " was", " made", " with multiple parameters.";

print "<h2>PHP is fun!</h2>";
print "Hello world!<br>";
print "I'm about to learn PHP!";

?>

下面的实例演示了如何使用 echo 命令输出变量和字符串:

<?php
$txt1="Learn PHP";
$txt2="w3cschool.cc";
$cars=array("Volvo","BMW","Toyota");

echo $txt1;
echo "<br>";
echo "Study PHP at $txt2"; //php 双引号内部可包含变量
echo "My car is a {$cars[0]}"; //用大括号 显式的指定这是变量

$txt1="Learn PHP";
$txt2="w3cschool.cc";
$cars=array("Volvo","BMW","Toyota");

print $txt1;
print "<br>";
print "Study PHP at $txt2";
print "My car is a {$cars[0]}";

?>

PHP5数据类型

String(字符串), Integer(整型), Float(浮点型), Boolean(布尔型), Array(数组), Object(对象), NULL(空值)。

字符串
你可以将任何文本放在单引号和双引号中:

<?php 
$x = "Hello world!";
echo $x;
echo "<br>"; 
$x = 'Hello world!'; //单引号 包括字符串字面量 双引号包含的字符串 可包含变量
echo $x;
?>

整型
在以下实例中我们将测试不同的数字。 PHP var_dump() 函数返回变量的数据类型和值:

<?php 
$x = 5985;
var_dump($x);
echo "<br>"; 
$x = -345; // 负数 
var_dump($x);
echo "<br>"; 
$x = 0x8C; // 十六进制数
var_dump($x);
echo "<br>";
$x = 047; // 八进制数
var_dump($x);
?>

浮点型

<?php 
$x = 10.365;
var_dump($x);
echo "<br>"; 
$x = 2.4e3;
var_dump($x);
echo "<br>"; 
$x = 8E-5;
var_dump($x);
?>

布尔型(注意所有字母都是大写的)

布尔型可以是 TRUE 或 FALSE。

数组

数组可以在一个变量中存储多个值

<?php 
$cars=array("Volvo","BMW","Toyota");
var_dump($cars);
?>

对象

在 PHP 中,对象必须声明。

首先,你必须使用class关键字声明类对象。类是可以包含属性和方法的结构。
然后我们在类中定义数据类型,然后在实例化的类中使用数据类型:

<?php
class Car
{
    var $color;
    function Car($color="green") {
      $this->color = $color;
    }
    function what_color() {
      return $this->color;
    }
}

function print_vars($obj) {
   foreach (get_object_vars($obj) as $prop => $val) {
     echo "\t$prop = $val\n";
   }
}

// instantiate one object
$herbie = new Car("white");

// show herbie properties
echo "\herbie: Properties\n";
print_vars($herbie);

?>

NULL 值
NULL 值表示变量没有值。NULL 是数据类型为 NULL 的值。

<?php
$x="Hello world!";
$x=null;
var_dump($x);
?>

常量

常量是一个简单值的标识符。该值在脚本中不能改变。 (常量名不需要加 $ 修饰符)。

注意: 常量在整个脚本中都可以使用。

设置常量,使用 define() 函数,函数语法如下:

define(string constant_name, mixed value, case_sensitive = true)
该函数有三个参数:

  • constant_name:必选参数,常量名称,即标志符。
  • value:必选参数,常量的值。
  • case_sensitive:可选参数,指定是否大小写敏感,设定为 true 表示不敏感。

以下实例我们创建一个 区分大小写的常量, 常量值为 “Welcome to W3CSchool.cc!”:

<?php
define("GREETING", "Welcome to W3CSchool.cc!");
echo GREETING;
?>

字符串函数和字符串连接

<?php
$txt1="Hello world!";
$txt2="What a nice day!";
echo $txt1 . " " . $txt2; // 字符串连接运算符 .

echo strlen("Hello world!"); //获取字符串长度
echo strpos("Hello world!","world"); //获取子串位置
//字符串中第一个字符的位置是 0
?>

运算符

<?php
//其他运算符略..
//逻辑运算符 ! && || and or xor
//数组运算符 合并: + 比较:== != === !==
$x = array("a" => "red", "b" => "green"); 
$y = array("c" => "blue", "d" => "yellow"); 
$z = $x + $y; // $x 和 $y 数组合并
var_dump($z);
var_dump($x == $y);
var_dump($x === $y);
var_dump($x != $y);
var_dump($x <> $y);
var_dump($x !== $y);
?>

流程控制语句 同js

<?php

$t=date("H");
if ($t<"20")
{
      echo "Have a good day!";
}



$t=date("H");
if ($t<"20")
{
      echo "Have a good day!";
}
else
{
      echo "Have a good night!";
}

$t=date("H");
if ($t<"10")
{
      echo "Have a good morning!";
}
else if ($t<"20")
{
      echo "Have a good day!";
}
else
{
      echo "Have a good night!";
}

$favcolor="red";
switch ($favcolor)
{
      case "red":
      echo "Your favorite color is red!";
      break;
      case "blue":
      echo "Your favorite color is blue!";
      break;
      case "green":
      echo "Your favorite color is green!";
      break;
      default:
      echo "Your favorite color is neither red, blue, or green!";
}

//循环
$i=1;
while($i<=5)
{
      echo "The number is " . $i . "<br>";
      $i++;
}


$i=1;
do
{
      $i++;
      echo "The number is " . $i . "<br>";
}
while ($i<=5);

for ($i=1; $i<=5; $i++)
{
      echo "The number is " . $i . "<br>";
}


$x=array("one","two","three");
foreach ($x as $value)
{
      echo $value . "<br>";
}

?>

数组

<?php
$cars=array("Volvo","BMW","Toyota"); //数组定义 数值数组
echo "I like " . $cars[0] . ", " . $cars[1] . " and " . $cars[2] . "."; //访问数组元素

//数组长度count()
$cars=array("Volvo","BMW","Toyota");
echo count($cars); //count() 函数用于返回数组的长度

//遍历数值数组
$cars=array("Volvo","BMW","Toyota");
$arrlength=count($cars);

for($x=0;$x<$arrlength;$x++)
{
      echo $cars[$x];
      echo "<br>";
}

$age=array("Peter"=>"35","Ben"=>"37","Joe"=>"43");//定义关联数组
echo "Peter is " . $age['Peter'] . " years old.";

//遍历关联数组
$age=array("Peter"=>"35","Ben"=>"37","Joe"=>"43");
foreach($age as $x=>$x_value)
{
      echo "Key=" . $x . ", Value=" . $x_value;
      echo "<br>";
}
?>

数组排序

PHP – 数组排序函数

  • sort() – 对数组进行升序排列
  • rsort() – 对数组进行降序排列
  • asort() – 根据关联数组的值,对数组进行升序排列
  • ksort() – 根据关联数组的键,对数组进行升序排列
  • arsort() – 根据关联数组的值,对数组进行降序排列
  • krsort() – 根据关联数组的键,对数组进行降序排列
 //sort()
$cars=array("Volvo","BMW","Toyota");
sort($cars);

$clength=count($cars);
for($x=0;$x<$clength;$x++)
{
      echo $cars[$x];
      echo "<br>";
}

//rsort()   
$cars=array("Volvo","BMW","Toyota");
rsort($cars);   

//asort() arsort() 关联数组排序 value
$age=array("Peter"=>"35","Ben"=>"37","Joe"=>"43");
asort($age);
arsort($age);

//ksort() krsort() 关联数组排序 key
$age=array("Peter"=>"35","Ben"=>"37","Joe"=>"43");
ksort($age);
krsort($age);
?>

超级全局变量

PHP中预定义了几个超级全局变量(superglobals) ,这意味着它们在一个脚本的全部作用域中都可用。

  • $GLOBALS
  • $_SERVER
  • $_REQUEST
  • $_POST
  • $_GET
  • $_COOKIE
  • $_FILES
  • $_ENV
  • $_SESSION

$GLOBAL 是一个包含了全部变量的全局组合数组。变量的名字就是数组的键。

<?php 
$x = 75; 
$y = 25;
 
function addition() 
{ 
      $GLOBALS['z'] = $GLOBALS['x'] + $GLOBALS['y']; 
}
 
addition(); 
echo $z; 
?>

$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由 Web 服务器创建。不能保证每个服务器都提供全部项目;

<?php 
echo $_SERVER['PHP_SELF']; // /try/demo_source/demo_global_server.php
echo "<br>";
echo $_SERVER['SERVER_NAME']; // w3cschool.cc 
echo "<br>";
echo $_SERVER['HTTP_HOST']; // www.w3cschool.cc
echo "<br>";
echo $_SERVER['HTTP_REFERER']; //来由
echo "<br>";
echo $_SERVER['HTTP_USER_AGENT']; //浏览器
echo "<br>";
echo $_SERVER['SCRIPT_NAME']; // /try/demo_source/demo_global_server.php

//另外$_SERVER对象还包括 SERVER_ADDR, SERVER_PROTOCOL ,REQUEST_METHOD..等属性

?>

$_REQUEST 用于收集HTML表单提交的数据。

<html>
<body>

<form method="post" action="<?php echo $_SERVER['PHP_SELF'];?>">
Name: <input type="text" name="fname">
<input type="submit">
</form>

<?php 
$name = $_REQUEST['fname']; 
echo $name; 
?>

</body>
</html>

$_POST 被广泛应用于收集表单数据,在HTML form标签的指定该属性:”method=”post”

<html>
<body>
<!--页面post给自己-->
<form method="post" action="<?php echo $_SERVER['PHP_SELF'];?>">
Name: <input type="text" name="fname">
<input type="submit">
</form>

<?php 
$name = $_POST['fname']; 
echo $name; 
?>

</body>
</html>

$_GET 同样被广泛应用于收集表单数据,在HTML form标签的指定该属性:”method=”get”。

$_GET 也可以收集URL中发送的数据。

<html>
<body>

<a href="test_get.php?subject=PHP&web=w3cschool.cc">Test $GET</a>

</body>
</html>

//test_get.php

<html>
<body>

<?php 
echo "Study " . $_GET['subject'] . " at " . $_GET['web'];
?>

</body>
</html>

PHP 函数

PHP 的真正威力源自于它的函数。

在 PHP 中,提供了超过 1000 个内建的函数。

<html>
<body>

<?php
function writeName()
{
      echo "Kai Jim Refsnes";
}

echo "My name is ";
writeName();

//函数传参
function writeName($fname,$punctuation)
{
      echo $fname . " Refsnes" . $punctuation . "<br>";
}

echo "My name is ";
writeName("Kai Jim",".");
echo "My sister's name is ";

//函数返回值 
function add($x,$y) 
{ 
 $total=$x+$y; return $total; 
} 
echo "1 + 16 = " . add(1,16); 

?> 
</body> 
</html>

魔术变量

有八个魔术常量它们的值随着它们在代码中的位置改变而改变。

  • _LINE__
    文件中的当前行号。
  • FILE
    文件的完整路径和文件名。如果用在被包含文件中,则返回被包含的文件名。
  • DIR
    文件所在的目录。如果用在被包括文件中,则返回被包括的文件所在的目录。
  • FUNCTION
    函数名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该函数被定义时的名字(区分大小写)。在 PHP 4 中该值总是小写字母的。
<?php
function test() {
      echo  '函数名为:' . __FUNCTION__ ;
}
test();
?>
  • CLASS 类的名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该类被定义时的名字(区分大小写)。

在 PHP 4 中该值总是小写字母的。类名包括其被声明的作用区域(例如 Foo\Bar)。注意自 PHP 5.4 起 CLASS 对 trait 也起作用。当用在 trait 方法中时,CLASS 是调用 trait 方法的类的名字。

<?php
class test {
      function _print() {
            echo '类名为:'  . __CLASS__ . "<br>";
            echo  '函数名为:' . __FUNCTION__ ;
      }
}
$t = new test();
$t->_print();
?>
  • TRAIT Trait 的名字(PHP 5.4.0 新加)。自 PHP 5.4.0 起,PHP 实现了代码复用的一个方法,称为 traits。

Trait 名包括其被声明的作用区域(例如 Foo\Bar)。

从基类继承的成员被插入的 SayWorld Trait 中的 MyHelloWorld 方法所覆盖。其行为 MyHelloWorld 类中定义的方法一致。优先顺序是当前类中的方法会覆盖 trait 方法,而 trait 方法又覆盖了基类中的方法。

<?php
class Base {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait SayWorld {
    public function sayHello() {
        parent::sayHello();
        echo 'World!';
    }
}

class MyHelloWorld extends Base {
    use SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
?>
  • METHOD 类的方法名(PHP 5.0.0 新加)。返回该方法被定义时的名字(区分大小写)
<?php
function test() {
      echo  '函数名为:' . __METHOD__ ;
}
test();
?>
  • NAMESPACE 当前命名空间的名称(区分大小写)。此常量是在编译时定义的(PHP 5.3.0 新增)。
<?php
namespace MyProject;

echo '命名空间为:"', __NAMESPACE__, '"'; // 输出 "MyProject"
?>

命名空间

PHP 命名空间(namespace)是在PHP 5.3中加入的,如果你学过C#和Java,那命名空间就不算什么新事物。 不过在PHP当中还是有着相当重要的意义。

PHP 命名空间可以解决以下两类问题:

  • 用户代码与PHP内部的类/函数/常量之间的名字冲突。
  • 创建别名(或简短)的名称,提高源代码的可读性。

默认情况下,所有常量、类和函数名都放在全局空间下,就和PHP支持命名空间之前一样。
如果一个文件中包含命名空间,它必须在其它所有代码之前声明命名空间

< ?php  
// 定义代码在 'MyProject' 命名空间中  
namespace MyProject;  
 
// ... 代码 ...  

//也可以在同一个文件中定义不同的命名空间代码
namespace MyProject1;  
// MyProject1 命名空间中的PHP代码  
 
namespace MyProject2;  
// MyProject2 命名空间中的PHP代码    
 
// 另一种语法
namespace MyProject3 {  
 // MyProject3 命名空间中的PHP代码    
}  

?>

在声明命名空间之前唯一合法的代码是用于定义源文件编码方式的 declare 语句。所有非 PHP 代码包括空白符都不能出现在命名空间的声明之前。

<?php
declare(encoding='UTF-8'); //定义多个命名空间和不包含在命名空间中的代码
namespace MyProject {
      const CONNECT_OK = 1;
      class Connection { /* ... */ }
      function connect() { /* ... */  }
}

namespace { // 全局代码
      session_start();
      $a = MyProject\connect();
      echo MyProject\Connection::start();
}
?>

以下代码会出现语法错误:

<html>
<?php
namespace MyProject; // 命名空间前出现了“<html>” 会致命错误 - 命名空间必须是程序脚本的第一条语句
?>

子命名空间

<?php
namespace MyProject\Sub\Level;  //声明分层次的单个命名空间
//创建了常量 MyProject\Sub\Level\CONNECT_OK,
//类 MyProject\Sub\Level\Connection 
//和函数 MyProject\Sub\Level\Connect。

const CONNECT_OK = 1;
class Connection { /* ... */ }
function Connect() { /* ... */  }

?>

命名空间使用
PHP 命名空间中的类名可以通过三种方式引用:

  1. 非限定名称,或不包含前缀的类名称,例如 $a=new foo(); 或 foo::staticmethod();。如果当前命名空间是 currentnamespace,foo 将被解析为 currentnamespace\foo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,则 foo 会被解析为foo。 警告:如果命名空间中的函数或常量未定义,则该非限定的函数名称或常量名称会被解析为全局函数名称或常量名称。~~无命名空间前缀的引用,默认在当前命名空间查找,然后到全局命名空间找
  2. 限定名称,或包含前缀的名称,例如 $a = new subnamespace\foo(); 或 subnamespace\foo::staticmethod();。如果当前的命名空间是 currentnamespace,则 foo 会被解析为 currentnamespace\subnamespace\foo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,foo 会被解析为subnamespace\foo。 ~~带相对命名空间前缀的(类似相对路径)引用,自动在开头添加调用代码所处的命名空间
  3. 完全限定名称,或包含了全局前缀操作符的名称,例如, $a = new \currentnamespace\foo(); 或 \currentnamespace\foo::staticmethod();。在这种情况下,foo 总是被解析为代码中的文字名(literal name)currentnamespace\foo。~~带绝对命名空间前缀的引用(类似绝对路径) 引用路径已明确 不用查找

file1.php

<?php
namespace Foo\Bar\subnamespace; 

const FOO = 1;
function foo() {}
class foo
{
    static function staticmethod() {}
}
?>

file2.php

 <?php
namespace Foo\Bar;
include 'file1.php';

const FOO = 2;
function foo() {}
class foo
{
    static function staticmethod() {}
}

/* 非限定名称 */
foo(); // 解析为函数 Foo\Bar\foo
foo::staticmethod(); // 解析为类 Foo\Bar\foo的静态方法staticmethod。
echo FOO; // resolves to constant Foo\Bar\FOO

/* 限定名称 */
subnamespace\foo(); // 解析为函数 Foo\Bar\subnamespace\foo
subnamespace\foo::staticmethod(); // 解析为类 Foo\Bar\subnamespace\foo,
                                  // 以及类的方法 staticmethod
echo subnamespace\FOO; // 解析为常量 Foo\Bar\subnamespace\FOO
                                  
/* 完全限定名称 */
\Foo\Bar\foo(); // 解析为函数 Foo\Bar\foo
\Foo\Bar\foo::staticmethod(); // 解析为类 Foo\Bar\foo, 以及类的方法 staticmethod
echo \Foo\Bar\FOO; // 解析为常量 Foo\Bar\FOO
?>

在命名空间内部访问全局类、函数和常量:

<?php
namespace Foo;

function strlen() {}
const INI_ALL = 3;
class Exception {}

$a = \strlen('hi'); // 调用全局函数strlen
$b = \INI_ALL; // 访问全局常量 INI_ALL
$c = new \Exception('error'); // 实例化全局类 Exception
?>

命名空间和动态语言特征

PHP 命名空间的实现受到其语言自身的动态特征的影响。因此,如果要将下面的代码转换到命名空间中,动态访问元素。

//example.php

<?php
class classname
{
    function __construct()
    {
        echo __METHOD__,"\n";
    }
}
function funcname()
{
    echo __FUNCTION__,"\n";
}
const constname = "global";

$a = 'classname';
$obj = new $a; // prints classname::__construct
$b = 'funcname';
$b(); // prints funcname
echo constant('constname'), "\n"; // prints global
?>
<?php
namespace namespacename;
class classname
{
    function __construct()
    {
        echo __METHOD__,"\n";
    }
}
function funcname()
{
    echo __FUNCTION__,"\n";
}
const constname = "namespaced";

include 'example1.php';//全局命名空间和某个命名空间有相同类 函数 和 常量 分别区别访问

$a = 'classname';
$obj = new $a; // prints classname::__construct
$b = 'funcname';
$b(); // prints funcname
echo constant('constname'), "\n"; // prints global

/* note that if using double quotes, "\\namespacename\\classname" must be used */
$a = '\namespacename\classname';
$obj = new $a; // prints namespacename\classname::__construct
$a = 'namespacename\classname';
$obj = new $a; // also prints namespacename\classname::__construct
$b = 'namespacename\funcname';
$b(); // prints namespacename\funcname
$b = '\namespacename\funcname';
$b(); // also prints namespacename\funcname
echo constant('\namespacename\constname'), "\n"; // prints namespaced
echo constant('namespacename\constname'), "\n"; // also prints namespaced
?>

namespace关键字和__NAMESPACE__常量

<?php
namespace MyProject;

echo '"', __NAMESPACE__, '"'; // 输出 "MyProject"
?>
<?php

echo '"', __NAMESPACE__, '"'; // 输出 ""
?>

使用__NAMESPACE__动态创建名称

<?php
namespace MyProject;

function get($classname)
{
    $a = __NAMESPACE__ . '\\' . $classname;
    return new $a;
}
?>

关键字 namespace 可用来显式访问当前命名空间或子命名空间中的元素。它等价于类中的 self 操作符。

<?php
namespace MyProject;

use blah\blah as mine; // see "Using namespaces: importing/aliasing"

blah\mine(); // calls function blah\blah\mine()
namespace\blah\mine(); // calls function MyProject\blah\mine()

namespace\func(); // calls function MyProject\func()
namespace\sub\func(); // calls function MyProject\sub\func()
namespace\cname::method(); // calls static method "method" of class MyProject\cname
$a = new namespace\sub\cname(); // instantiates object of class MyProject\sub\cname
$b = namespace\CONSTANT; // assigns value of constant MyProject\CONSTANT to $b
?>

在PHP中,别名是通过操作符 use 来实现的. 下面是一个使用所有可能的三种导入方式的例子:

  1. 使用use操作符导入/使用别名
<?php
namespace foo;
use My\Full\Classname as Another;

// 下面的例子与 use My\Full\NSname as NSname 相同
use My\Full\NSname;

// 导入一个全局类
use \ArrayObject;

$obj = new namespace\Another; // 实例化 foo\Another 对象
$obj = new Another; // 实例化 My\Full\Classname 对象
NSname\subns\func(); // 调用函数 My\Full\NSname\subns\func
$a = new ArrayObject(array(1)); // 实例化 ArrayObject 对象
// 如果不使用 "use \ArrayObject" ,则实例化一个 foo\ArrayObject 对象
?>
  1. 一行中包含多个use语句
<?php
use My\Full\Classname as Another, My\Full\NSname;

$obj = new Another; // 实例化 My\Full\Classname 对象
NSname\subns\func(); // 调用函数 My\Full\NSname\subns\func
?>
<p>导入操作是在编译执行的,但动态的类名称、函数名称或常量名称则不是。</p>
<ol>
<li>导入和动态名称</li>
</ol>
<pre>
<?php
use My\Full\Classname as Another, My\Full\NSname;

$obj = new Another; // 实例化一个 My\Full\Classname 对象
$a = 'Another';
$obj = new $a;      // 实际化一个 Another 对象
?>
  1. 导入和动态名称
<?php
use My\Full\Classname as Another, My\Full\NSname;

$obj = new Another; // 实例化一个 My\Full\Classname 对象
$a = 'Another';
$obj = new $a;      // 实际化一个 Another 对象
?>

另外,导入操作只影响非限定名称和限定名称。完全限定名称由于是确定的,故不受导入的影响。

  1. 导入和完全限定名称
<?php
use My\Full\Classname as Another, My\Full\NSname;

$obj = new Another; // instantiates object of class My\Full\Classname
$obj = new \Another; // instantiates object of class Another
$obj = new Another\thing; // instantiates object of class My\Full\Classname\thing
$obj = new \Another\thing; // instantiates object of class Another\thing
?>

使用命名空间:后备全局函数/常量

在一个命名空间中,当 PHP 遇到一个非限定的类、函数或常量名称时,它使用不同的优先策略来解析该名称。

  • 类名称总是解析到当前命名空间中的名称(找不到就报错咯)。因此在访问系统内部或不包含在命名空间中的类名称时,必须使用完全限定名称
  • 函数和常量来说,如果当前命名空间中不存在该函数或常量,PHP 会退而使用全局空间中的函数或常量。
<?php
namespace A\B\C;
class Exception extends \Exception {}

$a = new Exception('hi'); // $a 是类 A\B\C\Exception 的一个对象
$b = new \Exception('hi'); // $b 是类 Exception 的一个对象

$c = new ArrayObject; // 致命错误, 找不到 A\B\C\ArrayObject 类
?>

全局空间
如果没有定义任何命名空间,所有的类与函数的定义都是在全局空间,与 PHP 引入命名空间概念前一样。在名称前加上前缀 \ 表示该名称是全局空间中的名称,即使该名称位于其它的命名空间中时也是如此。

<?php
namespace A\B\C;

/* 这个函数是 A\B\C\fopen */
function fopen() { 
     /* ... */
     $f = \fopen(...); // 调用全局的fopen函数
     return $f;
} 
?>

命名空间的顺序 略..

PHP文件处理

打开文件

$file = fopen($fname, mode) //文件的名称, 打开模式

  • r 只读。在文件的开头开始。
  • r+ 读/写。在文件的开头开始。
  • w 只写。打开并清空文件的内容;如果文件不存在,则创建新文件。
  • w+ 读/写。打开并清空文件的内容;如果文件不存在,则创建新文件。
  • a 追加。打开并向文件末尾进行写操作,如果文件不存在,则创建新文件。
  • a+ 读/追加。通过向文件末尾写内容,来保持文件内容。
  • x 只写。创建新文件。如果文件已存在,则返回 FALSE 和一个错误。
  • x+ 读/写。创建新文件。如果文件已存在,则返回 FALSE 和一个错误。

注释:如果 fopen() 函数无法打开指定文件,则返回 0 (false)。

<?php
//打开文件
$file = fopen("welcome.txt", "r") or exit("Unable to open file!");
//Output a line of the file until the end is reached

//是否到达文件末尾 
while(!feof($file))
{
      echo fgets($file). "<br>"; //逐行读取文件
      //echo fgetc($file); //逐字符读取

}
//关闭文件
fclose($file);
?>

Filesystem 函数

  • basename() 返回路径中的文件名部分。
  • chgrp() 改变文件组。
  • chmod() 改变文件模式。
  • chown() 改变文件所有者。
  • clearstatcache() 清除文件状态缓存。
  • copy() 复制文件。
  • delete() 参见 unlink() 或 unset()
  • dirname() 返回路径中的目录名称部分。
  • disk_free_space() 返回目录的可用空间。
  • disk_total_space() 返回一个目录的磁盘总容量。
  • diskfreespace() disk_free_space() 的别名。
  • fclose() 关闭打开的文件。
  • feof() 测试文件指针是否到了文件末尾。
  • fflush() 向打开的文件刷新缓冲输出。
  • fgetc() 从打开的文件中返回字符。
  • fgetcsv() 从打开的文件中解析一行,校验 CSV 字段。
  • fgets() 从打开的文件中返回一行。
  • fgetss() 从打开的文件中返回一行,并过滤掉 HTML 和 PHP 标签。
  • file() 把文件读入一个数组中。
  • file_exists() 检查文件或目录是否存在。
  • file_get_contents() 把文件读入字符串。~~获取文件内容
  • file_put_contents() 把字符串写入文件。~~写入文件内容
  • fileatime() 返回文件的上次访问时间。
  • filectime() 返回文件的上次修改时间。
  • filegroup() 返回文件的组 ID。
  • fileinode() 返回文件的 inode 编号。
  • filemtime() 返回文件内容的上次修改时间。
  • fileowner() 返回文件的用户 ID (所有者)。
  • fileperms() 返回文件的权限。
  • filesize() 返回文件大小。
  • filetype() 返回文件类型。
  • flock() 锁定或释放文件。
  • fnmatch() 根据指定的模式来匹配文件名或字符串。
  • fopen() 打开一个文件或 URL。
  • fpassthru() 从打开的文件中读数据,直到文件末尾(EOF),并向输出缓冲写结果。
  • fputcsv() 把行格式化为 CSV 并写入一个打开的文件中。
  • fputs() fwrite() 的别名。
  • fread() 读取打开的文件。
  • fscanf() 根据指定的格式对输入进行解析。
  • fseek() 在打开的文件中定位。
  • fstat() 返回关于一个打开的文件的信息。
  • ftell() 返回在打开文件中的当前位置。
  • ftruncate() 把打开文件截断到指定的长度。
  • fwrite() 写入打开的文件。
  • glob() 返回一个包含匹配指定模式的文件名/目录的数组。
  • is_dir() 判断文件是否是一个目录。
  • is_executable() 判断文件是否可执行。
  • is_file() 判断文件是否是常规的文件。
  • is_link() 判断文件是否是连接。
  • is_readable() 判断文件是否可读。
  • is_uploaded_file() 判断文件是否是通过 HTTP POST 上传的。
  • is_writable() 判断文件是否可写。
  • is_writeable() is_writable() 的别名。
  • lchgrp() 改变符号连接的组所有权。
  • lchown() 改变符号连接的用户所有权。
  • link() 创建一个硬连接。
  • linkinfo() 返回有关一个硬连接的信息。
  • lstat() 返回关于文件或符号连接的信息。
  • mkdir() 创建目录。
  • move_uploaded_file() 把上传的文件移动到新位置。
  • parse_ini_file() 解析一个配置文件。
  • parse_ini_string() 解析一个配置字符串。
  • pathinfo() 返回关于文件路径的信息。
  • pclose() 关闭由 popen() 打开的进程。
  • popen() 打开一个进程。
  • readfile() 读取一个文件,并写入到输出缓冲。
  • readlink() 返回符号连接的目标。
  • realpath() 返回绝对路径名。
  • realpath_cache_get() 返回高速缓存条目。
  • realpath_cache_size() 返回高速缓存大小。
  • rename() 重命名文件或目录。
  • rewind() 倒回文件指针的位置。
  • rmdir() 删除空的目录。
  • set_file_buffer() 设置已打开文件的缓冲大小。
  • stat() 返回关于文件的信息。
  • symlink() 创建符号连接。
  • tempnam() 创建唯一的临时文件。
  • tmpfile() 创建唯一的临时文件。
  • touch() 设置文件的访问和修改时间。
  • umask() 改变文件的文件权限。
  • unlink() 删除文件。

PHP JSON

  • json_encode 对变量进行 JSON 编码
  • json_decode 对 JSON 格式的字符串进行解码,转换为 PHP 变量
  • json_last_error 返回最后发生的错误

string json_encode ( $value [, $options = 0 ] )
参数

value: 要编码的值。该函数只对 UTF-8 编码的数据有效。
options:由以下常量组成的二进制掩码:JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK,JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT

<?php
   $arr = array('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5);
   echo json_encode($arr);


    class Emp {
       public $name = "";
       public $hobbies  = "";
       public $birthdate = "";
   }
   $e = new Emp();
   $e->name = "sachin";
   $e->hobbies  = "sports";
   $e->birthdate = date('m/d/Y h:i:s a', "8/5/1974 12:20:03 p");
   $e->birthdate = date('m/d/Y h:i:s a', strtotime("8/5/1974 12:20:03"));

   echo json_encode($e);
?>

mixed json_decode ($json [,$assoc = false [, $depth = 512 [, $options = 0 ]]])
参数

json_string: 待解码的 JSON 字符串,必须是 UTF-8 编码数据

assoc: 当该参数为 TRUE 时,将返回数组,FALSE 时返回对象。

depth: 整数类型的参数,它指定递归深度

options: 二进制掩码,目前只支持 JSON_BIGINT_AS_STRING 。

<?php
   $json = '{"a":1,"b":2,"c":3,"d":4,"e":5}';

   var_dump(json_decode($json));
   var_dump(json_decode($json, true));
?>

每天进步一点点之MySQL必知必会笔记

第一章 了解MySQL

1.1 数据库基础

1.1.1 什么是数据库

数据库:保存有组织的数据的容器(通常是一个文件或一组文件)。

数据库管理系统(DBMS):创建和管理数据库容器的工具。

一般我们使用的是DBMS

1.1.2 表

表:某种特定类型数据的结构化清单。

不要将不同种的数据存放在同一个表中。

数据库中每个表都有一个名字,用来标识自己。此名字是唯一的,这表示数据库中没有其他表具有相同的名字。

注:表名的唯一性取决于多个因素,如数据库名和表名等的结合。这表示,虽然在相同数据库中不能两次使用相同的表名,但在不同的数据库中却可以使用相同的表名。

表具有一些特性,这些特性定义了数据在表中如何存储,如可以存储什么样的数据,数据如何分解,各部分信息如何命名,等等。

模式: 关于数据库和表的布局及特性的信息。

注: 有时,模式用作数据库的同义词。遗憾的是,模式的含义通常在上下文中并不是很清晰。本书中,模式指的是上面给出的定义。

1.1.3 列和数据类型

表由列组成。列中存储着表中某部分的信息。

列( column):表中的一个字段。所有表都是由一个或多个列组成的。

数据类型( datatype) 所容许的数据的类型。每个表列都有相应的数据类型,它限制(或容许)该列中存储的数据。

注: 数据类型限制可存储在列中的数据种类(例如,防止在数值字段中录入字符值)。数据类型还帮助正确地排序数据,并在优化磁盘使用方面起重要的作用。因此,在创建表时必须对数据类型给予特别的关注。

1.1.4 行

表中的数据是按行存储的,所保存的每个记录存储在自己的行内。如果将表想象为网格,网格中垂直的列为表列,水平行为表行。

行( row):表中的一个记录。

1.1.5 主键

主键( primary key):一一列(或一组列),其值能够唯一区分表中每个行。

唯一标识表中每行的这个列(或这组列)称为主键。主键用来表示一个特定的行。没有主键,更新或删除表中特定行很困难,因为没有安全的方法保证只涉及相关的行。

应该总是定义主键,虽然并不总是都需要主键,但大多数数据库设计人员都应保证他们创建的每个表具有一个主键,以便于以后的数据操纵和管理。

表中的任何列都可以作为主键,只要它满足以下条件:(MySQL强制要求的)

 任意两行都不具有相同的主键值;
 每个行都必须具有一个主键值(主键列不允许NULL值)。

主键通常定义在表的一列上,但这并不是必需的,也可以一起使用多个列作为主键。在使用多列作为主键时,上述条件必须应用到构成主键的所有列,所有列值的组合必须是唯一的(但单个列的值可以不唯一)。

除MySQL强制实施的规则外,应该坚持的几个普遍认可的最好习惯为:

 不更新主键列中的值;
 不重用主键列的值;
 不在主键列中使用可能会更改的值。(例如,如果使用一个名字作为主键以标识某个供应商,当该供应商合并和更改其名字时,必须更改这个主键。)

1.2  什么是SQL

SQL (发音为字母S-Q-L或sequel)是结构化查询语言 ( Structured Query Language)的缩写。 SQL是一种专门用来与数据库通信的语言。

注:虽然SQL有标准委员会,但事实上任意两个DBMS实现的SQL都不完全相同。

第二章  MySQL简介

2.1 什么是MySQL

MySQL是一种DBMS,即它是一种数据库软件。

2.1.1 客户机—服务器软件

DBMS可分为两类:一类为基于共享文件系统的DBMS,另一类为基于客户机—服务器的DBMS。 显然MySQL属于后面一种。

客户机—服务器应用分为两个不同的部分。 服务器部分是负责所有数据访问和处理的一个软件。这个软件运行在称为数据库服务器的计算机上。

注: 与数据文件打交道的只有服务器软件。关于数据、数据添加、删除和数据更新的所有请求都由服务器软件完成。这些请求或更改来自运行客户机软件的计算机。 客户机是与用户打交道的软件。例如,如果你请求一个按字母顺序列出的产品表,则客户机软件通过网络提交该请求给服务器软件。服务器软件处理这个请求,根据需要过滤、丢弃和排序数据;然后把结果送回到你的客户机软件。

为了使用MySQL,你需要访问运行MySQL服务器软件的计算机和发布命令到MySQL的客户机软件的计算机。

 服务器软件为MySQL DBMS。你可以在本地安装的副本上运行,也可以连接到运行在你具有访问权的远程服务器上的一个副本。
 客户机可以是MySQL提供的工具、脚本语言(如Perl)、 Web应用开发语言(如ASP、 ColdFusion、 JSP和PHP)、程序设计语言(如C、 C++、 Java)等。

2.2 MySQL工具

2.2.1 mysql命令行实用程序

MySQL选项和参数: mysql -u 用户名 -p -h 主机名 -P 端口。

请注意:

 命令输入在mysql>之后;
 命令用;或\g结束,换句话说,仅按Enter不执行命令;
 输入help或\h获得帮助,也可以输入更多的文本获得特定命令的帮助(如,输入help select获得使用SELECT语句的帮助);
 输入quit或exit退出命令行实用程序。

第三章  使用MySQL

3.1 连接

为了连接到MySQL,需要以下信息:

 主机名(计算机名)——如果连接到本地MySQL服务器, 为localhost;

 端口(如果使用默认端口3306之外的端口);

 一个合法的用户名;

 用户口令(如果需要)。

就是上一章最后讲的内容,这里就不在赘述了。

3.2 选择数据库

在你能执行任意数据库操作前,需要选择一个数据库。为此,可使用USE关键字。

mysql> use learning
  Database changed

注: 记住,必须先使用USE打开数据库,才能读取其中的数据。

3.3 了解数据库和表

可用MySQL的SHOW命令来显示 数据库、表、列、用户、权限等的信息( MySQL从内部表中提取这些信息)。

mysql> show DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| learning           |
+--------------------+
2 rows in set (0.00 sec)

SHOW DATABASES;返回可用数据库的一个列表。包含在这个列表中的可能是MySQL内部使用的数据库(如例子中的mysql和information_schema)。

mysql> SHOW TABLES;
+--------------------+
| Tables_in_learning |
+--------------------+
| customers          |
| orderitems         |
| orders             |
| productnotes       |
| products           |
| vendors            |
+--------------------+
6 rows in set (0.00 sec)

SHOW TABLES;返回当前选择的数据库内可用表的列表。

mysql> show columns from customers;
+--------------+-----------+------+-----+---------+----------------+
| Field        | Type      | Null | Key | Default | Extra          |
+--------------+-----------+------+-----+---------+----------------+
| cust_id      | int(11)   | NO   | PRI | NULL    | auto_increment |
| cust_name    | char(50)  | NO   |     | NULL    |                |
| cust_address | char(50)  | YES  |     | NULL    |                |
| cust_city    | char(50)  | YES  |     | NULL    |                |
| cust_state   | char(5)   | YES  |     | NULL    |                |
| cust_zip     | char(10)  | YES  |     | NULL    |                |
| cust_country | char(50)  | YES  |     | NULL    |                |
| cust_contact | char(50)  | YES  |     | NULL    |                |
| cust_email   | char(255) | YES  |     | NULL    |                |
+--------------+-----------+------+-----+---------+----------------+
9 rows in set (0.00 sec)

SHOW COLUMNS 要 求 给 出 一 个 表 名 ( 这 个 例 子 中 的 FROMcustomers),它对每个字段返回一行,行中包含字段名、数据类型、是否允许NULL、键信息、默认值以及其他信息(如字段cust_id的auto_increment)。

注: MySQL支持用DESCRIBE作为SHOW COLUMNS FROM的一种快捷方式。换句话说, DESCRIBE customers;是SHOW COLUMNS FROM customers;的一种快捷方式。

所支持的其他SHOW语句还有:

 SHOW STATUS,用于显示广泛的服务器状态信息;
 SHOW CREATE DATABASE和SHOW CREATE TABLE,分别用来显示创
建特定数据库或表的MySQL语句;
 SHOW GRANTS,用来显示授予用户(所有用户或特定用户)的安
全权限;
 SHOW ERRORS和SHOW WARNINGS, 用来显示服务器错误或警告消息。

注:可以在mysql命令行实用程序中,执行命令HELP SHOW;显示允许的SHOW语句。

第四章 检 索 数 据

4.1 SELECT语句

SELECT语句了。它的用途是从一个或多个表中检索信息。为了使用SELECT检索表数据,必须至少给出两条信息——想选择什么,以及从什么地方选择。

4.2 检索单个列

mysql> select prod_name from products;
+----------------+
| prod_name      |
+----------------+
| .5 ton anvil   |
| 1 ton anvil    |
| 2 ton anvil    |
| Detonator      |
| Bird seed      |
| Carrots        |
| Fuses          |
| JetPack 1000   |
| JetPack 2000   |
| Oil can        |
| Safe           |
| Sling          |
| TNT (1 stick)  |
| TNT (5 sticks) |
+----------------+
14 rows in set (0.00 sec)

上述语句利用 SELECT语句从 products表中检索一个名为prod_name的列。所需的列名在SELECT关键字之后给出, FROM关键字指出从其中检索数据的表名。

注: 多条SQL语句必须以分号(;)分隔。 MySQL如同多数DBMS一样,不需要在单条SQL语句后加分号。但特定的DBMS可能必须在单条SQL语句后加上分号。当然,如果愿意可以总是加上分号。事实上,即使不一定需要,但加上分号肯定没有坏处。如果你使用的是mysql命令行,必须加上分号来结束SQL语句。

SQL语句和大小写 请注意, SQL语句不区分大小写,因此SELECT与select是相同的。同样,写成Select也没有关系。许多SQL开发人员喜欢对所有SQL关键字使用大写,而对所有列和表名使用小写,这样做使代码更易于阅读和调试。

不过,一定要认识到虽然SQL是不区分大小写的,但有些标识符(如数据库名、表名、列名)可能不同:在MySQL 4.1及之前的版本中,这些标识符默认是区分大小写的;在MySQL 4.1.1版本中,这些标识符默认是不区分大小写的。最佳方式是按照大小写的惯例,且使用时保持一致。

使用空格:在处理SQL语句时,其中所有空格都被忽略。 SQL语句可以在一行上给出,也可以分成许多行。多数SQL开发人员认为将SQL语句分成多行更容易阅读和调试。

4.3 检索多个列

在选择多个列时,一定要在列名之间加上逗号,但最后一个列名后不加。如果在最后一个列名后加了逗号,将出现错误。

mysql> select prod_id,prod_name,prod_price from products;
+---------+----------------+------------+
| prod_id | prod_name      | prod_price |
+---------+----------------+------------+
| ANV01   | .5 ton anvil   |       5.99 |
| ANV02   | 1 ton anvil    |       9.99 |
| ANV03   | 2 ton anvil    |      14.99 |
| DTNTR   | Detonator      |      13.00 |
| FB      | Bird seed      |      10.00 |
| FC      | Carrots        |       2.50 |
| FU1     | Fuses          |       3.42 |
| JP1000  | JetPack 1000   |      35.00 |
| JP2000  | JetPack 2000   |      55.00 |
| OL1     | Oil can        |       8.99 |
| SAFE    | Safe           |      50.00 |
| SLING   | Sling          |       4.49 |
| TNT1    | TNT (1 stick)  |       2.50 |
| TNT2    | TNT (5 sticks) |      10.00 |
+---------+----------------+------------+
14 rows in set (0.00 sec)

4.4 检索所有列

如果给定一个通配符( *),则返回表中所有列。列的顺序一般是列在表定义中出现的顺序。但有时候并不是这样的,表的模式的变化(如添加或删除列)可能会导致顺序的变化。

mysql> select * from orders;
+-----------+---------------------+---------+
| order_num | order_date          | cust_id |
+-----------+---------------------+---------+
|     20005 | 2005-09-01 00:00:00 |   10001 |
|     20006 | 2005-09-12 00:00:00 |   10003 |
|     20007 | 2005-09-30 00:00:00 |   10004 |
|     20008 | 2005-10-03 00:00:00 |   10005 |
|     20009 | 2005-10-08 00:00:00 |   10001 |
+-----------+---------------------+---------+
5 rows in set (0.00 sec)

使用通配符 一般,除非你确实需要表中的每个列,否则最好别使用*通配符。虽然使用通配符可能会使你自己省事,不用明确列出所需列,但检索不需要的列通常会降低检索和应用程序的性能。

检索未知列:使用通配符有一个大优点。由于不明确指定列名(因为星号检索每个列),所以能检索出名字未知的列。

4.5 检索不同的行

想要得到值不同的行,解决办法是使用DISTINCT关键字,顾名思义,此关键字指示MySQL只返回不同的值。

mysql> select DISTINCT vend_id from products;
+---------+
| vend_id |
+---------+
|    1001 |
|    1002 |
|    1003 |
|    1005 |
+---------+
4 rows in set (0.00 sec)

注: DISTINCT关键字,它必须直接放在列名的前面。

不能部分使用DISTINCT DISTINCT关键字应用于所有列而不仅是前置它的列。如果给出SELECT DISTINCT vend_id,prod_price,除非指定的两个列都相同,否则所有行都将被检索出来。

mysql> select DISTINCT vend_id,prod_price from products;
+---------+------------+
| vend_id | prod_price |
+---------+------------+
|    1001 |       5.99 |
|    1001 |       9.99 |
|    1001 |      14.99 |
|    1003 |      13.00 |
|    1003 |      10.00 |
|    1003 |       2.50 |
|    1002 |       3.42 |
|    1005 |      35.00 |
|    1005 |      55.00 |
|    1002 |       8.99 |
|    1003 |      50.00 |
|    1003 |       4.49 |
+---------+------------+
12 rows in set (0.00 sec)

4.6 限制结果

SELECT语句返回所有匹配的行,它们可能是指定表中的每个行。为了返回第一行或前几行,可使用LIMIT子句。

mysql> select prod_name from products limit 5;
+--------------+
| prod_name    |
+--------------+
| .5 ton anvil |
| 1 ton anvil  |
| 2 ton anvil  |
| Detonator    |
| Bird seed    |
+--------------+
5 rows in set (0.00 sec)

也可以指定开始的行数

mysql> select prod_name from products limit 5, 5;
+--------------+
| prod_name    |
+--------------+
| Carrots      |
| Fuses        |
| JetPack 1000 |
| JetPack 2000 |
| Oil can      |
+--------------+
5 rows in set (0.00 sec)

LIMIT 5, 5指示MySQL返回从行5开始的5行。第一个数为开始位置,第二个数为要检索的行数。

所以, 带一个值的LIMIT总是从第一行开始,给出的数为返回的行数。带两个值的LIMIT可以指定从行号为第一个值的位置开始。

行0:检索出来的第一行为行0而不是行1。因此, LIMIT 1, 1将检索出第二行而不是第一行。

在行数不够时:LIMIT中指定要检索的行数为检索的最大行数。如果没有足够的行(例如,给出LIMIT 10, 5,但只有13行), MySQL将只返回它能返回的那么多行。

MySQL 5支持LIMIT的另一种替代语法。 LIMIT4 OFFSET 3意为从行3开始取4行,就像LIMIT 3, 4一样。

4.7 使用完全限定的表名

完全限定的列名就是同时使用表名和列名标识一个列。

mysql> select products.prod_name from products;
+----------------+
| prod_name      |
+----------------+
| .5 ton anvil   |
| 1 ton anvil    |
| 2 ton anvil    |
| Detonator      |
| Bird seed      |
| Carrots        |
| Fuses          |
| JetPack 1000   |
| JetPack 2000   |
| Oil can        |
| Safe           |
| Sling          |
| TNT (1 stick)  |
| TNT (5 sticks) |
+----------------+
14 rows in set (0.00 sec)

完全限定的表名就是同时使用数据库名和表名标识一个表。

mysql> select products.prod_name from learning.products;
+----------------+
| prod_name      |
+----------------+
| .5 ton anvil   |
| 1 ton anvil    |
| 2 ton anvil    |
| Detonator      |
| Bird seed      |
| Carrots        |
| Fuses          |
| JetPack 1000   |
| JetPack 2000   |
| Oil can        |
| Safe           |
| Sling          |
| TNT (1 stick)  |
| TNT (5 sticks) |
+----------------+
14 rows in set (0.00 sec)

第五章 排序检索数据

5.1 排序数据

检索出的数据并不是以纯粹的随机顺序显示的。如果不排序,数据一般将以它在底层表中出现的顺序显示。这可以是数据最初添加到表中的顺序。但是,如果数据后来进行过更新或删除,则此顺序将会受到MySQL重用回收存储空间的影响。

因此,如果不明确控制的话,不能(也不应该)依赖该排序顺序。

关系数据库设计理论认为,如果不明确规定排序顺序,则不应该假定检索出的数据的顺序有意义。

为了明确地排序用SELECT语句检索出的数据,可使用ORDER BY子句。ORDER BY子句取一个或多个列的名字,据此对输出进行排序。

mysql> select prod_name from products order by prod_name;
+----------------+
| prod_name      |
+----------------+
| .5 ton anvil   |
| 1 ton anvil    |
| 2 ton anvil    |
| Bird seed      |
| Carrots        |
| Detonator      |
| Fuses          |
| JetPack 1000   |
| JetPack 2000   |
| Oil can        |
| Safe           |
| Sling          |
| TNT (1 stick)  |
| TNT (5 sticks) |
+----------------+
14 rows in set (0.00 sec)

通过非选择列进行排序:通常, ORDER BY子句中使用的列将是为显示所选择的列。但是,实际上并不一定要这样,用非检索的列排序数据是完全合法的。

5.2 按多个列排序

mysql> select prod_id, prod_price, prod_name from products order by prod_price,prod_name;
+---------+------------+----------------+
| prod_id | prod_price | prod_name      |
+---------+------------+----------------+
| FC      |       2.50 | Carrots        |
| TNT1    |       2.50 | TNT (1 stick)  |
| FU1     |       3.42 | Fuses          |
| SLING   |       4.49 | Sling          |
| ANV01   |       5.99 | .5 ton anvil   |
| OL1     |       8.99 | Oil can        |
| ANV02   |       9.99 | 1 ton anvil    |
| FB      |      10.00 | Bird seed      |
| TNT2    |      10.00 | TNT (5 sticks) |
| DTNTR   |      13.00 | Detonator      |
| ANV03   |      14.99 | 2 ton anvil    |
| JP1000  |      35.00 | JetPack 1000   |
| SAFE    |      50.00 | Safe           |
| JP2000  |      55.00 | JetPack 2000   |
+---------+------------+----------------+
14 rows in set (0.00 sec)

重要的是理解在按多个列排序时,排序完全按所规定的顺序进行。换句话说,对于上述例子中的输出,仅在多个行具有相同的prod_price值时才对产品按prod_name进行排序。如果prod_price列中所有的值都是唯一的,则不会按prod_name排序。

5.3 指定排序方向

进行降序排序,必须指定DESC关键字。

mysql> select prod_id,prod_price,prod_name from products order by prod_price desc;
+---------+------------+----------------+
| prod_id | prod_price | prod_name      |
+---------+------------+----------------+
| JP2000  |      55.00 | JetPack 2000   |
| SAFE    |      50.00 | Safe           |
| JP1000  |      35.00 | JetPack 1000   |
| ANV03   |      14.99 | 2 ton anvil    |
| DTNTR   |      13.00 | Detonator      |
| FB      |      10.00 | Bird seed      |
| TNT2    |      10.00 | TNT (5 sticks) |
| ANV02   |       9.99 | 1 ton anvil    |
| OL1     |       8.99 | Oil can        |
| ANV01   |       5.99 | .5 ton anvil   |
| SLING   |       4.49 | Sling          |
| FU1     |       3.42 | Fuses          |
| FC      |       2.50 | Carrots        |
| TNT1    |       2.50 | TNT (1 stick)  |
+---------+------------+----------------+
14 rows in set (0.00 sec)

对多个列排序:

mysql> select prod_id,prod_price,prod_name from products order by prod_price desc, prod_name;
+---------+------------+----------------+
| prod_id | prod_price | prod_name      |
+---------+------------+----------------+
| JP2000  |      55.00 | JetPack 2000   |
| SAFE    |      50.00 | Safe           |
| JP1000  |      35.00 | JetPack 1000   |
| ANV03   |      14.99 | 2 ton anvil    |
| DTNTR   |      13.00 | Detonator      |
| FB      |      10.00 | Bird seed      |
| TNT2    |      10.00 | TNT (5 sticks) |
| ANV02   |       9.99 | 1 ton anvil    |
| OL1     |       8.99 | Oil can        |
| ANV01   |       5.99 | .5 ton anvil   |
| SLING   |       4.49 | Sling          |
| FU1     |       3.42 | Fuses          |
| FC      |       2.50 | Carrots        |
| TNT1    |       2.50 | TNT (1 stick)  |
+---------+------------+----------------+
14 rows in set (0.00 sec)

DESC关键字只应用到直接位于其前面的列名。 在多个列上降序排序 如果想在多个列上进行降序排序, 必须对每个列指定DESC关键字。

与DESC相反的关键字是ASC ( ASCENDING),在升序排序时可以指定它。升序是排序时默认的。

区分大小写和排序顺序 在对文本性的数据进行排序时, A与a相同吗? a位于B之前还是位于Z之后?这些问题不是理论问题,其答案取决于数据库如何设置。
在字典 ( dictionary)排序顺序中,A被视为与a相同,这是MySQL(和大多数数据库管理系统)的默认行为。但是,许多数据库管理员能够在需要时改变这种行为(如果你的数据库包含大量
外语字符,可能必须这样做)。
这里,关键的问题是,如果确实需要改变这种排序顺序,用简单的ORDER BY子句做不到。你必须请求数据库管理员的帮助。

使用ORDER BY和LIMIT的组合,能够找出一个列中最高或最低的值。如:

mysql> select prod_price from products order by prod_price desc limit 1;
+------------+
| prod_price |
+------------+
|      55.00 |
+------------+
1 row in set (0.00 sec)

ORDER BY子句的位置 在给出ORDER BY子句时,应该保证它位于FROM子句之后。如果使用LIMIT,它必须位于ORDER BY之后。使用子句的次序不对将产生错误消息。

第六章 过滤数据

6.1 使用WHERE子句

在SELECT语句中,数据根据WHERE子句中指定的搜索条件进行过滤。WHERE子句在表名( FROM子句)之后给出。

mysql> select prod_name,prod_price from products where prod_price=2.5;
+---------------+------------+
| prod_name     | prod_price |
+---------------+------------+
| Carrots       |       2.50 |
| TNT (1 stick) |       2.50 |
+---------------+------------+
2 rows in set (0.00 sec)

SQL过滤与应用过滤:数据也可以在应用层过滤。为此目的, SQL的SELECT语句为客户机应用检索出超过实际所需的数据,然后客户机代码对返回数据进行循环,以提取出需要的行。但是,这种做法极度影响了效率。 并且使所创建的应用完全不具备可伸缩性。此外,如果在客户机上过滤数据,服务器不得不通过网络发送多余的数据,这将导致网络带宽的浪费。

WHERE子句的位置 在同时使用ORDER BY和WHERE子句时,应该让ORDER BY位于WHERE之后, 否则将会产生错误。

6.2 WHERE子句操作符

MySQL支持下表列出的所有条件操作符。

操 作 符 说 明
= 等于
<>  不等于
!= 不等于
小于
<= 小于等于
大于
>= 大于等于
BETWEEN 在指定的两个值之间

6.2.1 检查单个值

mysql> select prod_name, prod_price from products where prod_name='fuses';
+-----------+------------+
| prod_name | prod_price |
+-----------+------------+
| Fuses     |       3.42 |
+-----------+------------+
1 row in set (0.00 sec)

MySQL在执行匹配时默认不区分大小写,所以fuses与Fuses匹配。

小于的例子:

mysql> select prod_name, prod_price from products where prod_price<10;
+---------------+------------+
| prod_name     | prod_price |
+---------------+------------+
| .5 ton anvil  |       5.99 |
| 1 ton anvil   |       9.99 |
| Carrots       |       2.50 |
| Fuses         |       3.42 |
| Oil can       |       8.99 |
| Sling         |       4.49 |
| TNT (1 stick) |       2.50 |
+---------------+------------+
7 rows in set (0.00 sec)

小于等于的例子:

mysql> select prod_name, prod_price from products where prod_price<=10;
+----------------+------------+
| prod_name      | prod_price |
+----------------+------------+
| .5 ton anvil   |       5.99 |
| 1 ton anvil    |       9.99 |
| Bird seed      |      10.00 |
| Carrots        |       2.50 |
| Fuses          |       3.42 |
| Oil can        |       8.99 |
| Sling          |       4.49 |
| TNT (1 stick)  |       2.50 |
| TNT (5 sticks) |      10.00 |
+----------------+------------+
9 rows in set (0.00 sec)

6.2.2 不匹配检查

mysql> select vend_id,prod_name from products where vend_id<>1003;
+---------+--------------+
| vend_id | prod_name    |
+---------+--------------+
|    1001 | .5 ton anvil |
|    1001 | 1 ton anvil  |
|    1001 | 2 ton anvil  |
|    1002 | Fuses        |
|    1005 | JetPack 1000 |
|    1005 | JetPack 2000 |
|    1002 | Oil can      |
+---------+--------------+
7 rows in set (0.00 sec)

何时使用引号:单引号用来限定字符串。如果将值与串类型的列进行比较,则需要限定引号。用来与数值列进行比较的值不用引号。

mysql> select vend_id,prod_name from products where vend_id!=1003;
+---------+--------------+
| vend_id | prod_name    |
+---------+--------------+
|    1001 | .5 ton anvil |
|    1001 | 1 ton anvil  |
|    1001 | 2 ton anvil  |
|    1002 | Fuses        |
|    1005 | JetPack 1000 |
|    1005 | JetPack 2000 |
|    1002 | Oil can      |
+---------+--------------+
7 rows in set (0.00 sec)

6.2.3 范围值检查

为了检查某个范围的值,可使用BETWEEN操作符。其语法与其他WHERE子句的操作符稍有不同,因为它需要两个值,即范围的开始值和结束值。

mysql> select prod_name,prod_price from products where prod_price between 5 and 10;
+----------------+------------+
| prod_name      | prod_price |
+----------------+------------+
| .5 ton anvil   |       5.99 |
| 1 ton anvil    |       9.99 |
| Bird seed      |      10.00 |
| Oil can        |       8.99 |
| TNT (5 sticks) |      10.00 |
+----------------+------------+
5 rows in set (0.00 sec)

在使用BETWEEN时,必须指定两个值——所需范围的低端值和高端值。这两个值必须用AND关键字分隔。 BETWEEN匹配范围中所有的值,包括指定的开始值和结束值。

6.2.4 空值检查

在创建表时,表设计人员可以指定其中的列是否可以不包含值。在一个列不包含值时,称其为包含空值NULL。

NULL:无值( no value),它与字段包含0、空字符串或仅仅包含空格不同。

SELECT语句有一个特殊的WHERE子句,可用来检查具有NULL值的列。这个WHERE子句就是IS NULL子句。

mysql> select cust_id from customers where cust_email is null;
+---------+
| cust_id |
+---------+
|   10002 |
|   10005 |
+---------+
2 rows in set (0.00 sec)

NULL与不匹配 在通过过滤选择出不具有特定值的行时,你可能希望返回具有NULL值的行。但是,不行。因为未知具有特殊的含义,数据库不知道它们是否匹配,所以在匹配过滤或不匹配过滤时不返回它们。

因此,在过滤数据时,一定要验证返回数据中确实给出了被过滤列具有NULL的行。

mysql> select cust_id,cust_email from customers where cust_email<>'sam@yosemite.com';
+---------+---------------------+
| cust_id | cust_email          |
+---------+---------------------+
|   10001 | ylee@coyote.com     |
|   10003 | rabbit@wascally.com |
+---------+---------------------+
2 rows in set (0.01 sec)

第七章 数据过滤

7.1 组合WHERE子句

7.1.1 AND操作符

为了进行更强的过滤控制, MySQL允许给出多个WHERE子句。这些子句可以两种方式使用:以AND子句的方式或OR子句的方式使用。

操作符( operator):用来联结或改变WHERE子句中的子句的关键字。也称为逻辑操作符( logical operator) 。

mysql> select prod_id,prod_price,prod_name from products where vend_id=1003 and prod_price<=10;
+---------+------------+----------------+
| prod_id | prod_price | prod_name      |
+---------+------------+----------------+
| FB      |      10.00 | Bird seed      |
| FC      |       2.50 | Carrots        |
| SLING   |       4.49 | Sling          |
| TNT1    |       2.50 | TNT (1 stick)  |
| TNT2    |      10.00 | TNT (5 sticks) |
+---------+------------+----------------+
5 rows in set (0.01 sec)

这条SELECT语句中的WHERE子句包含两个条件,并且用AND关键字联结它们。 AND指示DBMS只返回满足所有给定条件的行。

AND:用在WHERE子句中的关键字,用来指示检索满足所有给定条件的行。

上述例子中使用了只包含一个关键字AND的语句,把两个过滤条件组合在一起。还可以添加多个过滤条件,每添加一条就要使用一个AND。

7.1.2 OR操作符

OR操作符与AND操作符不同,它指示MySQL检索匹配任一条件的行。

OR操作符告诉DBMS匹配任一条件而不是同时匹配两个条件。

mysql> select prod_name,prod_price from products where vend_id=1002 or vend_id=1003;
+----------------+------------+
| prod_name      | prod_price |
+----------------+------------+
| Detonator      |      13.00 |
| Bird seed      |      10.00 |
| Carrots        |       2.50 |
| Fuses          |       3.42 |
| Oil can        |       8.99 |
| Safe           |      50.00 |
| Sling          |       4.49 |
| TNT (1 stick)  |       2.50 |
| TNT (5 sticks) |      10.00 |
+----------------+------------+
9 rows in set (0.00 sec)

OR:WHERE子句中使用的关键字,用来表示检索匹配任一给定条件的行。

7.1.3 计算次序——and 和or的计算优先级

mysql> select prod_name,prod_price from products where vend_id=1002 or vend_id=1003 and prod_price>=10;
+----------------+------------+
| prod_name      | prod_price |
+----------------+------------+
| Detonator      |      13.00 |
| Bird seed      |      10.00 |
| Fuses          |       3.42 |
| Oil can        |       8.99 |
| Safe           |      50.00 |
| TNT (5 sticks) |      10.00 |
+----------------+------------+
6 rows in set (0.00 sec)

出现上述情况是由于SQL(像多数语言一样)在处理OR操作符前,优先处理AND操作符。就是and的优先级高于or。

此问题的解决方法是使用圆括号明确地分组相应的操作符。请看下面的SELECT语句及输出:

mysql> select prod_name,prod_price from products where (vend_id=1002 or vend_id=1003) and prod_price>=10;
+----------------+------------+
| prod_name      | prod_price |
+----------------+------------+
| Detonator      |      13.00 |
| Bird seed      |      10.00 |
| Safe           |      50.00 |
| TNT (5 sticks) |      10.00 |
+----------------+------------+
4 rows in set (0.00 sec)

在上述例子中, 由于圆括号具有较AND或OR操作符高的计算次序, DBMS首先过滤圆括号内的OR条件。

在WHERE子句中使用圆括号 任何时候使用具有AND和OR操作符的WHERE子句,都应该使用圆括号明确地分组操作符。不要过分依赖默认计算次序,即使它确实是你想要的东西也是如此。使用圆括号没有什么坏处,它能消除歧义。

7.2 IN操作符

IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。 IN取合法值的由逗号分隔的清单,全都括在圆括号中。

mysql> select prod_name,prod_price from products where vend_id in (1002, 1003) order by prod_name;
+----------------+------------+
| prod_name      | prod_price |
+----------------+------------+
| Bird seed      |      10.00 |
| Carrots        |       2.50 |
| Detonator      |      13.00 |
| Fuses          |       3.42 |
| Oil can        |       8.99 |
| Safe           |      50.00 |
| Sling          |       4.49 |
| TNT (1 stick)  |       2.50 |
| TNT (5 sticks) |      10.00 |
+----------------+------------+
9 rows in set (0.00 sec)

IN操作符完成与OR相同的功能

为什么要使用IN操作符?其优点具体如下。
 在使用长的合法选项清单时, IN操作符的语法更清楚且更直观。
 在使用IN时,计算的次序更容易管理(因为使用的操作符更少)。
 IN操作符一般比OR操作符清单执行更快。
 IN的最大优点是可以包含其他SELECT语句,使得能够更动态地建立WHERE子句。第14章将对此进行详细介绍。

IN WHERE子句中用来指定要匹配值的清单的关键字,功能与OR相当。

7.3 NOT操作符

WHERE子句中的NOT操作符有且只有一个功能,那就是否定它之后所跟的任何条件。 NOT WHERE子句中用来否定后跟条件的关键字。

mysql> select prod_name,prod_price from products where vend_id not in (1002, 1003) order by prod_name;
+--------------+------------+
| prod_name    | prod_price |
+--------------+------------+
| .5 ton anvil |       5.99 |
| 1 ton anvil  |       9.99 |
| 2 ton anvil  |      14.99 |
| JetPack 1000 |      35.00 |
| JetPack 2000 |      55.00 |
+--------------+------------+
5 rows in set (0.00 sec)

MySQL中的 NOT MySQL 支 持 使 用 NOT 对 IN 、 BETWEEN 和EXISTS子句取反,这与多数其他DBMS允许使用NOT对各种条件取反有很大的差别

第八章 用通配符进行过滤

8.1 LIKE操作符

通配符( wildcard) 用来匹配值的一部分的特殊字符。

搜索模式( search pattern)由字面值、通配符或两者组合构成的搜索条件。

为在搜索子句中使用通配符,必须使用LIKE操作符。 LIKE指示MySQL,后跟的搜索模式利用通配符匹配而不是直接相等匹配进行比较。

谓词 操作符何时不是操作符?答案是在它作为谓词( predicate)时。从技术上说, LIKE是谓词而不是操作符。虽然最终的结果是相同的,但应该对此术语有所了解,以免在SQL文档中遇到此术语时不知道。

8.1.1 百分号( %)通配符

最常使用的通配符是百分号( %)。在搜索串中, %表示任何字符出现任意次数。

mysql> select prod_id, prod_name from products where prod_name like 'jet%';
+---------+--------------+
| prod_id | prod_name    |
+---------+--------------+
| JP1000  | JetPack 1000 |
| JP2000  | JetPack 2000 |
+---------+--------------+
2 rows in set (0.00 sec)

%告诉MySQL接受jet之后的任意字符,不管它有多少字符。

区分大小写:根据MySQL的配置方式,搜索可以是区分大小写的。如果区分大小写, ‘jet%’与JetPack 1000将不匹配。

通配符可在搜索模式中任意位置使用,并且可以使用多个通配符。

mysql> select prod_id, prod_name from products where prod_name like '%anvil%';
+---------+--------------+
| prod_id | prod_name    |
+---------+--------------+
| ANV01   | .5 ton anvil |
| ANV02   | 1 ton anvil  |
| ANV03   | 2 ton anvil  |
+---------+--------------+
3 rows in set (0.00 sec)

通配符也可以出现在搜索模式的中间,虽然这样做不太有用。下面的例子找出以s起头以e结尾的所有产品:

mysql> select prod_name from products where prod_name like 's%e';
+-----------+
| prod_name |
+-----------+
| Safe      |
+-----------+
1 row in set (0.00 sec)

重要的是要注意到,除了一个或多个字符外, %还能匹配0个字符。 %代表搜索模式中给定位置的0个、 1个或多个字符。

注意尾空格 尾空格可能会干扰通配符匹配。例如,在保存词anvil 时 , 如 果 它 后 面 有 一 个 或 多 个 空 格 , 则 子 句 WHEREprod_name LIKE ‘%anvil’将不会匹配它们,因为在最后的l后有多余的字符。解决这个问题的一个简单的办法是在搜索模式最后附加一个%。一个更好的办法是使用函数(第11章将会介绍)去掉首尾空格。

注意NULL:虽然似乎%通配符可以匹配任何东西,但有一个例外,即NULL。即使是WHERE prod_name LIKE ‘%’也不能匹配用值NULL作为产品名的行。

8.1.2 下划线( _)通配符

另一个有用的通配符是下划线( _)。下划线的用途与%一样,但下划线只匹配单个字符而不是多个字符。

mysql> select prod_id,prod_name from products where prod_name like '_ ton anvil';
+---------+-------------+
| prod_id | prod_name   |
+---------+-------------+
| ANV02   | 1 ton anvil |
| ANV03   | 2 ton anvil |
+---------+-------------+
2 rows in set (0.00 sec)

.5 ton anvil产品没有匹配,因为搜索模式要求匹配两个通配符而不是一个。对照一下,下面的SELECT语句使用%通配符,返回三行产品:

mysql> select prod_id,prod_name from products where prod_name like '% ton anvil';
+---------+--------------+
| prod_id | prod_name    |
+---------+--------------+
| ANV01   | .5 ton anvil |
| ANV02   | 1 ton anvil  |
| ANV03   | 2 ton anvil  |
+---------+--------------+
3 rows in set (0.00 sec)

与%能匹配0个字符不一样, _总是匹配一个字符,不能多也不能少。

8.2 使用通配符的技巧

通配符搜索的处理一般要比前面讨论的其他搜索所花时间更长。这里给出一些使用通配符要记住的技巧。

 不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。
 在确实需要使用通配符时,除非绝对有必要,否则不要把它们用在搜索模式的开始处。把通配符置于搜索模式的开始处,搜索起来是最慢的。
 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。

第九章 用正则表达式进行搜索

9.2 使用MySQL正则表达式

MySQL用WHERE子句对正则表达式提供了初步的支持,允许你指定正则表达式,过滤SELECT检索出的数据。

仅为正则表达式语言的一个子集:如果你熟悉正则表达式, 需要注意: MySQL仅支持多数正则表达式实现的一个很小的子集。

9.2.1 基本字符匹配

mysql> select prod_name from products where prod_name regexp '1000' order by prod_name;
+--------------+
| prod_name    |
+--------------+
| JetPack 1000 |
+--------------+
1 row in set (0.00 sec)
mysql> select prod_name from products where prod_name regexp '.000' order by prod_name;
+--------------+
| prod_name    |
+--------------+
| JetPack 1000 |
| JetPack 2000 |
+--------------+
2 rows in set (0.00 sec)

这里使用了正则表达式.000。 .是正则表达式语言中一个特殊的字符。它表示匹配任意一个字符。

LIKE与REGEXP 在LIKE和REGEXP之间有一个重要的差别。请看以下两条语句:

mysql> select prod_name from products where prod_name like '1000' order by prod_name;
  Empty set (0.00 sec)
mysql> select prod_name from products where prod_name regexp '1000' order by prod_name;
+--------------+
| prod_name    |
+--------------+
| JetPack 1000 |
+--------------+
1 row in set (0.00 sec)

LIKE匹配整个列。如果被匹配的文本在列值中出现, LIKE将不会找到它,相应的行也不被返回(除非使用通配符)。而REGEXP在列值内进行匹配,如果被匹配的文本在列值中出现, REGEXP将会找到它,相应的行将被返回。这是一个非常重要的差别。

匹配不区分大小写 MySQL中的正则表达式匹配(自版本3.23.4后)不区分大小写(即,大写和小写都匹配)。为区分大小写,可使用BINARY关键字,如WHERE prod_name REGEXPBINARY ‘JetPack .000’。

9.2.2 进行OR匹配

为搜索两个串之一(或者为这个串,或者为另一个串),使用|,如下所示:

mysql> select prod_name from products where prod_name regexp '1000|2000' order by prod_name;
+--------------+
| prod_name    |
+--------------+
| JetPack 1000 |
| JetPack 2000 |
+--------------+
2 rows in set (0.00 sec)

使用|从功能上类似于在SELECT语句中使用OR语句, 多个OR条件可并入单个正则表达式。

两个以上的OR条件:可以给出两个以上的OR条件。例如,’1000 | 2000 | 3000’将匹配1000或2000或3000。

9.2.3 匹配几个字符之一

可通过指定一组用[和]括起来的字符来完成选择其中之一的工作。

mysql> select prod_name from products where prod_name regexp '[123] Ton' order by prod_name;
+-------------+
| prod_name   |
+-------------+
| 1 ton anvil |
| 2 ton anvil |
+-------------+
2 rows in set (0.00 sec)

[]是另一种形式的OR语句。 事实上,正则表达式[123]Ton为[1|2|3]Ton的缩写,也可以使用后者。

mysql> select prod_name from products where prod_name regexp '[1|2|3] Ton' order by prod_name;
+-------------+
| prod_name   |
+-------------+
| 1 ton anvil |
| 2 ton anvil |
+-------------+
2 rows in set (0.00 sec)

注意:[]这种形式跟|的区别。

mysql> select prod_name from products where prod_name regexp '1|2|3 Ton' order by prod_name;
+---------------+
| prod_name     |
+---------------+
| 1 ton anvil   |
| 2 ton anvil   |
| JetPack 1000  |
| JetPack 2000  |
| TNT (1 stick) |
+---------------+
5 rows in set (0.01 sec)

|应该是匹配整个字符,而[]是括号中的字符跟后边的字符拼接之后在进行匹配。

除非把字符|括在一个集合中,否则它将应用于整个串。

字符集合也可以被否定,即,它们将匹配除指定字符外的任何东西。为否定一个字符集,在集合的开始处放置一个^即可。因此,尽管[123]匹配字符1、 2或3,但[^123]却匹配除这些字符外的任何东西。注意^号在括号内和括号外的区别。

9.2.4 匹配范围

集合可用来定义要匹配的一个或多个字符。

[0123456789]

为简化这种类型的集合,可使用-来定义一个范围。下面的式子功能上等同于上述数字列表:

[0-9]

所谓的范围匹配就是将上节所说的进行一个简单表示而已,其实本质是一个东西。

范围不限于完整的集合, [1-3]和[6-9]也是合法的范围。此外,范围不一定只是数值的, [a-z]匹配任意字母字符。

mysql> select prod_name from products where prod_name regexp '[1-5] Ton' order by prod_name;
+--------------+
| prod_name    |
+--------------+
| .5 ton anvil |
| 1 ton anvil  |
| 2 ton anvil  |
+--------------+
3 rows in set (0.00 sec)

9.2.5 匹配特殊字符

正则表达式语言由具有特定含义的特殊字符构成。我们已经看到.、[]、|和-等,还有其他一些字符。那么匹配这些字符要怎么实现呢?

mysql> select vend_name from vendors where vend_name regexp '.' order by vend_name;
+----------------+
| vend_name      |
+----------------+
| ACME           |
| Anvils R Us    |
| Furball Inc.   |
| Jet Set        |
| Jouets Et Ours |
| LT Supplies    |
+----------------+
6 rows in set (0.00 sec)

.匹配任意字符,因此每个行都被检索出来。为了匹配特殊字符,必须用\\为前导。\\-表示查找-, \\.表示查找.。

注意:这里跟其他地方不一样的,需要用两个斜杠。(其他地方大部分用一个斜杠表示转义)(如果这时只用了一个斜杠,那么效果跟没有斜杠是一样的)

mysql> select vend_name from vendors where vend_name regexp '\\.' order by vend_name;
+--------------+
| vend_name    |
+--------------+
| Furball Inc. |
+--------------+
1 row in set (0.00 sec)

这种处理就是所谓的转义( escaping),正则表达式内具有特殊意义的所有字符都必须以这种方式转义。这包括.、 |、 []以及迄今为止使用过的其他特殊字符。

\\也用来引用元字符(具有特殊含义的字符)

空白元字符

元 字 符 说 明
\\f 换页
\\n 换行
\\r 回车
\\t 制表
\\v 纵向制表

匹配\ 为了匹配反斜杠( \)字符本身,需要使用\\\。

\或\\? 多数正则表达式实现使用单个反斜杠转义特殊字符,以便能使用这些字符本身。但MySQL要求两个反斜杠 ( MySQL自己解释一个,正则表达式库解释另一个)。

9.2.6 匹配字符类

存在找出你自己经常使用的数字、所有字母字符或所有数字字母字符等的匹配。为更方便工作,可以使用预定义的字符集,称为字符类

说明
[:alnum:] 任意字母和数字(同[a-zA-Z0-9])
[:alpha:] 任意字符(同[a-zA-Z])
[:blank:] 空格和制表(同[\\t])
[:cntrl:] ASCII控制字符( ASCII 0到31和127)
[:digit:] 任意数字(同[0-9])
[:graph:] 与[:print:]相同,但不包括空格
[:lower:] 任意小写字母(同[a-z])
[:print:] 任意可打印字符
[:punct:] 既不在[:alnum:]又不在[:cntrl:]中的任意字符
[:space:] 包括空格在内的任意空白字符(同[\\f\\n\\r\\t\\v])
[:upper:] 任意大写字母(同[A-Z])
[:xdigit:] 任意十六进制数字(同[a-fA-F0-9])

9.2.7 匹配多个实例

目前为止使用的所有正则表达式都试图匹配单次出现。

下表9-3列出的正则表达式重复元字符可以完成连续多次匹配的问题

字符说明
* 0个或多个匹配
+ 1个或多个匹配(等于{1,})
? 0个或1个匹配(等于{0,1})
{n} 指定数目的匹配
{n,} 不少于指定数目的匹配
{n,m} 匹配数目的范围(m不超过255)
mysql> select prod_name from products where prod_name regexp '\\([0-9] sticks?\\)' order by prod_name;
+----------------+
| prod_name      |
+----------------+
| TNT (1 stick)  |
| TNT (5 sticks) |
+----------------+
2 rows in set (0.00 sec)

正则表达式\\([0-9] sticks?\\)需要解说一下。 \\(匹配(,[0-9]匹配任意数字(这个例子中为1和5), sticks?匹配stick和sticks( s后的?使s可选,因为?匹配它前面的任何字符的0次或1次出
现), \\)匹配)。没有?,匹配stick和sticks会非常困难。

注意:?只对前边的一个字符进行重复匹配。用小括号括起来的也算是一个字符。

以下是另一个例子。

mysql> select prod_name from products where prod_name regexp '[[:digit:]]{4}' order by prod_name;
+--------------+
| prod_name    |
+--------------+
| JetPack 1000 |
| JetPack 2000 |
+--------------+
2 rows in set (0.00 sec)

注意:这里[[:digit:]]有两层括号,如果只有一层括号。则匹配不到任何值。[:digit:]其实是相当于0-9出现的,所以一定外边还要有一层括号。

mysql> select prod_name from products where prod_name regexp '[:digit:]{4}' order by prod_name;
  Empty set (0.00 sec)

9.2.8 定位符

为了匹配特定位置的文本,需要使用下表列出的定位符。

字符说明
^ 文本的开始
$ 文本的结尾
[[:<:]] 词的开始
[[:>:]] 词的结尾

如果你想找出以一个数(包括以小数点开始的数)开始的所有产品,怎么办?简单搜索[0-9\\.](或[[:digit:]\\.])不行,因为它将在文本内任意位置查找匹配。解决办法是使用^定位符

mysql> select prod_name from products where prod_name regexp '^[0-9\\.]' order by prod_name;
+--------------+
| prod_name    |
+--------------+
| .5 ton anvil |
| 1 ton anvil  |
| 2 ton anvil  |
+--------------+
3 rows in set (0.00 sec)

^的双重用途:^有两种用法。在集合中(用[和]定义),用它来否定该集合,否则,用来指串的开始处。

使REGEXP起类似LIKE的作用 本章前面说过, LIKE和REGEXP的不同在于, LIKE匹配整个串而REGEXP匹配子串。利用定位符,通过用^开始每个表达式,用$结束每个表达式,可以使REGEXP的作用与LIKE一样。

简单的正则表达式测试 可以在不使用数据库表的情况下用SELECT来测试正则表达式。REGEXP检查总是返回0 (没有匹配)或1(匹配)。可以用带文字串的REGEXP来测试表达式,并试验它们。相应的语法如下:

mysql> select 'hello' regexp '[[:digit:]]';
+------------------------------+
| 'hello' regexp '[[:digit:]]' |
+------------------------------+
|                            0 |
+------------------------------+
1 row in set (0.00 sec)

这个例子显然将返回0(因为文本hello中没有数字)。

第十章 创建计算字段

10.1 计算字段

计算字段可以计算出数据库中不存在的字段。与前面各章介绍过的列不同,计算字段并不实际存在于数据库表中。计算字段是运行时在SELECT语句内创建的。

重要的是要注意到,只有数据库知道SELECT语句中哪些列是实际的表列,哪些列是计算字段。从客户机(如应用程序)的角度来看,计算字段的数据是以与其他列的数据相同的方式返回的。

客户机与服务器的格式:可在SQL语句内完成的许多转换和格式化工作都可以直接在客户机应用程序内完成。但一般来说,在数据库服务器上完成这些操作比在客户机中完成要快得多,因为DBMS是设计来快速有效地完成这种处理的。

10.2 拼接字段

拼接( concatenate) 将值联结到一起构成单个值。

解决办法是把两个列拼接起来。在MySQL的SELECT语句中,可使用Concat()函数来拼接两个列。

MySQL的不同之处:多数DBMS使用+或||来实现拼接,MySQL则使用Concat()函数来实现。当把SQL语句转换成MySQL语句时一定要把这个区别铭记在心。

mysql> select concat(vend_name, '(', vend_country, ')') from vendors order by vend_name;
+-------------------------------------------+
| concat(vend_name, '(', vend_country, ')') |
+-------------------------------------------+
| ACME(USA)                                 |
| Anvils R Us(USA)                          |
| Furball Inc.(USA)                         |
| Jet Set(England)                          |
| Jouets Et Ours(France)                    |
| LT Supplies(USA)                          |
+-------------------------------------------+
6 rows in set (0.00 sec)

Concat()拼接串,即把多个串连接起来形成一个较长的串。Concat()需要一个或多个指定的串,各个串之间用逗号分隔。

在第8章中曾提到通过删除数据右侧多余的空格来整理数据,这可以使用MySQL的RTrim()函数来完成,如下所示:

mysql> select concat(rtrim(vend_name), '(', rtrim(vend_country), ')') from vendors order by vend_name;
+---------------------------------------------------------+
| concat(rtrim(vend_name), '(', rtrim(vend_country), ')') |
+---------------------------------------------------------+
| ACME(USA)                                               |
| Anvils R Us(USA)                                        |
| Furball Inc.(USA)                                       |
| Jet Set(England)                                        |
| Jouets Et Ours(France)                                  |
| LT Supplies(USA)                                        |
+---------------------------------------------------------+
6 rows in set (0.00 sec)

RTrim()函数去掉值右边的所有空格。通过使用RTrim(),各个列都进行了整理。

Trim函数 MySQL除了支持RTrim()(正如刚才所见,它去掉串右边的空格),还支持LTrim()(去掉串左边的空格)以及Trim()(去掉串左右两边的空格)。

使用别名

如果仅在SQL查询工具中查看一下结果,这样没有什么不好。但是,一个未

命名的列不能用于客户机应用中,因为客户机没有办法引用它。(这里所说的客户机的应用应该也是在同一条语句中的,不同的语句如果调用别名是不能找到的。客户机应用的意思就是返回到客户端的时候可以用这个别名进行调用这个值(但是还是在一条语句中的结果))

SQL支持列别名。 别名( alias) 是一个字段或值的替换名。别名用AS关键字赋予。请看下面的SELECT语句:

mysql> select concat(vend_name, '(', vend_country, ')') as vend_title from vendors order by vend_name;
+------------------------+
| vend_title             |
+------------------------+
| ACME(USA)              |
| Anvils R Us(USA)       |
| Furball Inc.(USA)      |
| Jet Set(England)       |
| Jouets Et Ours(France) |
| LT Supplies(USA)       |
+------------------------+
6 rows in set (0.00 sec)

SELECT语句本身与以前使用的相同,只不过这里的语句中计算字段之后跟了文本AS vend_title。它指示SQL创建一个包含指定计算的名为vend_title的计算字段。从输出中可以看到,结果与以前的相同,但现在列名为vend_title,任何客户机应用都可以按名引用这个列,就像它是一个实际的表列一样。

别名的其他用途:别名还有其他用途。常见的用途包括在实际的表列名包含不符合规定的字符(如空格)时重新命名它,在原来的名字含混或容易误解时扩充它,等等。

导出列:别名有时也称为导出列( derived column),不管称为什么,它们所代表的都是相同的东西。

10.3 执行算术计算

计算字段的另一常见用途是对检索出的数据进行算术计算。

mysql> select prod_id, quantity, item_price, quantity*item_price as expanded_price from orderitems where order_num = 20005;
+---------+----------+------------+----------------+
| prod_id | quantity | item_price | expanded_price |
+---------+----------+------------+----------------+
| ANV01   |       10 |       5.99 |          59.90 |
| ANV02   |        3 |       9.99 |          29.97 |
| TNT2    |        5 |      10.00 |          50.00 |
| FB      |        1 |      10.00 |          10.00 |
+---------+----------+------------+----------------+
4 rows in set (0.00 sec)

MySQL支持加减乘除基本算术操作符。此外,圆括号可用来区分优先顺序。

如何测试计算:SELECT提供了测试和试验函数与计算的一个很好的办法。虽然SELECT通常用来从表中检索数据,但可以省略FROM子句以便简单地访问和处理表达式。例如, SELECT 3*2;将返回6, SELECT Trim(‘abc’);将返回abc,而SELECT Now()利用Now()函数返回当前日期和时间。通过这些例子,可以明白如何根据需要使用SELECT进行试验。

第十一章 使用数据处理函数

11.1 函数

多数SQL语句是可移植的,在SQL实现之间有差异时,这些差异通常不那么难处理。而函数的可移植性却不强。 几乎每种主要的DBMS的实现都支持其他实现不支持的函数,而且有时差异还很大。

因此函数是一把双刃剑,一方面它有着更高的运行效率,另一方面它的可移植性又不强。

因此如果你决定使用函数,应该保证做好代码注释,以便以后你(或其他人)能确切地知道所编写SQL代码的含义。便于做到移植代码。

11.2 使用函数

大多数SQL实现支持以下类型的函数。

 用于处理文本串(如删除或填充值,转换值为大写或小写)的文本函数。
 用于在数值数据上进行算术操作(如返回绝对值,进行代数运算)的数值函数。
 用于处理日期和时间值并从这些值中提取特定成分(例如,返回两个日期之差,检查日期有效性等)的日期和时间函数。
 返回DBMS正使用的特殊信息(如返回用户登录信息,检查版本细节)的系统函数。

11.2.1 文本处理函数

下面是另一个例子,这次使用Upper()函数:

mysql> select vend_name, upper(vend_name) as vend_name_upcase from vendors order by vend_name;
+----------------+------------------+
| vend_name      | vend_name_upcase |
+----------------+------------------+
| ACME           | ACME             |
| Anvils R Us    | ANVILS R US      |
| Furball Inc.   | FURBALL INC.     |
| Jet Set        | JET SET          |
| Jouets Et Ours | JOUETS ET OURS   |
| LT Supplies    | LT SUPPLIES      |
+----------------+------------------+
6 rows in set (0.00 sec)

下表列出了某些常用的文本处理函数。

函数 说明
Left() 返回串左边的字符
Length() 返回串的长度
Locate() 找出串的一个子串
Lower() 将串转换为小写
LTrim() 去掉串左边的空格
Right() 返回串右边的字符
RTrim() 去掉串右边的空格
Soundex() 返回串的SOUNDEX值
SubString() 返回子串的字符
Upper() 将串转换为大写

SOUNDEX是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。 SOUNDEX考虑了类似的发音字符和音节,使得能对串进行发音比较而不是字母比较。

mysql> select soundex('china');
+------------------+
| soundex('china') |
+------------------+
| C500             |
+------------------+
1 row in set (0.00 sec)
mysql> select soundex('yihuiwen')=soundex('yinhuiwen');
+------------------------------------------+
| soundex('yihuiwen')=soundex('yinhuiwen') |
+------------------------------------------+
|                                        1 |
+------------------------------------------+
1 row in set (0.00 sec)

customers表中有一个顾客Coyote Inc.,其联系名为Y.Lee。但如果这是输入错误,此联系名实际应该是Y.Lie,怎么办?显然,按正确的联系名搜索不会返回数据,如下所示:

mysql> select cust_name, cust_contact from customers where cust_contact='Y.Lie';
  Empty set (0.00 sec)

现在试一下使用Soundex()函数进行搜索,它匹配所有发音类似于Y.Lie的联系名:

mysql> select cust_name, cust_contact from customers where soundex(cust_contact)=soundex('Y.Lie');
+-------------+--------------+
| cust_name   | cust_contact |
+-------------+--------------+
| Coyote Inc. | Y Lee        |
+-------------+--------------+
1 row in set (0.00 sec)

11.2.2 日期和时间处理函数

一般,应用程序不使用用来存储日期和时间的格式,因此日期和时间函数总是被用来读取、统计和处理这些值。由于这个原因,日期和时间函数在MySQL语言中具有重要的作用。

下表列出了某些常用的日期和时间处理函数。

函数 说明
AddDate() 增加一个日期(天、周等)
AddTime() 增加一个时间(时、分等)
CurDate() 返回当前日期
CurTime() 返回当前时间
Date() 返回日期时间的日期部分
DateDiff() 计算两个日期之差
Date_Add() 高度灵活的日期运算函数
Date_Format() 返回一个格式化的日期或时间串
Day() 返回一个日期的天数部分
DayOfWeek() 对于一个日期,返回对应的星期几
Hour() 返回一个时间的小时部分
Minute() 返回一个时间的分钟部分
Month() 返回一个日期的月份部分
Now() 返回当前日期和时间
Second() 返回一个时间的秒部分
Time() 返回一个日期时间的时间部分
Year() 返回一个日期的年份部分

首先需要注意的是MySQL使用的日期格式。

日期必须为格式yyyy-mm-dd。 虽然其他的日期格式可能也行,但这是首选的日期格式,因为它排除了多义性。

应该总是使用4位数字的年份 支持2位数字的年份, MySQL处理00-69为2000-2069,处理70-99为1970-1999。虽然它们可能是打算要的年份,但使用完整的4位数字年份更可靠,因为MySQL不必做出任何假定。

mysql> select cust_id,order_num from orders where order_date='2005-09-01';
+---------+-----------+
| cust_id | order_num |
+---------+-----------+
|   10001 |     20005 |
+---------+-----------+
1 row in set (0.00 sec)

但是,使用WHERE order_date = ‘2005-09-01’可靠吗? order_date的数据类型为datetime。这种类型存储日期及时间值。样例表中的值全都具有时间值00:00:00,但实际中很可能并不总是这样。如果用当前日期和时间存储订单日期(因此你不仅知道订单日期,还知道下 订 单 当 天 的 时 间 ), 怎 么 办 ? 比 如 , 存 储 的 order_date 值 为2005-09-01 11:30:05,则WHERE order_date = ‘2005-09-01’失败。即使给出具有该日期的一行,也不会把它检索出来,因为WHERE匹配失败。

解决办法是指示MySQL仅将给出的日期与列中的日期部分进行比较,而不是将给出的日期与整个列值进行比较。为此,必须使用Date()函数。 Date(order_date)指示MySQL仅提取列的日期部分,更可靠的SELECT语句为:

mysql> select cust_id,order_num from orders where date(order_date)='2005-09-01';
+---------+-----------+
| cust_id | order_num |
+---------+-----------+
|   10001 |     20005 |
+---------+-----------+
1 row in set (0.00 sec)

如果要的是日期,请使用Date():如果你想要的仅是日期,则使用Date()是一个良好的习惯,即使你知道相应的列只包含日期也是如此。这样,如果由于某种原因表中以后有日期和时间值,你的SQL代码也不用改变。当然,也存在一个Time()函数,在你只想要时间时应该使用它。

如果你想检索出2005年9月下的所有订单,怎么办?简单的相等测试不行,因为它也要匹配月份中的天数。有几种解决办法,其中之一如下所示:

mysql> select cust_id,order_num from orders where date(order_date) between '2005-09-01' and '2005-09-30';
+---------+-----------+
| cust_id | order_num |
+---------+-----------+
|   10001 |     20005 |
|   10003 |     20006 |
|   10004 |     20007 |
+---------+-----------+
3 rows in set (0.00 sec)

但是上面的方法有一个弊端,就是你需要知道那个月的第一天跟最后一天的日期。(相当蛋疼的一件事)

so:

mysql> select cust_id,order_num from orders where year(order_date) = 2005 and month(order_date) = 9;
+---------+-----------+
| cust_id | order_num |
+---------+-----------+
|   10001 |     20005 |
|   10003 |     20006 |
|   10004 |     20007 |
+---------+-----------+
3 rows in set (0.00 sec)

11.2.3 数值处理函数

在主要DBMS的函数中,数值函数是最一致最统一的函数。下表列出一些常用的数值处理函数。

函数 说明
Abs() 返回一个数的绝对值
Cos() 返回一个角度的余弦
Exp() 返回一个数的指数值
Mod() 返回除操作的余数
Pi() 返回圆周率
Rand() 返回一个随机数
Sin() 返回一个角度的正弦
Sqrt() 返回一个数的平方根
Tan() 返回一个角度的正切

第十二章 汇 总 数 据

12.1 聚集函数

为了得到汇总信息, MySQL给出了5个聚集函数

聚集函数( aggregate function) 运行在行组上,计算和返回单个值的函数。

函数 说明
AVG() 返回某列的平均值
COUNT() 返回某列的行数
MAX() 返回某列的最大值
MIN() 返回某列的最小值
SUM() 返回某列值之和

12.1.1 AVG()函数

AVG()通过对表中行数计数并计算特定列值之和,求得该列的平均值。 AVG()可用来返回所有列的平均值,也可以用来返回特定列或行的平均值。(???特定行的平均值???)

mysql> select avg(prod_price) as avg_price from products;
+-----------+
| avg_price |
+-----------+
| 16.133571 |
+-----------+
1 row in set (0.00 sec)

AVG()也可以用来确定特定列或行的平均值。 下面的例子返回特定供应商所提供产品的平均价格:

mysql> select avg(prod_price) as avg_price from products where vend_id=1003;
+-----------+
| avg_price |
+-----------+
| 13.212857 |
+-----------+
1 row in set (0.00 sec)

只用于单个列 AVG()只能用来确定特定数值列的平均值,而且列名必须作为函数参数给出。为了获得多个列的平均值,必须使用多个AVG()函数。

NULL值 AVG()函数忽略列值为NULL的行。

12.1.2 COUNT()函数

COUNT()函数进行计数。可利用COUNT()确定表中行的数目或符合特定条件的行的数目。

COUNT()函数有两种使用方式。
‰ 使用COUNT(*)对表中行的数目进行计数, 不管表列中包含的是空值( NULL)还是非空值。
‰ 使用COUNT(column)对特定列中具有值的行进行计数,忽略NULL值。

mysql> select count(*) as num_cust from customers;
+----------+
| num_cust |
+----------+
|        5 |
+----------+
1 row in set (0.00 sec)

指定列名的计数:

mysql> select count(cust_email) as num_cust from customers;
+----------+
| num_cust |
+----------+
|        3 |
+----------+
1 row in set (0.00 sec)

在此例子中, cust_email的计数为3 (表示5个客户中只有3个客户有电子邮件地址)。

NULL值:如果指定列名,则指定列的值为空的行被COUNT()函数忽略,但如果COUNT()函数中用的是星号( *),则不忽略。

12.1.3 MAX()函数

MAX()返回指定列中的最大值。 MAX()要求指定列名。

mysql> select max(prod_price) as max_price from products;
+-----------+
| max_price |
+-----------+
|     55.00 |
+-----------+
1 row in set (0.00 sec)

对非数值数据使用MAX() 虽然MAX()一般用来找出最大的数值或日期值,但MySQL允许将它用来返回任意列中的最大值,包括返回文本列中的最大值。在用于文本数据时,如果数据按相应的列排序,则MAX()返回最后一行。

NULL值 MAX()函数忽略列值为NULL的行。

12.1.4 MIN()函数

MIN()的功能正好与MAX()功能相反,它返回指定列的最小值。与MAX()一样, MIN()要求指定列名。

mysql> select min(prod_price) as min_price from products;
+-----------+
| min_price |
+-----------+
|      2.50 |
+-----------+
1 row in set (0.00 sec)

对非数值数据使用MIN() MIN()函数与MAX()函数类似,MySQL允许将它用来返回任意列中的最小值,包括返回文本列中的最小值。在用于文本数据时,如果数据按相应的列排序,则MIN()返回最前面的行。

NULL值 MIN()函数忽略列值为NULL的行。

12.1.5 SUM()函数

SUM()用来返回指定列值的和(总计)。

mysql> select sum(quantity) as items_ordered from orderitems where order_num=20005;
+---------------+
| items_ordered |
+---------------+
|            19 |
+---------------+
1 row in set (0.00 sec)

SUM()也可以用来合计计算值。

mysql> select sum(item_price*quantity) as total_prices from orderitems where order_num=20005;
+--------------+
| total_prices |
+--------------+
|       149.87 |
+--------------+
1 row in set (0.00 sec)

在多个列上进行计算:如上例所示,利用标准的算术操作符,所有聚集函数都可用来执行多个列上的计算。

NULL值 SUM()函数忽略列值为NULL的行。

12.2 聚集不同值

以上5个聚集函数都可以如下使用:
‰ 对所有的行执行计算,指定ALL参数或不给参数(因为ALL是默认行为);
‰ 只包含不同的值,指定DISTINCT参数。

mysql> select avg(distinct prod_price) as avg_price from products where vend_id=1003;
+-----------+
| avg_price |
+-----------+
| 15.998000 |
+-----------+
1 row in set (0.00 sec)

注意:如果指定列名,则DISTINCT只能用于COUNT()。DISTINCT不能用于COUNT(*),因此不允许使用COUNT( DISTINCT),否则会产生错误。类似地, DISTINCT必须使用列名,不能用
于计算或表达式。

将DISTINCT用于MIN()和MAX() 虽然DISTINCT从技术上可用于MIN()和MAX(),但这样做实际上没有价值。一个列中的最小值和最大值不管是否包含不同值都是相同的。

12.3 组合聚集函数

mysql> select count(*) as num_item, min(prod_price) as price_min, max(prod_price) as price_max, avg(prod_price) as price_avg from products;
+----------+-----------+-----------+-----------+
| num_item | price_min | price_max | price_avg |
+----------+-----------+-----------+-----------+
|       14 |      2.50 |     55.00 | 16.133571 |
+----------+-----------+-----------+-----------+
1 row in set (0.00 sec)

取别名:在指定别名以包含某个聚集函数的结果时,不应该使用表中实际的列名。虽然这样做并非不合法,但使用唯一的名字会使你的SQL更易于理解和使用 (以及将来容易排除故障)。

第十三章  分 组 数 据

13.1 数据分组

分组允许把数据分为多个逻辑组,以便能对每个组进行聚集计算。

13.2 创建分组

分组是在SELECT语句的GROUP BY子句中建立的。

mysql> select vend_id, count(*) as num_prods from products group by vend_id;
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
|    1001 |         3 |
|    1002 |         2 |
|    1003 |         7 |
|    1005 |         2 |
+---------+-----------+
4 rows in set (0.00 sec)

注意:mysql中当select中的字段,在分组后表示不唯一,就会出错。如果表示唯一的话就没有问题的 。如下:

mysql> select vend_id,prod_id,count(*) from products group by vend_id;
  ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'learning.products.prod_id' 
which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

这里同一个vend_id中有好几个prod_id,所以这里就会出错(按照常理分析也是这样的,这样你没办法表示的说)。

mysql> select prod_name,count(*) from products group by prod_id;
+----------------+----------+
| prod_name      | count(*) |
+----------------+----------+
| .5 ton anvil   |        1 |
| 1 ton anvil    |        1 |
| 2 ton anvil    |        1 |
| Detonator      |        1 |
| Bird seed      |        1 |
| Carrots        |        1 |
| Fuses          |        1 |
| JetPack 1000   |        1 |
| JetPack 2000   |        1 |
| Oil can        |        1 |
| Safe           |        1 |
| Sling          |        1 |
| TNT (1 stick)  |        1 |
| TNT (5 sticks) |        1 |
+----------------+----------+
14 rows in set (0.00 sec)

上面这个例子就可以的,因为同一个prod_id只有一个prod_name。

但是从规范上来说还是不要select,group by中没有的列。

GROUP BY子句指示MySQL分组数据,然后对每个组而不是整个结果集进行聚集。

在具体使用GROUP BY子句前,需要知道一些重要的规定。

 GROUP BY子句可以包含任意数目的列。这使得能对分组进行嵌套,为数据分组提供更细致的控制。

 如果在GROUP BY子句中嵌套了分组,数据将在最后规定的分组上进行汇总。换句话说,在建立分组时,指定的所有列都一起计算(所以不能从个别的列取回数据)。

 GROUP BY子句中列出的每个列都必须是检索列或有效的表达式(但不能是聚集函数)。如果在SELECT中使用表达式,则必须在GROUP BY子句中指定相同的表达式。不能使用别名。

 除聚集计算语句外, SELECT语句中的每个列都必须在GROUP BY子句中给出。

 如果分组列中具有NULL值,则NULL将作为一个分组返回。如果列中有多行NULL值,它们将分为一组。

 GROUP BY子句必须出现在WHERE子句之后, ORDER BY子句之前。

使用ROLLUP 使用WITH ROLLUP关键字,可以得到每个分组以及每个分组汇总级别(针对每个分组)的值,如下所示:

mysql> select vend_id, count(*) as num_prods from products group by vend_id with rollup;
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
|    1001 |         3 |
|    1002 |         2 |
|    1003 |         7 |
|    1005 |         2 |
|    NULL |        14 |
+---------+-----------+
5 rows in set (0.00 sec)

注意到上面的例子中比没有with rollup关键字时多了一行。

这一列就是对所有的列进行汇总的一个信息。

mysql> select vend_id,prod_id,count(*) from products group by vend_id,prod_id with rollup;;
+---------+---------+----------+
| vend_id | prod_id | count(*) |
+---------+---------+----------+
|    1001 | ANV01   |        1 |
|    1001 | ANV02   |        1 |
|    1001 | ANV03   |        1 |
|    1001 | NULL    |        3 |
|    1002 | FU1     |        1 |
|    1002 | OL1     |        1 |
|    1002 | NULL    |        2 |
|    1003 | DTNTR   |        1 |
|    1003 | FB      |        1 |
|    1003 | FC      |        1 |
|    1003 | SAFE    |        1 |
|    1003 | SLING   |        1 |
|    1003 | TNT1    |        1 |
|    1003 | TNT2    |        1 |
|    1003 | NULL    |        7 |
|    1005 | JP1000  |        1 |
|    1005 | JP2000  |        1 |
|    1005 | NULL    |        2 |
|    NULL | NULL    |       14 |
+---------+---------+----------+
19 rows in set (0.00 sec)

上面的例子比没有with rollup关键字时多了5行,分别就是对每个分组的汇总,由于vend_id分组有四个,所以对这个四个分组有四个信息行,加上对全部数据的汇总,加起来就一共是5个信息行了。

需要注意的是,使用有 WITH ROLLUP 子句的 GROUP BY 语句时,不能再使用 ORDER BY 语句对结果集进行排序,如果对返回的结果顺序不满意,需要应用程序获得结果后在程序中进行排序。

mysql> select vend_id,prod_id,count(*) from products group by vend_id,prod_id with rollup order by vend_id;
  ERROR 1221 (HY000): Incorrect usage of CUBE/ROLLUP and ORDER BY

13.3 过滤分组

在MySQL中WHERE不能过滤分组,因为WHERE过滤指定的是行而不是分组。事实上, WHERE没有分组的概念。

那么要用什么过滤分组呢?

MySQL为此目的提供了另外的子句,那就是HAVING子句。 HAVING非常类似于WHERE。事实上,目前为止所学过的所有类型的WHERE子句都可以用HAVING来替代。唯一的差别是WHERE过滤行,而HAVING过滤分组。

HAVING支持所有WHERE操作符 在第6章和第7章中,我们学习了WHERE子句的条件(包括通配符条件和带多个操作符的子句)。所学过的有关WHERE的所有这些技术和选项都适用于HAVING。它们的句法是相同的,只是关键字有差别。

mysql> select cust_id, count(*) as orders from orders group by cust_id having count(*) >= 2;
+---------+--------+
| cust_id | orders |
+---------+--------+
|   10001 |      2 |
+---------+--------+
1 row in set (0.01 sec)

HAVING和WHERE的差别:这里有另一种理解方法,WHERE在数据分组前进行过滤, HAVING在数据分组后进行过滤。这是一个重要的区别, WHERE排除的行不包括在分组中。这可能会改变计算值,从而影响HAVING子句中基于这些值过滤掉的分组。

对于where子句和having子句是可以配合使用的。首先通过where子句将符合条件的行过滤出来。然后用having将符合条件的分组过滤出来。

mysql> select vend_id, count(*) as num_prods from products where prod_price >= 10 group by vend_id having count(*) >= 2;
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
|    1003 |         4 |
|    1005 |         2 |
+---------+-----------+
2 rows in set (0.00 sec)

在上述例子中,如果没有WHERE子句,将会多检索出两行(供应商1002,销售的所有产品价格都在10以下;供应商1001,销售3个产品,但只有一个产品的价格大于等于10):

mysql> select vend_id, count(*) as num_prods from products group by vend_id having count(*) >= 2; 
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
|    1001 |         3 |
|    1002 |         2 |
|    1003 |         7 |
|    1005 |         2 |
+---------+-----------+
4 rows in set (0.00 sec)

13.4 分组和排序

ORDER BY跟GROUP BY的区别

ORDER BY GROUP BY
排序产生的输出 分组行。但输出可能不是分组的顺序
任意列都可以使用(甚至非选择的列也可以使用) 只可能使用选择列或表达式列,而且必须使用每个选择列表达式
不一定需要 如果与聚集函数一起使用列(或表达式),则必须使用

应该提供明确的ORDER BY子句,即使其效果等同于GROUP BY子句也是如此。

不要忘记ORDER BY 一般在使用GROUP BY子句时,应该也给出ORDER BY子句。这是保证数据正确排序的唯一方法。千万不要仅依赖GROUP BY排序数据。

mysql> select order_num,sum(quantity*item_price) as ordertotal from orderitems group by order_num having sum(quantity*item_price) >= 50;
+-----------+------------+
| order_num | ordertotal |
+-----------+------------+
|     20005 |     149.87 |
|     20006 |      55.00 |
|     20007 |    1000.00 |
|     20008 |     125.00 |
+-----------+------------+
4 rows in set (0.00 sec)

 

mysql> select order_num,sum(quantity*item_price) as ordertotal from orderitems group by order_num having sum(quantity*item_price) >= 50 
  order by ordertotal;
+-----------+------------+
| order_num | ordertotal |
+-----------+------------+
|     20006 |      55.00 |
|     20008 |     125.00 |
|     20005 |     149.87 |
|     20007 |    1000.00 |
+-----------+------------+
4 rows in set (0.00 sec)

13.5 SELECT子句顺序

SELECT子句及其顺序

子 句 说 明 是否必须使用
SELECT 要返回的列或表达式
FROM 从中检索数据的表 仅在从表选择数据时使用,只有在select一些表达式的时候不需要(如:select 1+1;)
WHERE 行级过滤
GROUP BY 分组说明 仅在按组计算聚集时使用(如果出现列名和聚集函数一起时,就必须要使用)
HAVING 组级过滤
ORDER BY 输出排序顺序
LIMIT 要检索的行数

第十四章 使用子查询

14.1 子查询

SQL还允许创建子查询( subquery) ,即嵌套在其他查询中的查询。

14.2 利用子查询进行过滤

mysql> select cust_id from orders where order_num in (select order_num from orderitems where prod_id='TNT2');
+---------+
| cust_id |
+---------+
|   10001 |
|   10004 |
+---------+
2 rows in set (0.00 sec)

上面的例子就是子查询了。

在SELECT语句中,子查询总是从内向外处理。在处理上面的SELECT语句时, MySQL实际上执行了两个操作。

首先,它执行下面的查询:

select order_num from orderitems where prod_id='TNT2'

此查询返回两个订单号: 20005和20007。然后,这两个值以IN操作符要求的逗号分隔的格式传递给外部查询的WHERE子句。外部查询变成:

select cust_id from orders where order_num in (20005, 20007);

格式化SQL:包含子查询的SELECT语句难以阅读和调试,特别是它们较为复杂时更是如此。可以把子查询分解为多行并且适当地进行缩进,能极大地简化子查询的使用。

mysql> select cust_name,cust_contact 
    -> from customers
    -> where cust_id in (select cust_id 
    ->                   from orders 
    ->                   where order_num in (select order_num from orderitems 
    ->                                       where prod_id='TNT2'));
+----------------+--------------+
| cust_name      | cust_contact |
+----------------+--------------+
| Coyote Inc.    | Y Lee        |
| Yosemite Place | Y Sam        |
+----------------+--------------+
2 rows in set (0.00 sec)

上面可以看到还可以嵌套三层子查询。

可见,在WHERE子句中使用子查询能够编写出功能很强并且很灵活的SQL语句。对于能嵌套的子查询的数目没有限制,不过在实际使用时由于性能的限制,不能嵌套太多的子查询。

列必须匹配:在WHERE子句中使用子查询(如这里所示),应该保证SELECT语句具有与WHERE子句中相同数目的列。通常,子查询将返回单个列并且与单个列匹配,但如果需要也可以使用多个列。

虽然子查询一般与IN操作符结合使用,但也可以用于测试等于( =)、不等于( <>)等。

子查询和性能:这里给出的代码有效并获得所需的结果。但是,使用子查询并不总是执行这种类型的数据检索的最有效的方法。

14.3 作为计算字段使用子查询

使用子查询的另一方法是创建计算字段。

mysql> select cust_name,
    ->        cust_state,
    ->        (select count(*) from orders where orders.cust_id=customers.cust_id) as orders
    ->        from customers
    ->        order by cust_name;
+----------------+------------+--------+
| cust_name      | cust_state | orders |
+----------------+------------+--------+
| Coyote Inc.    | MI         |      2 |
| E Fudd         | IL         |      1 |
| Mouse House    | OH         |      0 |
| Wascals        | IN         |      1 |
| Yosemite Place | AZ         |      1 |
+----------------+------------+--------+
5 rows in set (0.00 sec)

orders是一个计算字段,它是由圆括号中的子查询建立的。该子查询对检索出的每个客户执行一次。在此例子中,该子查询执行了5次,因为检索出了5个客户。

子查询中的WHERE子句与前面使用的WHERE子句稍有不同,因为它使用了完全限定列名(在第4章中首次提到)。因为他要将orders表中cust_id跟customers表中cust_id进行匹配。

相关子查询( correlated subquery):涉及外部查询的子查询。

任何时候只要列名可能有多义性,就必须使用这种语法(表名和列名由一个句点分隔)。

虽然子查询在构造这种SELECT语句时极有用,但必须注意限制有歧义性的列名

不止一种解决方案:正如本章前面所述,虽然这里给出的样例代码运行良好,但它并不是解决这种数据检索的最有效的方法。在后面的章节中我们还要遇到这个例子。

逐渐增加子查询来建立查询:在写子查询的时候,应该逐步增加子查询,这样可以对每一个子查询进行检验,不容易出错。

第十五章  联 结 表

15.1 联结

SQL最强大的功能之一就是能在数据检索查询的执行中联结( join)表。联结是利用SQL的SELECT能执行的最重要的操作,

15.1.1 关系表(设计MySQL时的一个原则)

相同数据出现多次决不是一件好事,此因素是关系数据库设计的基础。关系表的设计就是要保证把信息分解成多个表,一类数据一个表。各表通过某些常用的值(即关系设计中的关系( elational) )互相关联。

建立两个表,一个存储供应商信息,另一个存储产品信息。 vendors表包含所有供应商信息,每个供应商占一行,每个供应商具有唯一的标识。此标识称为主键( primary key) (在第1章中首次
提到),可以是供应商ID或任何其他唯一值。
products表只存储产品信息,它除了存储供应商ID( vendors表的主键)外不存储其他供应商信息。vendors表的主键又叫作products的外键,它将vendors表与products表关联,利用供应商ID能从vendors表中找出相应供应商的详细信息。

外键( foreign key) 外键为某个表中的一列,它包含另一个表的主键值,定义了两个表之间的关系。
这样做的好处如下:
 供应商信息不重复,从而不浪费时间和空间;
 如果供应商信息变动,可以只更新vendors表中的单个记录,相关表中的数据不用改动;
 由于数据无重复,显然数据是一致的,这使得处理数据更简单。

总之,关系数据可以有效地存储和方便地处理。因此,关系数据库的可伸缩性远比非关系数据库要好。

可伸缩性( scale):能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序称之为可伸缩性好( scale well) 。

15.1.2 为什么要使用联结

分解数据为多个表能更有效地存储,更方便地处理,并且具有更大的可伸缩性。但是如果数据存储在多个表中,怎样用单条SELECT语句检索出数据?

答案是使用联结。简单地说,联结是一种机制,用来在一条SELECT语句中关联表,因此称之为联结。使用特殊的语法,可以联结多个表返回一组输出,联结在运行时关联表中正确的行。

维护引用完整性:

重要的是,要理解联结不是物理实体。换句话说,它在实际的数据库表中不存在。联结由MySQL根据需要建立,它存在于查询的执行当中。

那么如果出现一个表的外键的列中的值并没有与之对应的主键的值,那么这个时候就会出现问题。

为防止这种情况发生,可指示MySQL只允许在products表的供应商ID列中出现合法值(即出现在vendors表中的供应商)。这就是维护引用完整性,它是通过在表的定义中指定主键和外键来实现的。

15.2 创建联结

mysql> select vend_name, prod_name, prod_price         
    ->        from vendors, products 
    ->        where vendors.vend_id=products.vend_id
    ->        order by vend_name,prod_name;
+-------------+----------------+------------+
| vend_name   | prod_name      | prod_price |
+-------------+----------------+------------+
| ACME        | Bird seed      |      10.00 |
| ACME        | Carrots        |       2.50 |
| ACME        | Detonator      |      13.00 |
| ACME        | Safe           |      50.00 |
| ACME        | Sling          |       4.49 |
| ACME        | TNT (1 stick)  |       2.50 |
| ACME        | TNT (5 sticks) |      10.00 |
| Anvils R Us | .5 ton anvil   |       5.99 |
| Anvils R Us | 1 ton anvil    |       9.99 |
| Anvils R Us | 2 ton anvil    |      14.99 |
| Jet Set     | JetPack 1000   |      35.00 |
| Jet Set     | JetPack 2000   |      55.00 |
| LT Supplies | Fuses          |       3.42 |
| LT Supplies | Oil can        |       8.99 |
+-------------+----------------+------------+
14 rows in set (0.00 sec)

这条语句的FROM子句列出了两个表,分别是vendors和products。它们就是这条SELECT语句联结的两个表的名字。这两个表用WHERE子句正确联结, WHERE子句指示MySQL匹配vendors表中的vend_id和products表中的vend_id。

注意: 这里需要这种完全限定列名,因为如果只给出vend_id,则MySQL不知道指的是哪一个(它们有两个,每个表中一个)。

完全限定列名:在引用的列可能出现二义性时,必须使用完全限定列名(用一个点分隔的表名和列名)。如果引用一个没有用表名限制的具有二义性的列名, MySQL将返回错误。

15.2.1 WHERE子句的重要性

在联结两个表时,你实际上做的是将第一个表中的每一行与第二个表中的每一行配对。 WHERE子句作为过滤条件,它只包含那些匹配给定条件(这里是联结条件)的行。 没有WHERE子句,第一个表中的每个行将与第二个表中的每个行配对,而不管它们逻辑上是否可以配在一起。

笛卡儿积( cartesian product):由没有联结条件的表关系返回的结果为笛卡儿积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。

 mysql> select vend_name,prod_name,prod_price
    ->        from vendors, products
    ->        order by vend_name,prod_name;
+----------------+----------------+------------+
| vend_name      | prod_name      | prod_price |
+----------------+----------------+------------+
| ACME           | .5 ton anvil   |       5.99 |
| ACME           | 1 ton anvil    |       9.99 |
| ACME           | 2 ton anvil    |      14.99 |
| ACME           | Bird seed      |      10.00 |
| ACME           | Carrots        |       2.50 |
| ACME           | Detonator      |      13.00 |
| ACME           | Fuses          |       3.42 |
| ACME           | JetPack 1000   |      35.00 |
| ACME           | JetPack 2000   |      55.00 |
.......

不要忘了WHERE子句:应该保证所有联结都有WHERE子句,否则MySQL将返回比想要的数据多得多的数据。同理,应该保证WHERE子句的正确性。不正确的过滤条件将导致MySQL返回不正确的数据。

叉联结:有时我们会听到返回称为叉联结( cross join)的笛卡儿积的联结类型。

15.2.2 内部联结

目前为止所用的联结称为等值联结( equijoin),它基于两个表之间的相等测试。这种联结也称为内部联结。其实,对于这种联结可以使用稍微不同的语法来明确指定联结的类型。

mysql> SELECT vend_name,prod_name,prod_price 
    ->        FROM vendors INNER JOIN products 
    ->        ON vendors.vend_id=products.vend_id
    ->        ORDER BY vend_name,prod_name;
+-------------+----------------+------------+
| vend_name   | prod_name      | prod_price |
+-------------+----------------+------------+
| ACME        | Bird seed      |      10.00 |
| ACME        | Carrots        |       2.50 |
| ACME        | Detonator      |      13.00 |
| ACME        | Safe           |      50.00 |
| ACME        | Sling          |       4.49 |
| ACME        | TNT (1 stick)  |       2.50 |
| ACME        | TNT (5 sticks) |      10.00 |
| Anvils R Us | .5 ton anvil   |       5.99 |
| Anvils R Us | 1 ton anvil    |       9.99 |
| Anvils R Us | 2 ton anvil    |      14.99 |
| Jet Set     | JetPack 1000   |      35.00 |
| Jet Set     | JetPack 2000   |      55.00 |
| LT Supplies | Fuses          |       3.42 |
| LT Supplies | Oil can        |       8.99 |
+-------------+----------------+------------+
14 rows in set (0.00 sec)

这里,两个表之间的关系是FROM子句的组成部分,以INNERJOIN指定。在使用这种语法时,联结条件用特定的ON子句而不是WHERE子句给出。传递给ON的实际条件与传递给WHERE的相同。

使用哪种语法 ANSI SQL规范首选INNER JOIN语法。此外,尽管使用WHERE子句定义联结的确比较简单,但是使用明确的联结语法能够确保不会忘记联结条件,有时候这样做也能影响性能。

15.2.3 联结多个表

SQL对一条SELECT语句中可以联结的表的数目没有限制。创建联结的基本规则也相同。 首先列出所有表,然后定义表之间的关系。

mysql> SELECT prod_name,vend_name,prod_price,quantity
    ->        FROM orderitems, products, vendors
    ->        WHERE products.vend_id=vendors.vend_id
    ->          AND products.prod_id=orderitems.prod_id
    ->          AND order_num = 20005;
+----------------+-------------+------------+----------+
| prod_name      | vend_name   | prod_price | quantity |
+----------------+-------------+------------+----------+
| .5 ton anvil   | Anvils R Us |       5.99 |       10 |
| 1 ton anvil    | Anvils R Us |       9.99 |        3 |
| TNT (5 sticks) | ACME        |      10.00 |        5 |
| Bird seed      | ACME        |      10.00 |        1 |
+----------------+-------------+------------+----------+
4 rows in set (0.00 sec)

性能考虑:MySQL在运行时关联指定的每个表以处理联结。这种处理可能是非常耗费资源的,因此应该仔细,不要联结不必要的表。联结的表越多,性能下降越厉害。

多做实验:正如所见,为执行任一给定的SQL操作,一般存在不止一种方法。很少有绝对正确或绝对错误的方法。性能可能会受操作类型、表中数据量、是否存在索引或键以及其他一些条件的影响。因此,有必要对不同的选择机制进行实验,以找出最适合具体情况的方法。

第十六章 创建高级联结

16.1 使用表别名

别名除了用于列名和计算字段外, SQL还允许给表名起别名。这样做有两个主要理由:
 缩短SQL语句;
 允许在单条SELECT语句中多次使用相同的表。(自联结的时候使用)

mysql> SELECT cust_name,cust_contact
    ->        FROM customers AS c, orders AS o, orderitems AS oi
    ->        WHERE c.cust_id=o.cust_id
    ->          AND oi.order_num=o.order_num
    ->          AND prod_id='TNT2';
+----------------+--------------+
| cust_name      | cust_contact |
+----------------+--------------+
| Coyote Inc.    | Y Lee        |
| Yosemite Place | Y Sam        |
+----------------+--------------+
2 rows in set (0.00 sec)

应该注意,表别名只在查询执行中使用。与列别名不一样,表别名不返回到客户机。

16.2 使用不同类型的联结

16.2.1 自联结

使用表别名的主要原因之一是能在单条SELECT语句中不止一次引用相同的表。

mysql> SELECT p1.prod_id, p1.prod_name
    ->        FROM products AS p1, products AS p2
    ->        WHERE p1.vend_id=p2.vend_id
    ->          AND p2.prod_id='DTNTR';
+---------+----------------+
| prod_id | prod_name      |
+---------+----------------+
| DTNTR   | Detonator      |
| FB      | Bird seed      |
| FC      | Carrots        |
| SAFE    | Safe           |
| SLING   | Sling          |
| TNT1    | TNT (1 stick)  |
| TNT2    | TNT (5 sticks) |
+---------+----------------+
7 rows in set (0.00 sec)

为解决products表出现了两次的二义性的问题,使用了表别名。 products的第一次出现为别名p1,第二次出现为别名p2。现在可以将这些别名用作表名。

用自联结而不用子查询:自联结通常作为外部语句用来替代从相同表中检索数据时使用的子查询语句。虽然最终的结果是相同的,但有时候处理联结远比处理子查询快得多。应该试一下两种方法,以确定哪一种的性能更好。

16.2.2 自然联结

无论何时对表进行联结,应该至少有一个列出现在不止一个表中(被联结的列)。标准的联结(前一章中介绍的内部联结)返回所有数据,甚至相同的列多次出现。 自然联结排除多次出现,使每个列只返回一次。

我的理解就是,返回的数据中没有完全相同的列出现,就是联结的那两个列只出现一次。

下面是怎么得到自然联结:

R表                S表
A  B  C                     D  B  E
1  a  3                      2  c  7
2  b  6                      3  d  5
3  c  7                      1  a  3       自然连接怎么连接?

a) 自然连接是第一步R×S结果是:
A   B     C      D     B       E
1   a     3      2     c       7
1   a     3      3     d       5
1   a     3      1     a       3
2   b     6      2     c       7
2   b     6      3     d       5
2   b     6      1     a       3
3   c     7      2     c       7
3   c     7      3     d       5 
3   c     7      1     a       3
就是用R表中的每一项乘以S表中的每一项。
b) 选择R.B=S.B的记录:

R.A    R.B     R.C       S.D     S.B    S.E
1       a       3         1       a       3 
3       c       7         2       c       7

c) 然后去掉相同且值也相同的B属性,最后R∞S的值为:
   A     B      C      D       E
   1     a      3      1        3
   3     c      7      2        7

注:系统不会帮你完成自然联结的工作的,由你自己完成它。自然联结是这样一种联结,其中你只能选择那些唯一的列。

这一般是通过对表使用通配符( SELECT *),对所有其他表的列使用明确的子集来完成的。(也就是说不能出现两次*号,在select子句中)

事实上,迄今为止我们建立的每个内部联结都是自然联结,很可能我们永远都不会用到不是自然联结的内部联结。

16.2.3 外部联结

许多联结将一个表中的行与另一个表中的行相关联。但有时候会需要包含没有关联行的那些行。

例如: 列出所有产品以及订购数量,包括没有人订购的产品

联结包含了那些在相关表中没有关联行的行。这种类型的联结称为外部联结。

mysql> SELECT customers.cust_id, orders.order_num
    ->        FROM customers LEFT OUTER JOIN orders
    ->        ON customers.cust_id=orders.cust_id;
+---------+-----------+
| cust_id | order_num |
+---------+-----------+
|   10001 |     20005 |
|   10001 |     20009 |
|   10002 |      NULL |
|   10003 |     20006 |
|   10004 |     20007 |
|   10005 |     20008 |
+---------+-----------+
6 rows in set (0.00 sec)

与内部联结关联两个表中的行不同的是,外部联结还包括没有关联行的行。在使用OUTER JOIN语法时,必须使用RIGHT或LEFT关键字指定包括其所有行的表( RIGHT指出的是OUTER JOIN右边的表,而LEFT指出的是OUTER JOIN左边的表)。

没有*=操作符 MySQL不支持简化字符*=和=*的使用,这两种操作符在其他DBMS中是很流行的。

外部联结的类型 存在两种基本的外部联结形式:左外部联结和右外部联结。它们之间的唯一差别是所关联的表的顺序不同。换句话说,左外部联结可通过颠倒FROM或WHERE子句中表的顺序转换为右外部联结。因此,两种类型的外部联结可互换使用,而究竟使用哪一种纯粹是根据方便而定。

16.3 使用带聚集函数的联结

聚集函数用来汇总数据, 这些函数也可以与联结一起使用。

mysql> SELECT customers.cust_name,
    ->        customers.cust_id,
    ->        count(orders.order_num) AS num_ord
    ->        FROM customers INNER JOIN orders
    ->        ON customers.cust_id=orders.cust_id
    ->        GROUP BY customers.cust_id;
+----------------+---------+---------+
| cust_name      | cust_id | num_ord |
+----------------+---------+---------+
| Coyote Inc.    |   10001 |       2 |
| Wascals        |   10003 |       1 |
| Yosemite Place |   10004 |       1 |
| E Fudd         |   10005 |       1 |
+----------------+---------+---------+
4 rows in set (0.00 sec)
mysql> SELECT customers.cust_name,
    ->        customers.cust_id,
    ->        COUNT(orders.order_num) AS num_ord
    ->        FROM customers LEFT OUTER JOIN orders
    ->        ON customers.cust_id=orders.cust_id
    ->        GROUP BY customers.cust_id;
+----------------+---------+---------+
| cust_name      | cust_id | num_ord |
+----------------+---------+---------+
| Coyote Inc.    |   10001 |       2 |
| Mouse House    |   10002 |       0 |
| Wascals        |   10003 |       1 |
| Yosemite Place |   10004 |       1 |
| E Fudd         |   10005 |       1 |
+----------------+---------+---------+
5 rows in set (0.00 sec)

16.4 使用联结和联结条件

在总结关于联结的这两章前,有必要汇总一下关于联结及其使用的某些要点。

 注意所使用的联结类型。一般我们使用内部联结,但使用外部联结也是有效的。
 保证使用正确的联结条件,否则将返回不正确的数据。
 应该总是提供联结条件,否则会得出笛卡儿积。
 在一个联结中可以包含多个表,甚至对于每个联结可以采用不同的联结类型。虽然这样做是合法的,一般也很有用,但应该在一起测试它们前,分别测试每个联结。这将使故障排除更为简单。

第十七章  组 合 查 询

17.1 组合查询

多数SQL查询都只包含从一个或多个表中返回数据的单条SELECT语句。 MySQL也允许执行多个查询(多条SELECT语句),并将结果作为单个查询结果集返回。这些组合查询通常称为并( union) 或复合查询( compound query)。

有两种基本情况,其中需要使用组合查询:
 在单个查询中从不同的表返回类似结构的数据;
 对单个表执行多个查询,按单个查询返回数据。

组合查询和多个WHERE条件:多数情况下,组合相同表的两个查询完成的工作与具有多个WHERE子句条件的单条查询完成的工作相同。换句话说,任何具有多个WHERE子句的SELECT语句都可以作为一个组合查询给出,在以下段落中可以看到这一点。这两种技术在不同的查询中性能也不同。因此,应该试一下这两种技术,以确定对特定的查询哪一种性能更好

17.2 创建组合查询

可用UNION操作符来组合数条SQL查询。利用UNION,可给出多条SELECT语句,将它们的结果组合成单个结果集。

17.2.1 使用UNION

UNION的使用很简单。所需做的只是给出每条SELECT语句,在各条语句之间放上关键字UNION。

mysql> SELECT vend_id, prod_id, prod_price
    ->        FROM products 
    ->        WHERE prod_price<=5
    -> UNION  
    -> SELECT vend_id, prod_id, prod_price
    ->        FROM products
    ->        WHERE vend_id IN (1001, 1002);
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
|    1003 | FC      |       2.50 |
|    1002 | FU1     |       3.42 |
|    1003 | SLING   |       4.49 |
|    1003 | TNT1    |       2.50 |
|    1001 | ANV01   |       5.99 |
|    1001 | ANV02   |       9.99 |
|    1001 | ANV03   |      14.99 |
|    1002 | OL1     |       8.99 |
+---------+---------+------------+
8 rows in set (0.00 sec)

UNION指示MySQL执行两条SELECT语句,并把输出组合成单个查询结果集。

作为参考,这里给出使用多条WHERE子句而不是使用UNION的相同查询:

mysql> SELECT vend_id, prod_id, prod_price                                                                     
    ->        FROM products
    ->        WHERE prod_price<=5 OR vend_id IN (1001, 1002);
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
|    1001 | ANV01   |       5.99 |
|    1001 | ANV02   |       9.99 |
|    1001 | ANV03   |      14.99 |
|    1003 | FC      |       2.50 |
|    1002 | FU1     |       3.42 |
|    1002 | OL1     |       8.99 |
|    1003 | SLING   |       4.49 |
|    1003 | TNT1    |       2.50 |
+---------+---------+------------+
8 rows in set (0.00 sec)

在这个简单的例子中,使用UNION可能比使用WHERE子句更为复杂。但对于更复杂的过滤条件,或者从多个表(而不是单个表)中检索数据的情形,使用UNION可能会使处理更简单。

17.2.2 UNION规则

进行并时有几条规则需要注意。

 UNION必须由两条或两条以上的SELECT语句组成,语句之间用关键字UNION分隔(因此,如果组合4条SELECT语句,将要使用3个UNION关键字)。
 UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过各个列不需要以相同的次序列出)。
 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型(例如,不同的数值类型或不同的日期类型)。

17.2.3 包含或取消重复的行

UNION从查询结果集中自动去除了重复的行(换句话说,它的行为与单条SELECT语句中使用多个WHERE子句条件一样)。

这是UNION的默认行为,但是如果需要,可以改变它。事实上,如果想返回所有匹配行,可使用UNION ALL而不是UNION。

 mysql> SELECT vend_id, prod_id, prod_price                                                                     
    ->        FROM products
    ->        WHERE prod_price<=5
    -> UNION ALL
    -> SELECT vend_id, prod_price,prod_id
    ->        FROM products
    ->        WHERE vend_id IN (1001, 1002);
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
|    1003 | FC      | 2.50       |
|    1002 | FU1     | 3.42       |
|    1003 | SLING   | 4.49       |
|    1003 | TNT1    | 2.50       |
|    1001 | 5.99    | ANV01      |
|    1001 | 9.99    | ANV02      |
|    1001 | 14.99   | ANV03      |
|    1002 | 3.42    | FU1        |
|    1002 | 8.99    | OL1        |
+---------+---------+------------+
9 rows in set (0.00 sec)

UNION与WHERE:本章开始时说过, UNION几乎总是完成与多个WHERE条件相同的工作。 UNION ALL为UNION的一种形式,它完成WHERE子句完成不了的工作。如果确实需要每个条件的匹配行全部出现(包括重复行),则必须使用UNION ALL而不是WHERE。

17.2.4 对组合查询结果排序

SELECT语句的输出用ORDER BY子句排序。在用UNION组合查询时,只能使用一条ORDER BY子句,它必须出现在最后一条SELECT语句之后。对于结果集,不存在用一种方式排序一部分,而又用另一种方式排序另一部分的情况,因此不允许使用多条ORDER BY子句。

mysql> SELECT vend_id, prod_id, prod_price
    ->        FROM products
    ->        WHERE prod_price<=5
    -> UNION
    -> SELECT vend_id, prod_price,prod_id
    ->        FROM products
    ->        WHERE vend_id IN (1001, 1002)
    -> ORDER BY vend_id, prod_price;
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
|    1001 | 5.99    | ANV01      |
|    1001 | 9.99    | ANV02      |
|    1001 | 14.99   | ANV03      |
|    1002 | FU1     | 3.42       |
|    1002 | 3.42    | FU1        |
|    1002 | 8.99    | OL1        |
|    1003 | FC      | 2.50       |
|    1003 | TNT1    | 2.50       |
|    1003 | SLING   | 4.49       |
+---------+---------+------------+
9 rows in set (0.00 sec)

虽然ORDER BY子句似乎只是最后一条SELECT语句的组成部分,但实际上MySQL将用它来排序所有SELECT语句返回的所有结果。

组合不同的表:为使表述比较简单,本章例子中的组合查询使用的均是相同的表。但是其中使用UNION的组合查询可以应用不同的表。

第十八章  全文本搜索

18.1 理解全文本搜索

并非所有引擎都支持全文本搜索:正如第21章所述, MySQL支持几种基本的数据库引擎。并非所有的引擎都支持本书所描述的全文本搜索。两个最常使用的引擎为MyISAM和InnoDB,前者支持全文本搜索,而后者不支持。这就是为什么虽然本书中 创 建 的 多 数 样 例 表 使 用 InnoDB , 而 有 一 个 样 例 表( productnotes表)却使用MyISAM的原因。如果你的应用中需要全文本搜索功能,应该记住这一点。

之前的LIKE和正则表达式非常有用,但存在几个重要的限制。

 性能——通配符和正则表达式匹配通常要求MySQL尝试匹配表中所有行(而且这些搜索极少使用表索引)。因此,由于被搜索行数不断增加,这些搜索可能非常耗时。
 明确控制——使用通配符和正则表达式匹配,很难(而且并不总是能)明确地控制匹配什么和不匹配什么。例如,指定一个词必须匹配,一个词必须不匹配,而一个词仅在第一个词确实匹配的
情况下才可以匹配或者才可以不匹配。
 智能化的结果——虽然基于通配符和正则表达式的搜索提供了非常灵活的搜索,但它们都不能提供一种智能化的选择结果的方法。例如,一个特殊词的搜索将会返回包含该词的所有行,而不区分
包含单个匹配的行和包含多个匹配的行(按照可能是更好的匹配来排列它们)。类似,一个特殊词的搜索将不会找出不包含该词但包含其他相关词的行。

所有这些限制以及更多的限制都可以用全文本搜索来解决。在使用全文本搜索时, MySQL不需要分别查看每个行,不需要分别分析和处理每个词。 MySQL创建指定列中各词的一个索引,搜索可以针对这些词进行。

18.2 使用全文本搜索

为了进行全文本搜索,必须索引被搜索的列,而且要随着数据的改变不断地重新索引。在对表列进行适当设计后, MySQL会自动进行所有的索引和重新索引。

在索引之后, SELECT可与Match()和Against()一起使用以实际执行搜索。

18.2.1 启用全文本搜索支持

一般在创建表时启用全文本搜索。

下面的CREATE语句演示了FULLTEXT子句的使用:

CREATE TABLE productnotes
(
  note_id    int           NOT NULL AUTO_INCREMENT,
  prod_id    char(10)      NOT NULL,
  note_date datetime       NOT NULL,
  note_text  text          NULL ,
  PRIMARY KEY(note_id),
  FULLTEXT(note_text)
) ENGINE=MyISAM;

为了进行全文本搜索,MySQL根据子句FULLTEXT(note_text)的指示对它进行索引。这里的FULLTEXT索引单个列,如果需要也可以指定多个列。

在定义之后, MySQL自动维护该索引。在增加、更新或删除行时,索引随之自动更新。

可以在创建表时指定FULLTEXT,或者在稍后指定(在这种情况下所有已有数据必须立即索引)。

不要在导入数据时使用FULLTEXT:更新索引要花时间,虽然不是很多,但毕竟要花时间。如果正在导入数据到一个新表,此时不应该启用FULLTEXT索引。应该首先导入所有数据,然后再修改表, 定义FULLTEXT。 这样有助于更快地导入数据(而且使索引数据的总时间小于在导入每行时分别进行索引所需的总时间)。

18.2.2 进行全文本搜索

在索引之后,使用两个函数Match()和Against()执行全文本搜索,其中Match()指定被搜索的列, Against()指定要使用的搜索表达式。

mysql> SELECT note_text FROM productnotes WHERE Match(note_text) Against('rabbit');
+----------------------------------------------------------------------------------------------------------------------+
| note_text                                                                                                            |
+----------------------------------------------------------------------------------------------------------------------+
| Customer complaint: rabbit has been able to detect trap, food apparently less effective now.                         |
| Quantity varies, sold by the sack load. All guaranteed to be bright and orange, and suitable for use as rabbit bait. |
+----------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

由于WHERE子句,一个全文本搜索被执行。 Match(note_text)指示MySQL针对指定的列进行搜索, Against(‘rabbit’)指定词rabbit作为搜索文本。由于有两行包含词rabbit,这两个行被返回。

使 用 完 整 的 Match() 说 明 传 递 给 Match() 的 值 必 须 与FULLTEXT()定义中的相同。如果指定多个列,则必须列出它们(而且次序正确)。

搜索不区分大小写 除非使用BINARY方式 (本章中没有介绍),否则全文本搜索不区分大小写。

使用全文本搜索返回以文本匹配的良好程度排序的数据。两个行都包含词rabbit,但包含词rabbit作为第3个词的行的等级比作为第20个词的行高。这很重要。全文本搜索的一个重要部分就是对结果排序。具有较高等级的行先返回(因为这些行很可能是你真正想要的行)。

 mysql> SELECT note_text, Match(note_text) Against('rabbit') AS rank from productnotes;
+-----------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+
| note_text                                                                                                                                                 | rank               |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+
| Customer complaint: Sticks not individually wrapped, too easy to mistakenly detonate all at once. Recommend individual wrapping.                          |                  0 |
| Can shipped full, refills not available. Need to order new can if refill needed.                                                                          |                  0 |
| Safe is combination locked, combination not provided with safe. This is rarely a problem as safes are typically blown up or dropped by customers.         |                  0 |
| Quantity varies, sold by the sack load. All guaranteed to be bright and orange, and suitable for use as rabbit bait.                                      | 1.5905543565750122 |
| Included fuses are short and have been known to detonate too quickly for some customers. Longer fuses are available (item FU1) and should be recommended. |                  0 |
| Matches not included, recommend purchase of matches or detonator (item DTNTR).                                                                            |                  0 |
| Please note that no returns will be accepted if safe opened using explosives.                                                                             |                  0 |
| Multiple customer returns, anvils failing to drop fast enough or falling backwards on purchaser. Recommend that customer considers using heavier anvils.  |                  0 |
| Item is extremely heavy. Designed for dropping, not recommended for use with slings, ropes, pulleys, or tightropes.                                       |                  0 |
| Customer complaint: rabbit has been able to detect trap, food apparently less effective now.                                                              | 1.6408053636550903 |
| Shipped unassembled, requires common tools (including oversized hammer).                                                                                  |                  0 |
| Customer complaint: Circular hole in safe floor can apparently be easily cut with handsaw.                                                                |                  0 |
| Customer complaint: Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead.   |                  0 |
| Call from individual trapped in safe plummeting to the ground, suggests an escape hatch be added. Comment forwarded to vendor.                            |                  0 |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+
14 rows in set (0.01 sec)

等级由MySQL根据行中词的数目、唯一词的数目、整个索引中词的总数以及包含该词的行的数目计算出来。正如所见,不包含词rabbit的行等级为0(因此不被前一例子中的WHERE子句选择)。确实包含词rabbit的两个行每行都有一个等级值,文本中词靠前的行的等级值比词靠后的行的等级值高。

这个例子有助于说明全文本搜索如何排除行(排除那些等级为0的行),如何排序结果(按等级以降序排序)。

排序多个搜索项:如果指定多个搜索项,则包含多数匹配词的那些行将具有比包含较少词(或仅有一个匹配)的那些行高的等级值。

正如所见,全文本搜索提供了简单LIKE搜索不能提供的功能。而且,由于数据是索引的,全文本搜索还相当快。

18.2.3 使用查询扩展

询扩展用来设法放宽所返回的全文本搜索结果的范围。

在使用查询扩展时, MySQL对数据和索引进行两遍扫描来完成搜索:

 首先,进行一个基本的全文本搜索,找出与搜索条件匹配的所有行;
 其次, MySQL检查这些匹配行并选择所有有用的词(我们将会简要地解释MySQL如何断定什么有用,什么无用)。
 再其次, MySQL再次进行全文本搜索,这次不仅使用原来的条件,而且还使用所有有用的词。

利用查询扩展,能找出可能相关的结果,即使它们并不精确包含所查找的词。

首先进行一个简单的全文本搜索,没有查询扩展:

mysql> SELECT note_text FROM productnotes WHERE Match(note_text) Against('anvils');
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| note_text                                                                                                                                                |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Multiple customer returns, anvils failing to drop fast enough or falling backwards on purchaser. Recommend that customer considers using heavier anvils. |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

下面是相同的搜索,这次使用查询扩展:

mysql> SELECT note_text FROM productnotes WHERE Match(note_text) Against('anvils' WITH QUERY EXPANSION);
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| note_text                                                                                                                                                |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Multiple customer returns, anvils failing to drop fast enough or falling backwards on purchaser. Recommend that customer considers using heavier anvils. |
| Customer complaint: Sticks not individually wrapped, too easy to mistakenly detonate all at once. Recommend individual wrapping.                         |
| Customer complaint: Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead.  |
| Please note that no returns will be accepted if safe opened using explosives.                                                                            |
| Customer complaint: rabbit has been able to detect trap, food apparently less effective now.                                                             |
| Customer complaint: Circular hole in safe floor can apparently be easily cut with handsaw.                                                               |
| Matches not included, recommend purchase of matches or detonator (item DTNTR).                                                                           |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
7 rows in set (0.00 sec)

第一行包含词anvils,因此等级最高。第二行与anvils无关,但因为它包含第一行中的两个词( customer和recommend),所以也被检索出来。第3行也包含这两个相同的词,但它们在文本中的位置更靠后且分开得更远,因此也包含这一行,但等级为第三。第三行确实也没有涉及anvils(按它们的产品名)。

正如所见,查询扩展极大地增加了返回的行数,但这样做也增加了你实际上并不想要的行的数目。

行越多越好 表中的行越多(这些行中的文本就越多),使用查询扩展返回的结果越好。

18.2.4 布尔文本搜索

MySQL支持全文本搜索的另外一种形式,称为布尔方式。

以布尔方式,可以提供关于如下内容的细节:

 要匹配的词;
 要排斥的词(如果某行包含这个词,则不返回该行,即使它包含其他指定的词也是如此);
 排列提示(指定某些词比其他词更重要,更重要的词等级更高);
 表达式分组;
 另外一些内容。

即使没有FULLTEXT索引也可以使用:布尔方式不同于迄今为止 使 用 的 全 文 本 搜 索 语 法 的 地 方 在 于 , 即 使 没 有 定 义FULLTEXT索引,也可以使用它。但这是一种非常缓慢的操作(其性能将随着数据量的增加而降低)

为了匹配包含heavy但不包含任意以rope开始的词的行, 可使用以下查询:

mysql> SELECT note_text FROM productnotes WHERE Match(note_text) Against('heavy -rope*' IN BOOLEAN MODE);
+---------------------------------------------------------------------------------------------------------------------------------------------------------+
| note_text                                                                                                                                               |
+---------------------------------------------------------------------------------------------------------------------------------------------------------+
| Customer complaint: Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead. |
+---------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

如果不使用IN BOOLEAN MODE则:

mysql> SELECT note_text FROM productnotes WHERE Match(note_text) Against('heavy -rope*');
+---------------------------------------------------------------------------------------------------------------------------------------------------------+
| note_text                                                                                                                                               |
+---------------------------------------------------------------------------------------------------------------------------------------------------------+
| Item is extremely heavy. Designed for dropping, not recommended for use with slings, ropes, pulleys, or tightropes.                                     |
| Customer complaint: Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead. |
+---------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

上例中可以看到了两个全文本搜索布尔操作符-和*, -排除一个词,而*是截断操作符(可想象为用于词尾的一个通配符)。

下表列出支持的所有布尔操作符。

布尔操作符 说明
+ 包含,词必须存在
排除,词必须不出现
> 包含,而且增加等级值
< 包含,且减少等级值
() 把词组成子表达式(允许这些子表达式作为一个组被包含、排除、排列等)
~ 取消一个词的排序值
* 词尾的通配符
“” 定义一个短语(与单个词的列表不一样,它匹配整个短语以便包含或排除这个短语)

 

 mysql> SELECT note_text FROM productnotes WHERE Match(note_text) Against('+rabbit +bait' IN BOOLEAN MODE);

这个搜索匹配包含词rabbit和bait的行。

 mysql> SELECT note_text FROM productnotes WHERE Match(note_text) Against('rabbit bait' IN BOOLEAN MODE);

没有指定操作符,这个搜索匹配包含rabbit和bait中的至少一个词的行。

 mysql> SELECT note_text FROM productnotes WHERE Match(note_text) Against('"rabbit bait"' IN BOOLEAN MODE);

这个搜索匹配短语rabbit bait而不是匹配两个词rabbit和bait。

 mysql> SELECT note_text FROM productnotes WHERE Match(note_text) Against('>rabbit <carrot' IN BOOLEAN MODE);

匹配rabbit和carrot,增加前者的等级,降低后者的等级。

 mysql> SELECT note_text FROM productnotes WHERE Match(note_text) Against('+safe +(<combination)' IN BOOLEAN MODE);

这个搜索匹配词safe和combination,降低后者的等级。

18.2.5 全文本搜索的使用说明

在结束本章之前,给出关于全文本搜索的某些重要的说明。
 在索引全文本数据时,短词被忽略且从索引中排除。短词定义为那些具有3个或3个以下字符的词 (如果需要,这个数目可以更改)。
 MySQL带有一个内建的非用词( stopword)列表,这些词在索引 全文本数据时总是被忽略。如果需要,可以覆盖这个列表(请参阅MySQL文档以了解如何完成此工作)。
 许多词出现的频率很高,搜索它们没有用处(返回太多的结果)。因此, MySQL规定了一条50%规则,如果一个词出现在50%以上的行中,则将它作为一个非用词忽略。 50%规则不用于IN BOOLEAN MODE。
 如果表中的行数少于3行,则全文本搜索不返回结果(因为每个词或者不出现,或者至少出现在50%的行中)。
 忽略词中的单引号。例如, don’t索引为dont。
 不具有词分隔符(包括日语和汉语)的语言不能恰当地返回全文本搜索结果。
 如前所述,仅在MyISAM数据库引擎中支持全文本搜索。

第十九章  插 入 数 据

19.1 数据插入

插入及系统安全:可针对每个表或每个用户,利用MySQL的安全机制禁止使用INSERT语句,这将在第28章介绍。

19.2 插入完整的行

把数据插入表中的最简单的方法是使用基本的INSERT语法,它要求指定表名和被插入到新行中的值。下面举一个例子:

mysql> INSERT INTO customers VALUES(NULL,
    ->                       'Pep E. LaPew',
    ->                       '100 Main Street',
    ->                       'Los Angeles',
    ->                       'CA',
    ->                       '90046',
    ->                       'USA',
    ->                       NULL,
    ->                       NULL);
Query OK, 1 row affected (0.05 sec)

存储到每个表列中的数据在VALUES子句中给出,对每个列必须提供一个值。 如果某个列没有值 ,应该使用NULL值。各个列必须以它们在表定义中出现的次序填充。 你不想给出一个值 ,又不
能省略此列 (如前所述,必须给出每个列),所以指定一个NULL值。 (它被MySQL忽略, MySQL在这里插入下一个可用的cust_id值)。

虽然这种语法很简单,但并不安全,应该尽量避免使用。上面的SQL语句高度依赖于表中列的定义次序,并且还依赖于其次序容易获得的信息。即使可得到这种次序信息,也不能保证下一次表结构变动后各个列保持完全相同的次序。因此,编写依赖于特定列次序的SQL语句是很不安全的。

没有输出:INSERT语句一般不会产生输出。

编写INSERT语句的更安全(不过更烦琐)的方法如下:

mysql> INSERT INTO customers(cust_name,
    ->                       cust_address,
    ->                       cust_city,
    ->                       cust_state,
    ->                       cust_zip,
    ->                       cust_country,
    ->                       cust_contact,
    ->                       cust_email)
    ->            VALUES('Pep E. LaPew',
    ->                   '100 Main Street',
    ->                   'Los Angeles',
    ->                   'CA',
    ->                   '90046',
    ->                   'USA',
    ->                   NULL,
    ->                   NULL);
Query OK, 1 row affected (0.04 sec)

上述例子在表名后的括号里明确地给出了列名。在插入行时, MySQL将用VALUES列表中的相应值填入列表中的对应项。 VALUES中的第一个值对应于第一个指定的列名。第二个值对应于第二个列名,如此等等。

因为提供了列名, VALUES必须以其指定的次序匹配指定的列名,不一定按各个列出现在实际表中的次序。其优点是,即使表的结构改变,此INSERT语句仍然能正确工作。

总是使用列的列表 一般不要使用没有明确给出列的列表的INSERT语句。使用列的列表能使SQL代码继续发挥作用,即使表结构发生了变化。

仔细地给出值 不管使用哪种INSERT语法,都必须给出VALUES的正确数目。如果不提供列名,则必须给每个表列提供一个值。如果提供列名,则必须对每个列出的列给出一个值。如果不这样,将产生一条错误消息,相应的行插入不成功。

使用这种语法,还可以省略列。这表示可以只给某些列提供值,给其他列不提供值。(事实上你已经看到过这样的例子:当列名被明确列出时, cust_id可以省略。)

省略列 如果表的定义允许,则可以在INSERT操作中省略某些列。省略的列必须满足以下某个条件。
 该列定义为允许NULL值(无值或空值)。
 在表定义中给出默认值。这表示如果不给出值,将使用默认值。
如果对表中不允许NULL值且没有默认值的列不给出值,则MySQL将产生一条错误消息,并且相应的行插入不成功。

提高整体性能 数据库经常被多个客户访问,对处理什么请求以及用什么次序处理进行管理是MySQL的任务。 INSERT操作可能很耗时(特别是有很多索引需要更新时),而且它可能降低等待处理的SELECT语句的性能。
如果数据检索是最重要的(通常是这样),则你可以通过在INSERT和INTO之间添加关键字LOW_PRIORITY,指示MySQL降低INSERT语句的优先级,如下所示:

 mysql> INSERT LOW_PRIORITY INTO

顺便说一下,这也适用于下一章介绍的UPDATE和DELETE语句。

19.3 插入多个行

可以使用多条INSERT语句,甚至一次提交它们,每条语句用一个分号结束,如下所示:

 INSERT INTO customers(cust_name,
                      cust_address,
                      cust_city,
                      cust_state,
                      cust_zip,
                      cust_country)
            VALUES('Pep E. LaPew',
                   '100 Main Street',
                   'Los Angeles',
                   'CA',
                   '90046',
                   'USA');
INSERT INTO customers(cust_name,
                      cust_address,
                      cust_city,
                      cust_state,
                      cust_zip,
                      cust_country)
            VALUES('M. Martian',
                   '42 Galaxy Way',
                   'New York',
                   'NY',
                   '11213',
                   'USA');
mysql> source /home/learning/insert
  Query OK, 1 row affected (0.04 sec)

  Query OK, 1 row affected (0.03 sec)

或者,只要每条INSERT语句中的列名(和次序)相同,可以如下组合各语句:

mysql> INSERT INTO customers(cust_name,
    ->                       cust_address,
    ->                       cust_city,
    ->                       cust_state,
    ->                       cust_zip,
    ->                       cust_country)
    ->             VALUES('Pep E. LaPew',
    ->                    '100 Main Street',
    ->                    'Los Angeles',
    ->                    'CA',
    ->                    '90046',
    ->                    'USA'),
    ->                    ('M. Martian',
    ->                    '42 Galaxy Way',
    ->                    'New York',
    ->                    'NY',
    ->                    '11213',
    ->                    'USA');
Query OK, 2 rows affected (0.03 sec)
Records: 2  Duplicates: 0  Warnings: 0

其中单条INSERT语句有多组值,每组值用一对圆括号括起来,用逗号分隔。

提高INSERT的性能:此技术可以提高数据库处理的性能,因为MySQL用单条INSERT语句处理多个插入比使用多条INSERT语句快。

19.4 插入检索出的数据

INSERT还存在另一种形式,可以利用它将一条SELECT语句的结果插入表中。这就是所谓的INSERT SELECT,顾名思义,它是由一条INSERT语句和一条SELECT语句组成的。

假如你想从另一表中合并客户列表到你的customers表。 不需要每次读取一行,然后再将它用INSERT插入,可以如下进行:

mysql> INSERT INTO customers(cust_id, 
    ->                       cust_contact,
    ->                       cust_email,
    ->                       cust_name,
    ->                       cust_address,
    ->                       cust_city,
    ->                       cust_state,
    ->                       cust_zip,
    ->                       cust_country)
    -> SELECT cust_id,
    ->        cust_contact,
    ->        cust_email,
    ->        cust_name,
    ->        cust_address,
    ->        cust_city,
    ->        cust_state,
    ->        cust_zip,
    ->        cust_country
    -> FROM custnew;

这个例子使用INSERT SELECT从custnew中将所有数据导入customers。 SELECT语句从custnew检索出要插入的值,而不是列出它们。 SELECT中列出的每个列对应于customers表名后所跟的列
表中的每个列。这条语句将插入多少行有赖于custnew表中有多少行。
如果这个表为空,则没有行被插入(也不产生错误,因为操作仍然是合法的)。如果这个表确实含有数据,则所有数据将被插入到customers。
这个例子导入了cust_id(假设你能够确保cust_id的值不重复)。你也可以简单地省略这列(从INSERT和SELECT中),这样MySQL就会生成新值。

INSERT SELECT中的列名 为简单起见,这个例子在INSERT和SELECT语句中使用了相同的列名。但是,不一定要求列名匹配。事实上, MySQL甚至不关心SELECT返回的列名。它使用的是
列的位置,因此SELECT中的第一列(不管其列名)将用来填充表列中指定的第一个列,第二列将用来填充表列中指定的第二个列,如此等等。这对于从使用不同列名的表中导入数据是非常有用的。

INSERT SELECT中SELECT语句可包含WHERE子句以过滤插入的数据。

第二十章  更新和删除数据

20.1 更新数据

为了更新(修改)表中的数据,可使用UPDATE语句。可采用两种方式使用UPDATE:
 更新表中特定行;
 更新表中所有行。

不要省略WHERE子句:在使用UPDATE时一定要注意细心。因为稍不注意,就会更新表中所有行。

UPDATE语句非常容易使用,甚至可以说是太容易使用了。基本的UPDATE语句由3部分组成,分别是:
 要更新的表;

 列名和它们的新值;

 确定要更新行的过滤条件。

例子:

mysql> UPDATE customers SET cust_email='elmer@fudd.com' WHERE cust_id=10005;
  Query OK, 1 row affected (0.05 sec)
  Rows matched: 1  Changed: 1  Warnings: 0

上述例子中where子句很重要。没有WHERE子句, MySQL将会用这个电子邮件地址更新customers表中所有行,这不是我们所希望的。

更新多个列的语法稍有不同:

mysql> UPDATE customers SET cust_name='The Fudds', cust_email='elmer@fudd.com' WHERE cust_id=10005;
  Query OK, 1 row affected (0.04 sec)
  Rows matched: 1  Changed: 1  Warnings: 0

在更新多个列时,只需要使用单个SET命令,每个“列=值”对之间用逗号分隔(最后一列之后不用逗号)。

在UPDATE语句中使用子查询:UPDATE语句中可以使用子查询,使得能用SELECT语句检索出的数据更新列数据。

IGNORE关键字 如果用UPDATE语句更新多行,并且在更新这些行中的一行或多行时出一个现错误,则整个UPDATE操作被取消(错误发生前更新的所有行被恢复到它们原来的值)。为即使是发
生错误,也继续进行更新,可使用IGNORE关键字,如下所示:

UPDATE IGNORE customers…

为了删除某个列的值,可设置它为NULL(假如表定义允许NULL值)。如下进行:

mysql> UPDATE customers SET cust_email=NULL WHERE cust_id=10005;
  Query OK, 1 row affected (0.04 sec)
  Rows matched: 1  Changed: 1  Warnings: 0

20.2 删除数据

为了从一个表中删除(去掉)数据,使用DELETE语句。可以两种方式使用DELETE:
 从表中删除特定的行;
 从表中删除所有行。

不要省略WHERE子句 在使用DELETE时一定要注意细心。因为稍不注意,就会错误地删除表中所有行。

下面的语句从customers表中删除一行:

mysql> DELETE FROM customers WHERE cust_id = 10012;
  Query OK, 1 row affected (0.03 sec)

DELETE FROM要求指定从中删除数据的表名。WHERE子句过滤要删除的行。

DELETE不需要列名或通配符。 DELETE删除整行而不是删除列。为了删除指定的列,请使用UPDATE语句。

删除表的内容而不是表:DELETE语句从表中删除行,甚至是删除表中所有行。但是, DELETE不删除表本身。

更快的删除:如果想从表中删除所有行,不要使用DELETE。可使用TRUNCATE TABLE语句,它完成相同的工作,但速度更快( TRUNCATE实际是删除原来的表并重新创建一个表,而不是逐行删除表中的数据)。

20.3 更新和删除的指导原则

下面是许多SQL程序员使用UPDATE或DELETE时所遵循的习惯。
 除非确实打算更新和删除每一行,否则绝对不要使用不带WHERE子句的UPDATE或DELETE语句。
 保证每个表都有主键(如果忘记这个内容,请参阅第15章),尽可能像WHERE子句那样使用它(可以指定各主键、多个值或值的范围)。
 在对UPDATE或DELETE语句使用WHERE子句前,应该先用SELECT进行测试,保证它过滤的是正确的记录,以防编写的WHERE子句不正确。
 使用强制实施引用完整性的数据库

小心使用:MySQL没有撤销( undo)按钮。应该非常小心地使用UPDATE和DELETE,否则你会发现自己更新或删除了错误的数据。

第二十一章  创建和操纵表

21.1 创建表

一般有两种创建表的方法:
 使用具有交互式创建和管理表的工具(如第2章讨论的工具);
 表也可以直接用MySQL语句操纵。

为了用程序创建表,可使用SQL的CREATE TABLE语句。值得注意的是,在使用交互式工具时,实际上使用的是MySQL语句。但是,这些语句不是用户编写的,界面工具会自动生成并执行相应的MySQL语句(更改现有表时也是这样)。

21.1.1 表创建基础

为利用CREATE TABLE创建表,必须给出下列信息:
 新表的名字,在关键字CREATE TABLE之后给出;
 表列的名字和定义,用逗号分隔。

CREATE TABLE customers
(
  cust_id      int       NOT NULL AUTO_INCREMENT,
  cust_name    char(50)  NOT NULL ,
  cust_address char(50)  NULL ,
  cust_city    char(50)  NULL ,
  cust_state   char(5)   NULL ,
  cust_zip     char(10)  NULL ,
  cust_country char(50)  NULL ,
  cust_contact char(50)  NULL ,
  cust_email   char(255) NULL ,
  PRIMARY KEY (cust_id)
) ENGINE=InnoDB;

CREATE TABLE语句也可能会包括其他关键字或选项,但至少要包括表的 表名紧跟在CREATE TABLE关键字后面。实际的表定义(所有列)括在圆括号之中。各列之间用逗号分隔。名字和列的细节。 每列的定义以列名(它在表中必须是唯一的)开始,后跟列的数据类型。 表的主键可以在创建表时用PRIMARY KEY关键字指定。整条语句由 右 圆 括 号 后 的 分 号 结 束 。

处理现有的表 在创建新表时,指定的表名必须不存在,否则将出错。如果要防止意外覆盖已有的表, SQL要求首先手工删除该表(请参阅后面的小节),然后再重建它,而不是简单地用创建表语句覆盖它。

如果你仅想在一个表不存在时创建它,应该在表名后给出IF NOT EXISTS。这样做不检查已有表的模式是否与你打算创建的表模式相匹配。它只是查看表名是否存在,并且仅在表名不存在时创建它。

21.1.2 使用NULL值

NULL值就是没有值或缺值。允许NULL值的列也允许在插入行时不给出该列的值。不允许NULL值的列不接受该列没有值的行,换句话说,在插入或更新行时,该列必须有值。

每个表列或者是NULL列,或者是NOT NULL列,这种状态在创建时由表的定义规定。

CREATE TABLE orders
(
  order_num  int      NOT NULL AUTO_INCREMENT,
  order_date datetime NOT NULL ,
  cust_id    int      NOT NULL ,
  PRIMARY KEY (order_num)
) ENGINE=InnoDB;

所有3个列都需要,因此每个列的定义都含有关键字NOT NULL。

NULL为默认设置,如果不指定NOT NULL,则认为指定的是NULL。

理解NULL 不要把NULL值与空串相混淆。 NULL值是没有值,它不是空串。如果指定”(两个单引号,其间没有字符),这在NOT NULL列中是允许的。空串是一个有效的值,它不是无值。 NULL值用关键字NULL而不是空串指定。

21.1.3 主键再介绍

主键值必须唯一。即,表中的每个行必须具有唯一的主键值。如果主键使用单个列,则它的值必须唯一。如果使用多个列,则这些列的组合值必须唯一。

为创建由多个列组成的主键,应该以逗号分隔的列表给出各列名,如下所示:

CREATE TABLE orderitems
(
  order_num  int          NOT NULL ,
  order_item int          NOT NULL ,
  prod_id    char(10)     NOT NULL ,
  quantity   int          NOT NULL ,
  item_price decimal(8,2) NOT NULL ,
  PRIMARY KEY (order_num, order_item)
) ENGINE=InnoDB;

主键可以在创建表时定义(如这里所示), 或者在创建表之后定义。

主键和NULL值:第1章介绍过,主键为其值唯一标识表中每个行的列。主键中只能使用不允许NULL值的列。允许NULL值的列不能作为唯一标识。

21.1.4 使用AUTO_INCREMENT

AUTO_INCREMENT告诉MySQL,本列每当增加一行时自动增量。每次执行一个INSERT操作时, MySQL自动对该列增量(从而才有这个关键字AUTO_INCREMENT),给该列赋予下一个可用的值。这样给每个行分配一个唯一的cust_id,从而可以用作主键值。

每个表只允许一个AUTO_INCREMENT列,而且它必须被索引(如,通过使它成为主键)。

覆盖AUTO_INCREMENT:如果一个列被指定为AUTO_INCREMENT,则它需要使用特殊的值吗?你可以简单地在INSERT语句中指定一个值,只要它是唯一的(至今尚未使用过)即可,该值将被用来替代自动生成的值。后续的增量将开始使用该手工插入的值。

确定AUTO_INCREMENT值:

让MySQL生成(通过自动增量)主键的一个缺点是你不知道这些值都是谁。

如何在使用AUTO_INCREMENT列时获得这个值呢?可使用last_insert_id()函数获得这个值,如下所示:

mysql> SELECT last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                0 |
+------------------+
1 row in set (0.00 sec)

此语句返回最后一个AUTO_INCREMENT值,然后可以将它用于后续的MySQL语句。

21.1.5 指定默认值

如果在插入行时没有给出值, MySQL允许指定此时使用的默认值。默认值用CREATE TABLE语句的列定义中的DEFAULT关键字指定。

CREATE TABLE orderitems
(
  order_num  int          NOT NULL ,
  order_item int          NOT NULL ,
  prod_id    char(10)     NOT NULL ,
  quantity   int          NOT NULL DEFAULT 1,
  item_price decimal(8,2) NOT NULL ,
  PRIMARY KEY (order_num, order_item)
) ENGINE=InnoDB;

不允许函数:与大多数DBMS不一样, MySQL不允许使用函数作为默认值,它只支持常量。

使用默认值而不是NULL值:许多数据库开发人员使用默认值而不是NULL列,特别是对用于计算或数据分组的列更是如此。

21.1.6 引擎类型

MySQL有一个具体管理和处理数据的内部引擎。在你使用CREATE TABLE语句时,该引擎具体创建表,而在你使用SELECT语句或进行其他数据库处理时,该引擎在内部处理你的请求。 多数时候,此引擎都隐藏在DBMS内,不需要过多关注它。

但MySQL与其他DBMS不一样,它具有多种引擎。它打包多个引擎,这些引擎都隐藏在MySQL服务器内,全都能执行CREATE TABLE和SELECT等命令。

你完全可以忽略这些数据库引擎。如果省略ENGINE=语句,则使用默认引擎(很可能是MyISAM),多数SQL语句都会默认使用它。但并不是所有语句都默认使用它,这就是为什么ENGINE=语句很重要的原因。

以下是几个需要知道的引擎:
 InnoDB是一个可靠的事务处理引擎(参见第26章),它不支持全文本搜索;
 MEMORY在功能等同于MyISAM, 但由于数据存储在内存(不是磁盘)中,速度很快(特别适合于临时表);
 MyISAM是一个性能极高的引擎,它支持全文本搜索(参见第18章),但不支持事务处理。

引擎类型可以混用。除productnotes表使用MyISAM外,本书中的样例表都使用InnoDB。 原因是作者希望支持事务处理(因此,使用InnoDB),但也需要在productnotes中支持全文本搜索(因此,使用MyISAM)。

外键不能跨引擎:混用引擎类型有一个大缺陷。外键(用于强制实施引用完整性,如第1章所述)不能跨引擎,即使用一个引擎的表不能引用具有使用不同引擎的表的外键。

MyISAM由于其性能和特性可能是最受欢迎的引擎。但如果你不需要可靠的事务处理,可以使用其他引擎。

21.2 更新表

为更新表定义,可使用ALTER TABLE语句。

为了使用ALTER TABLE更改表结构,必须给出下面的信息:
 在ALTER TABLE之后给出要更改的表名(该表必须存在,否则将出错);
 所做更改的列表。

给表添加一个列:

mysql> ALTER TABLE vendors ADD vend_phone CHAR(20);
  Query OK, 0 rows affected (0.52 sec)
  Records: 0  Duplicates: 0  Warnings: 0

删除列:

mysql> ALTER TABLE vendors DROP COLUMN vend_phone;
  Query OK, 0 rows affected (0.50 sec)
  Records: 0  Duplicates: 0  Warnings: 0

ALTER TABLE的一种常见用途是定义外键:

 ALTER TABLE orders ADD CONSTRAINT fk_orders_customers FOREIGN KEY (cust_id) REFERENCES customers (cust_id);

复杂的表结构更改一般需要手动删除过程,它涉及以下步骤:
 用新的列布局创建一个新表;
 使用INSERT SELECT语句(关于这条语句的详细介绍,请参阅第 19章)从旧表复制数据到新表。如果有必要,可使用转换函数和计算字段;
 检验包含所需数据的新表;
 重命名旧表(如果确定,可以删除它);
 用旧表原来的名字重命名新表;
 根据需要,重新创建触发器、存储过程、索引和外键。

小心使用ALTER:TABLE 使用ALTER TABLE要极为小心,应该在进行改动前做一个完整的备份(模式和数据的备份)。数据库表的更改不能撤销,如果增加了不需要的列,可能不能删
除它们。类似地,如果删除了不应该删除的列,可能会丢失该列中的所有数据。

21.3 删除表

删除表(删除整个表而不是其内容)非常简单,使用DROP TABLE语句即可:

 mysql> DROP TABLE vendors;

删除表没有确认,也不能撤销,执行这条语句将永久删除该表。

21.4 重命名表

使用RENAME TABLE语句可以重命名一个表:

mysql> RENAME TABLE vendors TO vendors2;
  Query OK, 0 rows affected (0.14 sec)

使用下面的语句对多个表重命名:

mysql> RENAME TABLE vendors TO vendors2,
    ->              customers TO customers;

第二十二章  使 用 视 图

22.1 视图

视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态检索数据的查询。

视图不包含表中应该有的任何列或数据,它包含的是一个SQL查询(与上面用以正确联结表的相同的查询)。

22.1.1 为什么使用视图

我们已经看到了视图应用的一个例子。下面是视图的一些常见应用。
重用SQL语句。
简化复杂的SQL操作。在编写查询后,可以方便地重用它而不必知道它的基本查询细节。
使用表的组成部分而不是整个表。
保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限。
更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。

在视图创建之后,可以用与表基本相同的方式利用它们。可以对视图执行SELECT操作,过滤和排序数据,将视图联结到其他视图或表,甚至能添加和更新数据(添加和更新数据存在某些限制。关于这个内容稍后还要做进一步的介绍)。

视图仅仅是用来查看存储在别处的数据的一种设施。视图本身不包含数据,因此它们返回的数据是从其他表中检索出来的。在添加或更改这些表中的数据时,视图将返回改变过的数据。

性能问题:因为视图不包含数据,所以每次使用视图时,都必须处理查询执行时所需的任一个检索。如果嵌套了很多的视图,性能会下降很快。因此,在部署使用了大量视图的应用前,应该进行测试。

22.1.2 视图的规则和限制

下面是关于视图创建和使用的一些最常见的规则和限制。
1.与表一样,视图必须唯一命名(不能给视图取与别的视图或表相同的名字)。
2.对于可以创建的视图数目没有限制。
3.为了创建视图,必须具有足够的访问权限。这些限制通常由数据库管理人员授予。
4. 视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造一个视图。
5.ORDER BY可以用在视图中,但如果从该视图检索数据SELECT中也含有ORDER BY,那么该视图中的ORDER BY将被覆盖。
6. 视图不能索引,也不能有关联的触发器或默认值。
7. 视图可以和表一起使用。例如,编写一条联结表和视图的SELECT

22.2 使用视图

1. 视图用CREATE VIEW语句来创建。
2. 使用SHOW CREATE VIEW viewname;来查看创建视图的语句。
3. 用DROP删除视图,其语法为DROP VIEW viewname;。
4. 更新视图时,可以先用DROP再用CREATE,也可以直接用CREATE OR REPLACE VIEW。如果要更新的视图不存在,则第2条更新语句会创建一个视图;如果要更新的视图存在,则第2条更新语句会替换原有视图。

22.2.1 利用视图简化复杂的联结

视图的最常见的应用之一是隐藏复杂的SQL,这通常都会涉及联结。

mysql> CREATE VIEW productcustomers AS
    ->        SELECT cust_name,cust_contact,prod_id
    ->               FROM customers,orders,orderitems
    ->               WHERE customers.cust_id=orders.cust_id
    ->                 AND orderitems.order_num=orders.order_num;
Query OK, 0 rows affected (0.05 sec)

为检索订购了产品TNT2的客户,可如下进行:

mysql> SELECT cust_name,cust_contact FROM productcustomers WHERE prod_id='TNT2';
+----------------+--------------+
| cust_name      | cust_contact |
+----------------+--------------+
| Coyote Inc.    | Y Lee        |
| Yosemite Place | Y Sam        |
+----------------+--------------+
2 rows in set (0.00 sec)

在MySQL处理此查询时,它将指定的WHERE子句添加到视图查询中的已有WHERE子句中,以便正确过滤数据。

可以看出,视图极大地简化了复杂SQL语句的使用。利用视图,可一次性编写基础的SQL,然后根据需要多次使用。

创建可重用的视图:创建不受特定数据限制的视图是一种好办法。例如,上面创建的视图返回生产所有产品的客户而不仅仅是生产TNT2的客户。扩展视图的范围不仅使得它能被重用,而且甚至更有用。这样做不需要创建和维护多个类似视图。

22.2.2 用视图重新格式化检索出的数据

视图的另一常见用途是重新格式化检索出的数据。

mysql> CREATE VIEW vendorlocations AS
    ->        SELECT Concat(RTrim(vend_name), '(', RTrim(vend_country), ')') 
    ->               FROM vendors
    ->               ORDER BY vend_name;
Query OK, 0 rows affected (0.04 sec)

 

mysql> SELECT * FROM vendorlocations;
+---------------------------------------------------------+
| Concat(RTrim(vend_name), '(', RTrim(vend_country), ')') |
+---------------------------------------------------------+
| ACME(USA)                                               |
| Anvils R Us(USA)                                        |
| Furball Inc.(USA)                                       |
| Jet Set(England)                                        |
| Jouets Et Ours(France)                                  |
| LT Supplies(USA)                                        |
+---------------------------------------------------------+
6 rows in set (0.00 sec)

22.2.3 用视图过滤不想要的数据

mysql> CREATE VIEW customeremaillist AS
    ->        SELECT cust_id, cust_name, cust_email
    ->               FROM customers
    ->               WHERE cust_email IS NOT NULL;
Query OK, 0 rows affected (0.04 sec)

这里的WHERE子句过滤了cust_email列中具有NULL值的那些行,使他们不被检索出来。

mysql> SELECT * FROM customeremaillist;
+---------+----------------+---------------------+
| cust_id | cust_name      | cust_email          |
+---------+----------------+---------------------+
|   10001 | Coyote Inc.    | ylee@coyote.com     |
|   10003 | Wascals        | rabbit@wascally.com |
|   10004 | Yosemite Place | sam@yosemite.com    |
+---------+----------------+---------------------+
3 rows in set (0.00 sec)

WHERE子句与WHERE子句 如果从视图检索数据时使用了一条WHERE子句,则两组子句(一组在视图中,另一组是传递给视图的)将自动组合。

22.2.4 使用视图与计算字段

视图对于简化计算字段的使用特别有用。

mysql> CREATE VIEW orderitemexpanded AS
    ->        SELECT order_num,
    ->               prod_id,
    ->               quantity,
    ->               item_price,
    ->               quantity * item_price AS expanded_price
    ->        FROM orderitems;
Query OK, 0 rows affected (0.05 sec)

 

mysql> SELECT * FROM orderitemexpanded WHERE order_num=20005;
+-----------+---------+----------+------------+----------------+
| order_num | prod_id | quantity | item_price | expanded_price |
+-----------+---------+----------+------------+----------------+
|     20005 | ANV01   |       10 |       5.99 |          59.90 |
|     20005 | ANV02   |        3 |       9.99 |          29.97 |
|     20005 | TNT2    |        5 |      10.00 |          50.00 |
|     20005 | FB      |        1 |      10.00 |          10.00 |
+-----------+---------+----------+------------+----------------+
4 rows in set (0.00 sec)

可以看到,视图非常容易创建,而且很好使用。正确使用,视图可极大地简化复杂的数据处理。

22.2.5 更新视图

视图是可更新的(即,可以对它们使用INSERT、 UPDATE和DELETE)。更新一个视图将更新其基表(可以回忆一下,视图本身没有数据)。如果你对视图增加或删除行,实际上是对其基表增加或删除行。

并非所有视图都是可更新的。基本上可以说,如果MySQL不能正确地确定被更新的基数据,则不允许更新(包括插入和删除)。这实际上意味着,如果视图定义中有以下操作,则不能进行视图的更新:

1. 分组(使用GROUP BY和HAVING);
2. 联结;
3. 子查询;
4. 并;
5. 聚集函数( Min()、 Count()、 Sum()等);
6. DISTINCT;
7. 导出(计算)列。

换句话说,本章许多例子中的视图都是不可更新的。这听上去好像是一个严重的限制,但实际上不是,因为视图主要用于数据检索。

第二十三章 使用存储过程

23.1 存储过程

存储过程简单来说,就是为以后的使用而保存的一条或多条MySQL语句的集合。可将其视为批文件,虽然它们的作用不仅限于批处理。

23.2 为什么要使用存储过程

使用存储过程有3个主要的好处,即简单、安全、高性能。显然,它们都很重要。不过,在将SQL代码转换为存储过程前,也必须知道它的一些缺陷。
1. 一般来说,存储过程的编写比基本SQL语句复杂,编写存储过程需要更高的技能,更丰富的经验。

2. 你可能没有创建存储过程的安全访问权限。许多数据库管理员限制存储过程的创建权限,允许用户使用存储过程,但不允许他们创建存储过程。

23.3 使用存储过程

23.3.1 执行存储过程

MySQL执行存储过程的语句为CALL。 CALL接受存储过程的名字以及需要传递给它的任意参数。

 

存储过程可以显示结果,也可以不显示结果。

23.3.2 创建存储过程

mysql> DELIMITER // # 这一句暂时将语句的终止符改成了//
mysql> CREATE PROCEDURE productprocing()
    -> BEGIN
    ->     SELECT Avg(prod_price) AS priceaverage FROM products;  # 如果没有改变终止符,在Mysql命令行中这里就直接终止了语句,导致运行错误。
    -> END //
Query OK, 0 rows affected (0.02 sec)
mysql> DELIMITER ;  # 这一句又将语句的终止符改回了;

此存储过程名为productpricing,用CREATE PROCEDURE productpricing()语句定义。BEGIN和END语句用来限定存储过程体,过程体本身仅是一个简单的SELECT语句。

注:mysql命令行客户机的分隔符:如果你使用的是mysql命令行实用程序,应该仔细阅读此说明。认的MySQL语句分隔符为;(正如你已经在迄今为止所使用的MySQL语句中所看到的那样)。 mysql命令行实用程序也使用;作为语句分隔符。如果命令行实用程序要解释存储过程自身内的;字符,则它们最终不会成为存储过程的成分,这会使存储过程中的SQL出现句法错误。

mysql> CREATE PROCEDURE productprocing()
    -> BEGIN 
    ->     SELECT Avg(prod_price) AS priceaverage FROM products;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 3
mysql> CREATE PROCEDURE productprocing() 
    -> BEGIN
    ->     SELECT Avg(prod_price) AS priceaverage FROM products
    -> END;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 4

解决办法是临时更改命令行实用程序的语句分隔符,如下所示:

mysql> DELIMITER //
mysql> CREATE PROCEDURE productprocing()
    -> BEGIN
    ->     SELECT Avg(prod_price) AS priceaverage FROM products; 
    -> END //
Query OK, 0 rows affected (0.02 sec)
mysql> DELIMITER ;

其中, DELIMITER //告诉命令行实用程序使用//作为新的语句结束分隔符,可以看到标志存储过程结束的END定义为END//而不是END;。这样,存储过程体内的;仍然保持不动,并且正确地传递给数据库引擎。最后,为恢复为原来的语句分隔符, 可使用DELIMITER ;。除\符号外,任何字符都可以用作语句分隔符。如果你使用的是mysql命令行实用程序,在阅读本章时请记住这里的内容。

调用这个存储过程:

mysql> call productprocing();
+--------------+
| priceaverage |
+--------------+
|    16.133571 |
+--------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

因为存储过程实际上是一种函数,所以存储过程名后需要有()符号(即使不传递参数也需要)。

23.3.3 删除存储过程

存储过程在创建之后,被保存在服务器上以供使用,直至被删除。删除命令从服务器中删除存储过程。

为删除刚创建的存储过程,可使用以下语句:

mysql> drop PROCEDURE productprocing;
Query OK, 0 rows affected (0.00 sec)

这条语句删除刚创建的存储过程。请注意没有使用后面的(),只给出存储过程名。

仅当存在时删除:如果指定的过程不存在,则DROP PROCEDURE将产生一个错误。当过程存在想删除它时(如果过程不存在也不产生错误)可使用DROP PROCEDURE IF EXISTS。

23.3.4 使用参数

一般,存储过程并不显示结果,而是把结果返回给你指定的变量。

变量( variable):内存中一个特定的位置,用来临时存储数据。

mysql> DELIMITER //
mysql> CREATE PROCEDURE productprocing(
    ->      OUT pl DECIMAL(8, 2),
    ->      OUT ph DECIMAL(8, 2),
    ->      OUT pa DECIMAL(8, 2)
    -> )
    -> BEGIN
    ->     SELECT Min(prod_price) INTO pl FROM products;
    ->     SELECT Max(prod_price) INTO ph FROM products;
    ->     SELECT Avg(prod_price) INTO pa FROM products;
    -> END //
Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER ;

此存储过程接受3个参数。 每个参数必须具有指定的类型( 注意,记录集不是允许的类型,因此,不能通过一个参数返回多个行和列。)。 关键字OUT指出相应的参数用来从存储过程传出一个值(返回给调用者)。

MySQL支持IN(传递给存储过程)、 OUT(从存储过程传出,如这里所用)和INOUT(对存储过程传入和传出)类型的参数。

存储过程的代码位于BEGIN和END语句内,如前所见,它们是一系列SELECT语句,用来检索值,然后保存到相应的变量(通过指定INTO关键字)。

为调用此修改过的存储过程,必须指定3个变量名,如下所示:

mysql> CALL productprocing(@pricelow,
    ->                     @pricehigh,
    ->                     @priceaverage);
Query OK, 1 row affected, 1 warning (0.00 sec)

由于此存储过程要求3个参数,因此必须正好传递3个参数,不多也不少。所以,这条CALL语句给出3个参数。它们是存储过程将保存结果的3个变量的名字。

变量名:所有MySQL变量都必须以@开始。

在调用时,这条语句并不显示任何数据。它返回以后可以显示(或在其他处理中使用)的变量。

为了显示检索出的产品平均价格,可如下进行:

mysql> SELECT @priceaverage;
+---------------+
| @priceaverage |
+---------------+
|         16.13 |
+---------------+
1 row in set (0.00 sec)

为了获得3个值,可使用以下语句:

mysql> SELECT @pricehigh, @pricelow, @priceaverage;
+------------+-----------+---------------+
| @pricehigh | @pricelow | @priceaverage |
+------------+-----------+---------------+
|      55.00 |      2.50 |         16.13 |
+------------+-----------+---------------+
1 row in set (0.00 sec)

下面是另外一个例子,这次使用IN和OUT参数。 ordertotal接受订单号并返回该订单的合计:

mysql> DELIMITER //
mysql> CREATE PROCEDURE ordertotal(
    ->      IN onumber INT,
    ->      OUT ototal DECIMAL(8, 2)
    -> )
    -> BEGIN
    ->     SELECT Sum(item_price*quantity) FROM orderitems WHERE order_num=onumber INTO ototal;
    -> END //
Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER ;

为调用这个新存储过程,可使用以下语句:

mysql> CALL ordertotal(20005, @total);
  Query OK, 1 row affected (0.00 sec)

必须给ordertotal传递两个参数;第一个参数为订单号,第二个参数为包含计算出来的合计的变量名。

为了显示此合计,可如下进行:

mysql> SELECT @total;
+--------+
| @total |
+--------+
| 149.87 |
+--------+
1 row in set (0.00 sec)

为了得到另一个订单的合计显示,需要再次调用存储过程,然后重新显示变量:

mysql> CALL ordertotal(20009, @total);
Query OK, 1 row affected (0.00 sec)

mysql> SELECT @total;
+--------+
| @total |
+--------+
|  38.47 |
+--------+
1 row in set (0.00 sec)

23.3.5 建立智能存储过程

mysql> DELIMITER //
mysql> -- Name: ordertotal
mysql> -- Parameters: onumber = order number
mysql> --             taxable = 0 if not taxable, 1 if taxable
mysql> --             ototal = order total variable
mysql> CREATE PROCEDURE ordertotal(
    ->      IN onumber INT,
    ->      IN taxable BOOLEAN,
    ->      OUT ototal DECIMAL(8, 2)
    -> )COMMENT 'Obtain order total, optionally adding tax'
    -> BEGIN
    ->     -- Declare variable for total
    ->     DECLARE total DECIMAL(8, 2);
    ->     -- Declare tax percentage
    ->     DECLARE taxrate INT DEFAULT 6;
    -> 
    ->     -- Get the order total 
    ->     SELECT Sum(item_price*quantity) FROM orderitems WHERE order_num=onumber INTO total;
    ->     -- Is this taxable?
    ->     IF taxable THEN
    ->         -- yes, so add taxrate to the total
    ->         SELECT total+(total/100*taxrate) INTO total;
    ->     END IF;
    ->     -- And finally, save to out variable
    ->     SELECT total INTO ototal;
    -> END //
Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER ;

在存储过程体中,用DECLARE语句定义了两个局部变量。

DECLARE要求指定变量名和数据类型,它也支持可选的默认值(这个例子中的taxrate的默认被设置为6%)。

IF语句检查taxable是否为真,如果为真,则用另一SELECT语句增加营业税到局部变量total。最后,用另一SELECT语句将total(它增加或许不增加营业税)保存到ototal。

COMMENT关键字:本例子中的存储过程在CREATE PROCEDURE语句中包含了一个COMMENT值。它不是必需的,但如果给出,将在SHOW PROCEDURE STATUS的结果中显示。

这显然是一个更高级,功能更强的存储过程。为试验它,请用以下两条语句:

mysql> CALL ordertotal(20005, 0, @total);
Query OK, 1 row affected (0.00 sec)

mysql> SELECT @total;
+--------+
| @total |
+--------+
| 149.87 |
+--------+
1 row in set (0.00 sec)

BOOLEAN值指定为1表示真,指定为0表示假(实际上,非零值都考虑为真,只有0被视为假)。

IF语句:这个例子给出了MySQL的IF语句的基本用法。 IF语句还支持ELSEIF和ELSE子句(前者还使用THEN子句,后者不使用)。在以后章节中我们将会看到IF的其他用法(以及其他流控制语句)。

23.3.6 检查存储过程

为显示用来创建一个存储过程的CREATE语句,使用SHOW CREATE PROCEDURE语句:

mysql> SHOW CREATE PROCEDURE ordertotal;

为了获得包括何时、由谁创建等详细信息的存储过程列表, 使用SHOW PROCEDURE STATUS。

 mysql> SHOW PROCEDURE STATUS;
+----------+----------------+-----------+------------+---------------------+---------------------+---------------+-------------------------------------------+----------------------+----------------------+--------------------+
| Db       | Name           | Type      | Definer    | Modified            | Created             | Security_type | Comment                                   | character_set_client | collation_connection | Database Collation |
+----------+----------------+-----------+------------+---------------------+---------------------+---------------+-------------------------------------------+----------------------+----------------------+--------------------+
| learning | ordertotal     | PROCEDURE | learning@% | 2016-11-08 21:37:25 | 2016-11-08 21:37:25 | DEFINER       | Obtain order total, optionally adding tax | utf8                 | utf8_general_ci      | latin1_swedish_ci  |
| learning | productprocing | PROCEDURE | learning@% | 2016-11-08 21:03:13 | 2016-11-08 21:03:13 | DEFINER       |                                           | utf8                 | utf8_general_ci      | latin1_swedish_ci  |
+----------+----------------+-----------+------------+---------------------+---------------------+---------------+-------------------------------------------+----------------------+----------------------+--------------------+
2 rows in set (0.01 sec)

限制过程状态结果 SHOW PROCEDURE STATUS列出所有存储过程。为限制其输出,可使用LIKE指定一个过滤模式,例如:

mysql> SHOW PROCEDURE STATUS LIKE 'productprocing'; # 注意这里是有单引号的
+----------+----------------+-----------+------------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
| Db       | Name           | Type      | Definer    | Modified            | Created             | Security_type | Comment | character_set_client | collation_connection | Database Collation |
+----------+----------------+-----------+------------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
| learning | productprocing | PROCEDURE | learning@% | 2016-11-08 21:03:13 | 2016-11-08 21:03:13 | DEFINER       |         | utf8                 | utf8_general_ci      | latin1_swedish_ci  |
+----------+----------------+-----------+------------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
1 row in set (0.00 sec)

第二十四章  使 用 游 标

24.1 游标

如果没有使用游标, MySQL检索操作返回一组称为结果集的行。

游标 ( cursor) 是一个存储在MySQL服务器上的数据库查询,它不是一条SELECT语句,而是被该语句检索出来的结果集。在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。

只能用于存储过程 不像多数DBMS, MySQL游标只能用于存储过程(和函数)。

24.2 使用游标

使用游标涉及几个明确的步骤。

 在能够使用游标前,必须声明(定义)它。这个过程实际上没有检索数据,它只是定义要使用的SELECT语句。
 一旦声明后,必须打开游标以供使用。这个过程用前面定义的SELECT语句把数据实际检索出来。
 对于填有数据的游标,根据需要取出(检索)各行。
 在结束游标使用时,必须关闭游标。

24.2.1 创建游标

mysql> DELIMITER //
mysql> CREATE PROCEDURE processorders()
    -> BEGIN
    ->     DECLARE ordernumbers CURSOR
    ->     FOR
    ->     SELECT order_num FROM orders;
    -> END //
Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER ;

DECLARE语句用来定义和命名游标,这里为ordernumbers。 存储过程处理完成后,游标就消失(因为它局限于存储过程)。
在定义游标之后,可以打开它。

24.2.2 打开和关闭游标

游标用OPEN CURSOR语句来打开:

mysql> OPEN ordernumbers;

在处理OPEN语句时执行查询,存储检索出的数据以供浏览和滚动。

游标处理完成后,应当使用如下语句关闭游标:

 mysql> CLOSE ordernumbers;

CLOSE释放游标使用的所有内部内存和资源,因此在每个游标不再需要时都应该关闭。

在一个游标关闭后,如果没有重新打开,则不能使用它。但是,使用声明过的游标不需要再次声明,用OPEN语句打开它就可以了。

隐含关闭 如果你不明确关闭游标, MySQL将会在到达END语句时自动关闭它。

mysql> CREATE PROCEDURE processorders()
    -> BEGIN
    ->     -- Declare the cursor
    ->     DECLARE ordernumbers CURSOR
    ->     FOR
    ->     SELECT order_num FROM orders;
    ->  
    ->     -- Open the cursor
    ->     OPEN ordernumbers;
    -> 
    ->     -- Close the cursor
    ->     CLOSE ordernumbers;
    -> END //
Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER ;

这个存储过程声明、打开和关闭一个游标。但对检索出的数据什么也没做。

24.2.3 使用游标数据

在一个游标被打开后,可以使用FETCH语句分别访问它的每一行。FETCH指定检索什么数据(所需的列),检索出来的数据存储在什么地方。它还向前移动游标中的内部行指针,使下一条FETCH语句检索下一行(不重复读取同一行)。

mysql> DELIMITER //
mysql> CREATE PROCEDURE processorders()
    -> BEGIN
    ->     -- Declare local variables
    ->     DECLARE o INT;
    -> 
    ->     -- Declare the cursor
    ->     DECLARE ordernumbers CURSOR
    ->     FOR
    ->     SELECT order_num FROM orders;
    -> 
    ->     -- Open the cursor
    ->     OPEN ordernumbers;
    -> 
    ->     -- Get order number
    ->     FETCH ordernumbers INTO o;
    -> 
    ->     -- Close the cursor
    ->     CLOSE ordernumbers;
    -> END //
Query OK, 0 rows affected (0.01 sec)

mysql> DELIMITER ;

其中FETCH用来检索当前行的order_num列 (将自动从第一行开始)到一个名为o的局部声明的变量中。对检索出的数据不做任何处理。

mysql> DELIMITER //
mysql> CREATE PROCEDURE processorder()
    -> BEGIN
    ->     -- Declare local variables
    ->     DECLARE done BOOLEAN DEFAULT 0;
    ->     DECLARE o INT;
    -> 
    ->     -- Declare the cursor 
    ->     DECLARE ordernumbers CURSOR 
    ->     FOR 
    ->     SELECT order_num FROM orders;
    -> 
    ->     -- Declare continue handler
    ->     DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
    -> 
    ->     -- Open the cursor
    ->     OPEN ordernumbers;
    -> 
    ->     -- Loop through all rows
    ->     REPEAT
    ->     
    ->         -- Get order number
    ->         FETCH ordernumbers INTO o;
    ->     -- End of loop
    ->     UNTIL done END REPEAT;
    -> 
    ->     -- Close the cursor
    ->     CLOSE ordernumbers;
    -> END //
Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER ;

与前一个例子一样,这个例子使用FETCH检索当前order_num到声明的名为o的变量中。但与前一个例子不一样的是,这个例子中的FETCH是在REPEAT内,因此它反复执行直到done为真(由UNTIL done END REPEAT;规定)。为使它起作用,用一个DEFAULT 0(假,不结束)定义变量done。那么, done怎样才能在结束时被设置为真呢?答案是用以下语句:

DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;

这条语句定义了一个CONTINUE HANDLER,它是在条件出现时被执行的代码。这里, 它指出当SQLSTATE ‘02000’出现时, SET done=1。SQL STATE ‘02000’是一个未找到条件, 当REPEAT由于没有更多的行供循环而不能继续时,出现这个条件。

DECLARE语句的次序:DECLARE语句的发布存在特定的次序。用DECLARE语句定义的局部变量必须在定义任意游标或句柄之前定义,而句柄必须在游标之后定义。不遵守此顺序将产生错误消息

如 果 调 用 这 个 存 储 过 程 , 它 将 定 义 几 个 变 量 和 一 个 CONTINUE HANDLER,定义并打开一个游标,重复读取所有行,然后关闭游标。如果一切正常,你可以在循环内放入任意需要的处理(在FETCH语句之后,循环结束之前)。

重复或循环?:除这里使用的REPEAT语句外, MySQL还支持循环语句,它可用来重复执行代码,直到使用LEAVE语句手动退出为止。通常REPEAT语句的语法使它更适合于对游标进行循环。

mysql> DELIMITER //
mysql> CREATE PROCEDURE processorders()
    -> BEGIN 
    ->     -- Declare local variables 
    ->     DECLARE done BOOLEAN DEFAULT 0;
    ->     DECLARE o INT;
    ->     DECLARE t DECIMAL(8,2);
    -> 
    ->     -- Declare the cursor 
    ->     DECLARE ordernumbers CURSOR
    ->     FOR 
    ->     SELECT order_num FROM orders;
    ->     
    ->     -- Declare continue handler
    ->     DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
    -> 
    ->     -- Create a table to store the results
    ->     CREATE TABLE IF NOT EXISTS ordertotals (order_num INT, total DECIMAL(8, 2));
    -> 
    ->     -- Open the cursor
    ->     OPEN ordernumbers;
    -> 
    ->     -- Loop through all rows 
    ->     REPEAT 
    ->         -- Get order number
    ->         FETCH ordernumbers INTO o;
    ->         -- Get the total for this order
    ->         CALL ordertotal(o, 1, t);  // 上一章定义的存储过程
    -> 
    ->         -- Insert order and total into ordertotals
    ->         INSERT INTO ordertotals(order_num, total)
    ->         VALUES(o, t);
    ->     -- End of loop
    ->     UNTIL done END REPEAT;
    -> 
    ->     -- Close the cursor
    ->     CLOSE ordernumbers;
    -> END //
Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER ;

我们增加了另一个名为t的变量(存储每个订单的合计)。 此存储过程还在运行中创建了一个新表(如果它不存在的话),名为ordertotals。这个表将保存存储过程生成的结果。FETCH像以前一样取每个order_num,然后用CALL执行另一个存储过程(我们在前一章中创建)来计算每个订单的带税的合计(结果存储到t)。最后,用INSERT保存每个订单的订单号和合计。

此存储过程不返回数据,但它能够创建和填充另一个表,可以用一条简单的SELECT语句查看该表:

mysql> CALL processorders();
Query OK, 1 row affected (0.28 sec)

mysql> SELECT * FROM ordertotals;
+-----------+---------+
| order_num | total   |
+-----------+---------+
|     20005 |  158.86 |
|     20009 |   40.78 |
|     20006 |   58.30 |
|     20007 | 1060.00 |
|     20008 |  132.50 |
|     20008 |  132.50 |
+-----------+---------+
6 rows in set (0.00 sec)

第二十五章 使用触发器

25.1 触发器

触发器是MySQL响应以下任意语句而自动执行的一条MySQL语句(或位于BEGIN和END语句之间的一组语句):
 DELETE;
 INSERT;
 UPDATE。
其他MySQL语句不支持触发器。

25.2 创建触发器

在创建触发器时,需要给出4条信息:
 唯一的触发器名;
 触发器关联的表;
 触发器应该响应的活动( DELETE、 INSERT或UPDATE);
 触发器何时执行(处理之前或之后)。
保持每个数据库的触发器名唯一:在MySQL 5中,触发器名必须在每个表中唯一,但不是在每个数据库中唯一。

触发器用CREATE TRIGGER语句创建。下面是一个简单的例子:

mysql> CREATE TRIGGER newproduct 
    ->        AFTER INSERT         //  触发器可在一个操作发生之前或之后执行,这里给出了AFTER INSERT,所以此触发器将在INSERT语句成功执行后执行。
    ->        ON products
    ->        FOR EACH ROW         //  这个触发器还指定FOREACH ROW,因此代码对每个插入行执行。
    ->        SELECT 'Product added'; //在新版的mysql中这一句是不合法的。

仅支持表:只有表才支持触发器,视图不支持(临时表也不支持)。

触发器按每个表每个事件每次地定义,每个表每个事件每次只允许一个触发器。因此,每个表最多支持6个触发器(每条INSERT、 UPDATE和DELETE的之前和之后)。单一触发器不能与多个事件或多个表关联,所以,如果你需要一个对INSERT和UPDATE操作执行的触发器,则应该定义两个触发器。

触发器失败:如果BEFORE触发器失败,则MySQL将不执行请求的操作。此外,如果BEFORE触发器或语句本身失败, MySQL将不执行AFTER触发器(如果有的话)。

25.3 删除触发器

为了删除一个触发器,可使用DROP TRIGGER语句,如下所示:

mysql> DROP TRIGGER newproduct;

触发器不能更新或覆盖。为了修改一个触发器,必须先删除它,然后再重新创建

25.4 使用触发器

25.4.1 INSERT触发器

INSERT触发器在INSERT语句执行之前或之后执行。需要知道以下几点:
 在INSERT触发器代码内,可引用一个名为NEW的虚拟表,访问被插入的行;
 在BEFORE INSERT触发器中, NEW中的值也可以被更新(允许更改被插入的值);
 对于AUTO_INCREMENT列, NEW在INSERT执行之前包含0,在INSERT执行之后包含新的自动生成值。

mysql> CREATE TRIGGER neworder 
    ->        AFTER INSERT 
    ->        ON orders
    ->        FOR EACH ROW 
    ->        SELECT NEW.order_num;
ERROR 1415 (0A000): Not allowed to return a result set from a trigger   // 在新版的mysql中这一句是错误的。也就是说在创建触发器语句中不能有select子句。

BEFORE或AFTER?:通常,将BEFORE用于数据验证和净化(目的是保证插入表中的数据确实是需要的数据)。本提示也适用于UPDATE触发器。

25.4.2 DELETE触发器

DELETE触发器在DELETE语句执行之前或之后执行。需要知道以下两
点:
 在DELETE触发器代码内,你可以引用一个名为OLD的虚拟表,访问被删除的行;
 OLD中的值全都是只读的,不能更新。

下面的例子演示使用OLD保存将要被删除的行到一个存档表中:

mysql> DELIMITER //
mysql> CREATE TRIGGER deleteorder
    ->        BEFORE DELETE
    ->        ON orders
    ->        FOR EACH ROW
    ->        BEGIN
    ->            INSERT INTO archive_orders(order_num, order_date, cust_id)
    ->            VALUES(OLD.order_num, OLD.order_date, OLD.cust_id);
    ->        END //
Query OK, 0 rows affected (0.08 sec)

mysql> DELIMITER ;

在任意订单被删除前将执行此触发器。它使用一条INSERT语句将OLD中的值(要被删除的订单)保存到一个名为archive_orders的存档表中。

使用BEFORE DELETE触发器的优点(相对于AFTER DELETE触发器来说)为,如果由于某种原因,订单不能存档, DELETE本身将被放弃。

使用BEGIN END块的好处是触发器能容纳多条SQL语句(在BEGIN END块中一条挨着一条)。

25.4.3 UPDATE触发器

UPDATE触发器在UPDATE语句执行之前或之后执行。需要知道以下几点:
 在UPDATE触发器代码中,你可以引用一个名为OLD的虚拟表访问以前( UPDATE语句前)的值,引用一个名为NEW的虚拟表访问新更新的值;
 在BEFORE UPDATE触发器中, NEW中的值可能也被更新(允许更改将要用于UPDATE语句中的值);
 OLD中的值全都是只读的,不能更新。

下面的例子保证州名缩写总是大写(不管UPDATE语句中给出的是大写还是小写):

mysql> CREATE TRIGGER updatevendor 
    ->        BEFORE UPDATE
    ->        ON vendors
    ->        FOR EACH ROW
    ->        SET NEW.vend_state = Upper(NEW.vend_state);
Query OK, 0 rows affected (0.07 sec)

每次更新一个行时, NEW.vend_state中的值(将用来更新表行的值)都用Upper(NEW.vend_state)替换。

25.4.4 关于触发器的进一步介绍

在结束本章之前,我们再介绍一些使用触发器时需要记住的重点。
 与其他DBMS相比, MySQL 5中支持的触发器相当初级。未来的MySQL版本中有一些改进和增强触发器支持的计划。
 创建触发器可能需要特殊的安全访问权限,但是,触发器的执行是自动的。如果INSERT、 UPDATE或DELETE语句能够执行,则相关的触发器也能执行。
 应该用触发器来保证数据的一致性(大小写、格式等)。在触发器中执行这种类型的处理的优点是它总是进行这种处理,而且是透明地进行,与客户机应用无关。
 触发器的一种非常有意义的使用是创建审计跟踪。使用触发器,把更改(如果需要,甚至还有之前和之后的状态)记录到另一个表非常容易。
 遗憾的是, MySQL触发器中不支持CALL语句。这表示不能从触发器内调用存储过程。所需的存储过程代码需要复制到触发器内。

第二十六章 管理事务处理

26.1 事务处理

并非所有引擎都支持事务处理 :MyISAM和InnoDB是两种最常使用的引擎。前者不支持明确的事务处理管理,而后者支持。

事务处理( transaction processing)可以用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行,要么完全不执行。

事务处理是一种 机制,用来管理必须成批执行的MySQL操作,以保证数据库不包含不完 整的操作结果。利用事务处理,可以保证一组操作不会中途停止,它们 或者作为整体执行,或者完全不执行(除非明确指示)。如果没有错误发 生,整组语句提交给(写到)数据库表。如果发生错误,则进行回退(撤 销)以恢复数据库到某个已知且安全的状态。

下面是关于 事务处理需要知道的几个术语:

 事务(transaction)指一组SQL语句;
 回退(rollback)指撤销指定SQL语句的过程;
 提交(commit)指将未存储的SQL语句结果写入数据库表;
 保留点(savepoint)指事务处理中设置的临时占位符(place-holder),你可以对它发布回退(与回退整个事务处理不同)。

26.2 控制事务处理

管理事务处理的关键在于将SQL语句组分解为逻辑块,并明确规定数 据何时应该回退,何时不应该回退。

MySQL使用下面的语句来标识事务的开始:

mysql> START TRANCACTION

26.2.1 使用ROLLBACK

MySQL的ROLLBACK命令用来回退(撤销)MySQL语句,请看下面的语句:

mysql> SELECT * FROM ordertotals;
    -> START TRANSACTION;
    -> DELETE FROM ordertotals;
    -> SELECT * FROM ordertotals;
    -> ROLLBACK;
    -> SELECT * FROM ordertotals;
    -> //
+-----------+---------+
| order_num | total   |
+-----------+---------+
|     20005 |  158.86 |
|     20009 |   40.78 |
|     20006 |   58.30 |
|     20007 | 1060.00 |
|     20008 |  132.50 |
|     20008 |  132.50 |
+-----------+---------+
6 rows in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 6 rows affected (0.00 sec)

Empty set (0.00 sec)

Query OK, 0 rows affected (0.11 sec)

+-----------+---------+
| order_num | total   |
+-----------+---------+
|     20005 |  158.86 |
|     20009 |   40.78 |
|     20006 |   58.30 |
|     20007 | 1060.00 |
|     20008 |  132.50 |
|     20008 |  132.50 |
+-----------+---------+
6 rows in set (0.11 sec)

显然,ROLLBACK只能在一个事务处理内使用(在执行一条START TRANSACTION命令之后)。

哪些语句可以回退? 事务处理用来管理INSERT、UPDATE和 DELETE语句。你不能回退SELECT语句。(这样做也没有什么意 义。)你不能回退CREATE或DROP操作。事务处理块中可以使用这两条语句,但如果你执行回退,它们不会被撤销。

26.2.2 使用COMMIT

一般的MySQL语句都是直接针对数据库表执行和编写的。这就是 所谓的隐含提交(implicit commit),即提交(写或保存)操作是自动 进行的。但是,在事务处理块中,提交不会隐含地进行。

为进行明确的提交, 使用COMMIT语句,如下所示:

mysql> DELIMITER //
mysql> START TRANSACTION;
    -> DELETE FROM orderitems WHERE order_num=20010;
    -> DELETE FROM orders WHERE order_num=20010;
    -> COMMIT;
    -> //
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER ;

使用事务处理块来保证订单不被部分删除。最后的COMMIT语句仅在不出错时写出更改。如果第一条DELETE起作用,但第二条失败,则DELETE不会提交(实际上,它是被自动撤销的)。

隐含事务关闭 当COMMIT或ROLLBACK语句执行后,事务会自动关闭(将来的更改会隐含提交)。

26.2.3 使用保留点

复杂的事务处理可能需要部分提交或回退。 为了支持回退部分事务处理,必须能在事务处理块中合适的位置放置占位符。这样,如果需要回退,可以回退到某个占位符。

这些占位符称为保留点。为了创建占位符,可如下使用SAVEPOINT语句:

mysql> SAVEPOINT delete1;

每个保留点都取标识它的唯一名字,以便在回退时, MySQL知道要回退到何处。为了回退到本例给出的保留点,可如下进行:

mysql> ROLLBACK TO delete1;

释放保留点:保留点在事务处理完成(执行一条ROLLBACK或COMMIT)后自动释放。自MySQL 5以来,也可以用RELEASE SAVEPOINT明确地释放保留点。

26.2.4 更改默认的提交行为

默认的MySQL行为是自动提交所有更改。MySql语句都是针对表执行的,而且所做的更改立即生效。为指示MySQL不自动提交更改,需要使用以下语句:

 mysql> SET autocommit=0;

autocommit标志决定是否自动提交更改,不管有没有COMMIT语句。设置autocommit为0(假)指示MySQL不自动提交更改(直到autocommit被设置为真为止)。

标志为连接专用:autocommit标志是针对每个连接而不是服务器的。也就是说在一个连接中改变autocommit并不会改变另外一个连接。

第二十七章  全球化和本地化

27.1 字符集和校对顺序

 

MySQL需要适应不同的字符集(不同的字母和字符),适应不同的排序和检索数据的方法。

在讨论多种语言和字符集时,将会遇到以下重要术语:
 字符集为字母和符号的集合;
 编码为某个字符集成员的内部表示;
 校对为规定字符如何比较的指令。

在MySQL的正常数据库活动( SELECT、 INSERT等)中,不需要操心太多的东西。使用何种字符集和校对的决定在服务器、数据库和表级进行。

27.2 使用字符集和校对顺序

查看所支持的字符集完整列表(这条语句显示所有可用的字符集以及每个字符集的描述和默认校对):

 mysql> SHOW CHARACTER set;
+----------+---------------------------------+---------------------+--------+
| Charset  | Description                     | Default collation   | Maxlen |
+----------+---------------------------------+---------------------+--------+
| big5     | Big5 Traditional Chinese        | big5_chinese_ci     |      2 |
| dec8     | DEC West European               | dec8_swedish_ci     |      1 |
| cp850    | DOS West European               | cp850_general_ci    |      1 |
| hp8      | HP West European                | hp8_english_ci      |      1 |
| koi8r    | KOI8-R Relcom Russian           | koi8r_general_ci    |      1 |
| latin1   | cp1252 West European            | latin1_swedish_ci   |      1 |
| latin2   | ISO 8859-2 Central European     | latin2_general_ci   |      1 |
。。。。。。。。

查看所支持校对的完整列表(此语句显示所有可用的校对,以及它们适用的字符集。):

 mysql> SHOW COLLATION;
+--------------------------+----------+-----+---------+----------+---------+
| Collation                | Charset  | Id  | Default | Compiled | Sortlen |
+--------------------------+----------+-----+---------+----------+---------+
| big5_chinese_ci          | big5     |   1 | Yes     | Yes      |       1 |
| big5_bin                 | big5     |  84 |         | Yes      |       1 |
| dec8_swedish_ci          | dec8     |   3 | Yes     | Yes      |       1 |
| dec8_bin                 | dec8     |  69 |         | Yes      |       1 |
| cp850_general_ci         | cp850    |   4 | Yes     | Yes      |       1 |
| cp850_bin                | cp850    |  80 |         | Yes      |       1 |
| hp8_english_ci           | hp8      |   6 | Yes     | Yes      |       1 |
| hp8_bin                  | hp8      |  72 |         | Yes      |       1 |
| koi8r_general_ci         | koi8r    |   7 | Yes     | Yes      |       1 |
| koi8r_bin                | koi8r    |  74 |         | Yes      |       1 |
。。。。。。。。

通常系统管理在安装时定义一个默认的字符集和校对。此外,也可以在创建数据库时,指定默认的字符集和校对。为了确定所用的字符集和校对,可以使用以下语句:

mysql> SHOW VARIABLES LIKE 'character%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8                       |
| character_set_connection | utf8                       |
| character_set_database   | latin1                     |
| character_set_filesystem | binary                     |
| character_set_results    | utf8                       |
| character_set_server     | latin1                     |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)
 mysql> SHOW VARIABLES LIKE 'collation%';
+----------------------+-------------------+
| Variable_name        | Value             |
+----------------------+-------------------+
| collation_connection | utf8_general_ci   |
| collation_database   | latin1_swedish_ci |
| collation_server     | latin1_swedish_ci |
+----------------------+-------------------+
3 rows in set (0.00 sec)

实际上,字符集很少是服务器范围(甚至数据库范围)的设置。不同的表,甚至不同的列都可能需要不同的字符集,而且两者都可以在创建表时指定。

为了给表指定字符集和校对,可使用带子句的CREATE TABLE:

mysql> CREATE TABLE mytable
    -> (
    ->  columnn1 INT,
    ->  columnn2 VARCHAR(10)
    -> )DEFAULT CHARACTER SET hebrew
    ->  COLLATE hebrew_general_ci;
Query OK, 0 rows affected (0.24 sec)

此语句创建一个包含两列的表,并且指定一个字符集和一个校对顺序。

如果只指定CHARACTER SET,则使用此字符集及其默认的校对(如SHOW CHARACTER SET的结果中所示)。

除了能指定字符集和校对的表范围外, MySQL还允许对每个列设置它们,如下所示:

mysql> CREATE TABLE mytable
    -> (
    ->  columnn1 INT,
    ->  columnn2 VARCHAR(10),
    ->  columnn3 VARCHAR(10) CHARACTER SET latin1 COLLATE latin1_general_ci
    -> )DEFAULT CHARACTER SET hebrew
    ->  COLLATE hebrew_general_ci;
Query OK, 0 rows affected (0.24 sec)

这里对整个表以及一个特定的列指定了CHARACTER SET和COLLATE。

如果你需要用与创建表时不同的校对顺序排序特定的SELECT语句,可以在SELECT语句自身中进行:

mysql> SELECT cust_name,cust_address FROM customers     
    ->     ORDER BY cust_name, cust_address  COLLATE latin1_general_cs;
+----------------+---------------------+
| cust_name      | cust_address        |
+----------------+---------------------+
| Coyote Inc.    | 200 Maple Lane      |
| E Fudd         | 4545 53rd Street    |
| Mouse House    | 333 Fromage Lane    |
| Wascals        | 1 Sunny Place       |
| Yosemite Place | 829 Riverside Drive |
+----------------+---------------------+
5 rows in set (0.00 sec)

此SELECT使用COLLATE指定一个备用的校对顺序。

临时区分大小写:上面的SELECT语句演示了在通常不区分大小写的表上进行区分大小写搜索的一种技术。当然,反过来也是可以的。

SELECT的其他COLLATE子句:除了这里看到的在ORDER BY子句中使用以外, COLLATE还可以用于GROUP BY、 HAVING、聚集函数、别名等。

如果绝对需要,串可以在字符集之间进行转换。为此,使用Cast()或Convert()函数。

第二十八章  安 全 管 理

28.1 访问控制

MySQL服务器的安全基础是: 用户应该对他们需要的数据具有适当的访问权,既不能多也不能少。

应该创建一系列的账号,有的用于管理,有的供用户使用,有的供开发人员使用,等等。
防止无意的错误:重要的是注意到,访问控制的目的不仅仅是防止用户的恶意企图。数据梦魇更为常见的是无意识错误的结果,如错打MySQL语句,在不合适的数据库中操作或其他一些用户错误。通过保证用户不能执行他们不应该执行的语句,访问控制有助于避免这些情况的发生。
不要使用root:应该严肃对待root登录的使用。仅在绝对需要时使用它(或许在你不能登录其他管理账号时使用)。不应该在日常的MySQL操作中使用root。

28.2 管理用户

获得所有用户账号列表:

mysql> use mysql
Database changed
mysql> SELECT user FROM user;
+------------------+
| user             |
+------------------+
| learning         |
| mobile           |
| debian-sys-maint |
| mysql.sys        |
| root             |
+------------------+
5 rows in set (0.00 sec)

28.2.1 创建用户账号

为了创建一个新用户账号,使用CREATE USER语句:

mysql> CREATE USER yhw IDENTIFIED BY '654321';
Query OK, 0 rows affected (0.02 sec)

在创建用户账号时不一定需要口令,不过这个例子用IDENTIFIED BY ‘654321’给出了一个口令。

指定散列口令:IDENTIFIED BY指定的口令为纯文本, MySQL将在保存到user表之前对其进行加密。为了作为散列值指定口令,使用IDENTIFIED BY PASSWORD。

使用GRANT或INSERT:GRANT语句(稍后介绍)也可以创建用户账号,但一般来说CREATE USER是最清楚和最简单的句子。此外, 也可以通过直接插入行到user表来增加用户, 不过为安
全起见,一般不建议这样做。 MySQL用来存储用户账号信息的表(以及表模式等)极为重要,对它们的任何毁坏都可能严重地伤害到MySQL服务器。因此,相对于直接处理来说,最好是用标记和函数来处理这些表。

为重新命名一个用户账号,使用RENAME USER语句,如下所示:

mysql> RENAME USER yhw TO yhwyhw;
Query OK, 0 rows affected (0.00 sec)

28.2.2 删除用户账号

为了删除一个用户账号(以及相关的权限),使用DROP USER语句:

mysql> DROP USER yhwyhw;
Query OK, 0 rows affected (0.00 sec)

自MySQL 5以来, DROP USER删除用户账号和所有相关的账号权限。

28.2.3 设置访问权限

在创建用户账号后,必须接着分配访问权限。新创建的用户账号没有访问权限。它们能登录MySQL,但不能看到数据,不能执行任何数据库操作。

为看到赋予用户账号的权限,使用SHOW GRANTS FOR,如下所示:

mysql> SHOW GRANTS FOR learning;
+--------------------------------------------------------------------------+
| Grants for learning@%                                                    |
+--------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'learning'@'%'                                     |
+--------------------------------------------------------------------------+
2 rows in set (0.00 sec)

USAGE表示根本没有权限(我知道,这不很直观),所以,此结果表示在任意数据库和任意表上对任何东西没有权限。

用户定义为user@host MySQL的权限用用户名和主机名结合定义。

以下例子给出GRANT的用法:

mysql> GRANT SELECT ON learning.* TO bforta;
Query OK, 0 rows affected (0.00 sec)

SHOW GRANTS反映这个更改:

mysql> SHOW GRANTS FOR bforta;
+----------------------------------------------+
| Grants for bforta@%                          |
+----------------------------------------------+
| GRANT USAGE ON *.* TO 'bforta'@'%'           |
| GRANT SELECT ON `learning`.* TO 'bforta'@'%' |
+----------------------------------------------+
2 rows in set (0.00 sec)

GRANT的反操作为REVOKE,用它来撤销特定的权限。下面举一个例子:

mysql> REVOKE SELECT ON learning.* FROM bforta;
Query OK, 0 rows affected (0.00 sec)

被撤销的访问权限必须存在,否则会出错。

GRANT和REVOKE可在几个层次上控制访问权限:
 整个服务器,使用GRANT ALL和REVOKE ALL;
 整个数据库,使用ON database.*;
 特定的表,使用ON database.table;
 特定的列;
 特定的存储过程。

下表列出可以授予或撤销的每个权限。

权限 权限级别 权限说明
CREATE 数据库、表或索引 创建数据库、表或索引权限
DROP 数据库或表 删除数据库或表权限
GRANT OPTION 数据库、表或保存的程序 赋予权限选项
REFERENCES 数据库或表
ALTER 更改表,比如添加字段、索引等
DELETE 删除数据权限
INDEX 索引权限
INSERT 插入权限
SELECT 查询权限
UPDATE 更新权限
CREATE VIEW 视图 创建视图权限
SHOW VIEW 视图 查看视图权限
ALTER ROUTINE 存储过程 更改存储过程权限
CREATE ROUTINE 存储过程 创建存储过程权限
EXECUTE 存储过程 执行存储过程权限
FILE 服务器主机上的文件访问 文件访问权限
CREATE TEMPORARY TABLES 服务器管理 创建临时表权限
LOCK TABLES 服务器管理 锁表权限
CREATE USER 服务器管理 创建用户权限
PROCESS 服务器管理 查看进程权限
RELOAD 服务器管理 执行flush-hosts, flush-logs, flush-privileges, flush-status, flush-tables, flush-threads, refresh, reload等命令的权限
REPLICATION CLIENT 服务器管理 复制权限
REPLICATION SLAVE 服务器管理 复制权限
SHOW DATABASES 服务器管理 查看数据库权限
SHUTDOWN 服务器管理 关闭数据库权限
SUPER 服务器管理 执行kill线程权限

简化多次授权:可通过列出各权限并用逗号分隔,将多条GRANT语句串在一起,如下所示:

mysql> GRANT SELECT,INSERT ON learning.* TO bforta;
Query OK, 0 rows affected (0.00 sec)

28.2.4 更改口令

可使用SET PASSWORD语句。新口令必须如下加密:

mysql> SET PASSWORD FOR bforta=Password('654321');
Query OK, 0 rows affected, 1 warning (0.00 sec)

SET PASSWORD还可以用来设置你自己的口令:

mysql> SET PASSWORD=Password('654321');

在不指定用户名时, SET PASSWORD更新当前登录用户的口令。

第二十九章  数据库维护

29.1 备份数据

由于MySQL数据 库是基于磁盘的文件,普通的备份系统和例程就能备份MySQL的数据。 但是,由于这些文件总是处于打开和使用状态,普通的文件副本备份不 一定总是有效。(是一种思路)

 使用命令行实用程序mysqldump转储所有数据库内容到某个外部文件。在进行常规备份前这个实用程序应该正常运行,以便能正 确地备份转储文件。

 可用命令行实用程序mysqlhotcopy从一个数据库复制所有数据 (并非所有数据库引擎都支持这个实用程序)。

 可以使用MySQL的BACKUP TABLE或SELECT INTO OUTFILE转储所有数据到某个外部文件。这两条语句都接受将要创建的系统文件 名,此系统文件必须不存在,否则会出错。数据可以用RESTORE TABLE来复原。

首先刷新未写数据:为了保证所有数据被写到磁盘(包括索引 数据),可能需要在进行备份前使用FLUSH TABLES语句。

29.2 进行数据库维护

 ANALYZE TABLE,用来检查表键是否正确。ANALYZE TABLE返回如 下所示的状态信息:

mysql> ANALYZE TABLE orders;
+-----------------+---------+----------+----------+
| Table           | Op      | Msg_type | Msg_text |
+-----------------+---------+----------+----------+
| learning.orders | analyze | status   | OK       |
+-----------------+---------+----------+----------+
1 row in set (0.00 sec)

 CHECK TABLE用来针对许多问题对表进行检查。在MyISAM表上还对 索引进行检查。CHECK TABLE支持一系列的用于MyISAM表的方式。 CHANGED检查自最后一次检查以来改动过的表。EXTENDED执行最 彻底的检查,FAST只检查未正常关闭的表,MEDIUM检查所有被删 除的链接并进行键检验,QUICK只进行快速扫描。如下所示,CHECK TABLE发现和修复问题:

mysql> CHECK TABLE orders, orderitems;
+---------------------+-------+----------+----------+
| Table               | Op    | Msg_type | Msg_text |
+---------------------+-------+----------+----------+
| learning.orders     | check | status   | OK       |
| learning.orderitems | check | status   | OK       |
+---------------------+-------+----------+----------+
2 rows in set (0.00 sec)

 如果MyISAM表访问产生不正确和不一致的结果,可能需要用 REPAIR TABLE来修复相应的表。这条语句不应该经常使用,如果 需要经常使用,可能会有更大的问题要解决。

 如果从一个表中删除大量数据,应该使用OPTIMIZE TABLE来收回所用的空间,从而优化表的性能。

29.3 诊断启动问题

服务器启动问题通常在对MySQL配置或服务器本身进行更改时出 现。MySQL在这个问题发生时报告错误,但由于多数MySQL服务器是作 为系统进程或服务自动启动的,这些消息可能看不到。

在排除系统启动问题时,首先应该尽量用手动启动服务器。MySQL 服务器自身通过在命令行上执行mysqld启动。下面是几个重要的mysqld 命令行选项:

 –help显示帮助——一个选项列表;
 –safe-mode装载减去某些最佳配置的服务器;
 –verbose显示全文本消息(为获得更详细的帮助消息与–help联合使用);
 –version显示版本信息然后退出。

29.4 查看日志文件

MySQL维护管理员依赖的一系列日志文件。主要的日志文件有以下几种。

  错误日志。它包含启动和关闭问题以及任意关键错误的细节。此 日志通常名为hostname.err,位于data目录中。此日志名可用 –log-error命令行选项更改。

  查询日志。它记录所有MySQL活动,在诊断问题时非常有用。此 日志文件可能会很快地变得非常大,因此不应该长期使用它。此 日志通常名为hostname.log,位于data目录中。此名字可以用 –log命令行选项更改。

  二进制日志。它记录更新过数据(或者可能更新过数据)的所有 语句。此日志通常名为hostname-bin,位于data目录内。此名字 可以用–log-bin命令行选项更改。注意,这个日志文件是MySQL5中添加的,以前的MySQL版本中使用的是更新日志。

 缓慢查询日志。顾名思义,此日志记录执行缓慢的任何查询。这 个日志在确定数据库何处需要优化很有用。此日志通常名为 hostname-slow.log,位于data目录中。此名字可以用–log-slow-queries命令行选项更改。

在使用日志时,可用FLUSH LOGS语句来刷新和重新开始所有日志文件。

第三十章 改 善 性 能

30.1 改善性能

 首先, MySQL(与所有DBMS一样)具有特定的硬件建议。在学习和研究MySQL时,使用任何旧的计算机作为服务器都可以。但对用于生产的服务器来说,应该坚持遵循这些硬件建议。
 一般来说,关键的生产DBMS应该运行在自己的专用服务器上。
 MySQL是用一系列的默认设置预先配置的,从这些设置开始通常是很好的。但过一段时间后你可能需要调整内存分配、缓冲区大小等。(为查看当前设置,可使用 SHOW VARIABLES;和 SHOW
STATUS;。)
 MySQL一个多用户多线程的DBMS,换言之,它经常同时执行多个任务。如果这些任务中的某一个执行缓慢,则所有请求都会执行缓慢。如果你遇到显著的性能不良,可使用SHOW PROCESSLIST显示所有活动进程(以及它们的线程ID和执行时间)。你还可以用 KILL命令终结某个特定的进程(使用这个命令需要作为管理员登录)。
 总是有不止一种方法编写同一条SELECT语句。 应该试验联结、并、子查询等,找出最佳的方法。
 使用EXPLAIN语句让MySQL解释它将如何执行一条SELECT语句。
 一般来说,存储过程执行得比一条一条地执行其中的各条MySQL语句快。
 应该总是使用正确的数据类型。
 决不要检索比需求还要多的数据。换言之,不要用SELECT *(除非你真正需要每个列)。
 有的操作(包括INSERT)支持一个可选的DELAYED关键字,如果使用它,将把控制立即返回给调用程序,并且一旦有可能就实际执行该操作。
 在导入数据时,应该关闭自动提交。你可能还想删除索引(包括FULLTEXT索引),然后在导入完成后再重建它们。
 必须索引数据库表以改善数据检索的性能。确定索引什么不是一件微不足道的任务,需要分析使用的SELECT语句以找出重复的WHERE和ORDER BY子句。如果一个简单的WHERE子句返回结果所花的时间太长,则可以断定其中使用的列(或几个列)就是需要索引的对象。
 你的SELECT语句中有一系列复杂的OR条件吗?通过使用多条SELECT语句和连接它们的UNION语句,你能看到极大的性能改进。
 索引改善数据检索的性能,但损害数据插入、删除和更新的性能。如果你有一些表,它们收集数据且不经常被搜索,则在有必要之前不要索引它们。(索引可根据需要添加和删除。)
 LIKE很慢。一般来说,最好是使用FULLTEXT而不是LIKE。
 数据库是不断变化的实体。一组优化良好的表一会儿后可能就面目全非了。由于表的使用和内容的更改,理想的优化和配置也会改变。
 最重要的规则就是,每条规则在某些条件下都会被打破。

每天进步一点点之Mysql内建函数汇总

mysql数据库中提供了很丰富的函数。mysql函数包括数学函数、字符串函数、日期和时间函数、条件判断函数、系统信息函数、加密函数、格式化函数等。通过这些函数,可以简化用户的操作。

 

简单介绍几类函数的使用范围:

数学函数:这类函数只要用于处理数字。这类函数包括绝对值函数、正弦函数、余弦函数、获取随机数函数等。

字符串函数:这类函数主要用于处理字符串。其中包括字符串连接函数、字符串比较函数、将字符串的字母变成小写或大写字母的函数、获取子串的函数等。

日期和时间函数:这类函数主要用于处理日期和时间。其中包括取当前时间的函数、获取当前日期的函数、返回年份的函数、返回日期的函数等。

流程函数:这类函数主要用于在SQL语句中控制条件选择。其中包括IF语句、CASE语句、WHEN语句等。

系统信息函数:这类函数主要用于获取mysql数据库的系统信息。其中包括获取数据库名的函数、获取当前用户的函数、获取数据库版本的函数等。

加密函数:这类函数主要用于对字符串进行加密解密。其中包括字符串加密函数、字符串解密函数等。

其他函数:包括格式化函数、锁函数等。

 

 

1.数学函数

函数 作用
ABS(x) 返回x的绝对值
CEIL(x),CEILING(x) 返回大于或等于x的最小整数(向上取整)
FLOOR(x) 返回小于或等于x的最大整数(向下取整)
RAND() 返回0~1的随机数
RAND(x) 返回0~1的随机数,x值相同时返回的随机数相同
SIGN(x) 返回x的符号,x是负数、0、正数分别返回-1、0、1
PI() 返回圆周率
TRUNCATE(x,y) 返回数值x保留到小数点后y位的值
ROUND(x) 返回离x最近的整数(四舍五入)
ROUND(x,y) 保留x小数点后y位的值,但截断时要四舍五入
POW(x,y),POWER(x,y) 返回x的y次方
SQRT(x) 返回x的平方根
EXP(x) 返回e的x次方
MOD(x,y) 返回x 除以y以后的余数
LOG(x) 返回自然对数(以e为底的对数)
LOG10(x) 返回以10为底的对数
RADIANS(x) 讲角度转换为弧度
DEGREES(x) 讲弧度转换为角度
SIN(x) 求正弦值
ASIN(x) 求反正弦值
COS(x) 求余弦值
ACOS(x) 求反余弦值
TAN(x) 求正切值
ATAN(x),ATAN(x,y) 求反正切值
COT(x) 求余切值

 

2.字符串函数

函数 作用
CHAR_LENGTH(s) 返回字符串s的字符数
LENGTH(s) 返回字符串s的长度
CONCAT(s1,s2,…..) 将字符串s1,s2等多个字符串合并为一个字符串
CONCAT_WS(x,s1,s2,….) 同COUCAT(s1,s2,…..),但是每个字符串之间要加上x
INSERT(s1,x,len,s2) 将字符串s2替换s1的x位置开始长度为len的字符串
UPPER(s),UCASE(s) 讲字符串s的所有字符都变成大写字母
LOWER(s),LCASE(s) 讲字符串s的所有字符都变成小写字母
LEFT(s,n) 返回字符串s的前n个字符
RIGHT(s,n) 返回字符串s的后n个字符
LPAD(s1,len,s2) 字符串s2来填充s1的开始处,使字符串长度达到len
RPAD(s1,len,s2) 字符串s2来填充s1的结尾处,使字符串长度达到len
LTRIM(s) 去掉字符串s开始处的空格
RTRIM(s) 去掉字符串s结尾处的空格
TRIM(s) 去掉字符串s开始处和结尾处的空格
TRIM(s1 FROM s) 去掉字符串s中开始处和结尾处的字符串s1
REPEAT(s,n) 将字符串s重复n次
SPACE(n) 返回n个空格
REPLACE(s,s1,s2) 用字符串s2代替字符串s中的字符串s1
STRCMP(s1,s2) 比较字符串s1和s2
SUBSTRING(s,n,len) 获取从字符串s中的第n个位置开始长度为len的字符串
MID(s,n,len) 同SUBSTRING(s,n,len)
ATE(s1,s),POSTTION(s1  IN s) 从字符串s中获取s1的开始位置
INSTR(s,s1) 从字符串s中获取s1的开始位置
REVERSE(s) 将字符串s的顺序反过来
ELT(n,s1,s2…) 返回第n个字符串
FIELD(s,s1,s2…) 返回第一个与字符串s匹配的字符串的位置
FIND_IN_SET(s1,s2) 返回在字符串s2中与s1匹配的字符串的位置
MAKE_SET(x,s1,s2…) 按x的二进制数从s1,s2……sn中选取字符串

 

3.日期和时间函数

函数 作用
CURDATE(),CURRENT_DATE() 返回当前日期
CURTIME(),CURRENT_TIME() 返回当前时间
NOW(),CURRENT_TIMESTAMP()

LOCALTIME(),SYSDATE()

LOCALTIMESTAMP()

返回当前日期和时间
UNIX_TIMESTAMP() 以UNIX时间戳的形式返回当前时间
UNIX_TIMESTAMP(d) 将时间d以UNIX时间戳的形式返回
FROM_UNIXTIME(d) 把UNIX时间戳的时间转换为普通格式的时间
UTC_DATE() 返回UTC(国际协调时间)日期
UTC_TIME() 返回UTC时间
MONTH(d) 返回日期d中的月份值,范围是1~12
MONTHNAME(d) 返回日期d中的月份名称,如january
DAYNAME(d) 返回日期d是星期几,如Monday
DAYOFWEEK(d) 返回日期d是星期几,1表示星期日,2表示星期2
WEEKDAY(d) 返回日期d是星期几,0表示星期一,1表示星期2
WEEK(d) 计算日期d是本年的第几个星期,范围是0-53
WEEKOFYEAR(d) 计算日期d是本年的第几个星期,范围是1-53
DAYOFYEAR(d) 计算日期d是本年的第几天
DAYOFMONTH(d) 计算日期d是本月的第几天
YEAR(d) 返回日期d中的年份值
QUARTER(d) 返回日期d是第几季度,范围1-4
HOUR(t) 返回时间t中的小时值
MINUTE(t) 返回时间t中的分钟值
SECOND(t) 返回时间t中的秒钟值
EXTRACT(type FROM d) 从日期d中获取指定的值,type指定返回的值,如YEAR,HOUR等
TIME_TO_SEC(t) 将时间t转换为秒
SEC_TO_TIME(s) 将以秒为单位的时间s转换为时分秒的格式
TO_DAYS(d) 计算日期d到0000年1月1日的天数
FROM_DAYS(n) 计算从0000年1月1日开始n天后的日期
DATEDIFF(d1,d2) 计算日期d1到d2之间相隔的天数
ADDDATE(d,n) 计算开始日期d加上n天的日期
ADDDATE(d, INTERVAL  expr type) 计算起始日期d加上一个时间段后的日期
SUBDATE(d,n)                           计算起始日期d减去n天的日期
SUBDATE(d, INTERVAL  expr type) 计算起始日期d减去一个时间段后的日期
ADDTIME(t,n) 计算起始时间t加上n秒的时间
SUBTIME(t,n) 计算起始时间t减去n秒的时间
DATE_FORMAT(d,f) 按照表达式f的要求显示日期d
TIME_FORMAT(t,f) 按照表达式f的要求显示时间t
GET_FORMAT(type,s) 根据字符串s获取type类型数据的显示格式

 

 

4.条件判断函数

条件判断函数用来在SQL语句中进行条件判断。更加是否满足判断条件,SQL语句执行不同的分支。

4.1 IF(expr,v1,v2)函数

IF(expr,v1,v2)函数中,如果表达式expr成立,返回结果v1,否则,返回结果v2。

mysql> selectname,if(price>2,‘OK‘,‘ON‘) from food;

+————–+———————–+

| name         | if(price>2,‘OK‘,‘ON‘) |

+————–+———————–+

| 光磊饼干     | OK                    |

| 宪政牛奶     | OK                    |

| 兴周果冻     | ON                    |

| GG咖啡       | OK                   |

| XX奶糖       | OK                    |

+————–+———————–+

5 rows in set (0.00sec)

 

4.2CASE函数

CASEWHEN expr1 THEN v1 [WHEN expr2 THEN v2…][ELSE vn] END

mysql> selectname,case when price =1.5 then ‘OK‘ when price >3.5 then ‘on my god‘ endfrom food;

+————–+———————————————————————+

| name         | case when price =1.5 then ‘OK‘ whenprice >3.5 then ‘on my god‘ end |

+————–+———————————————————————+

| 光磊饼干     | NULL                                                               |

| 宪政牛奶     | NULL                                                               |

| 兴周果冻     | OK                                                                  |

| GG咖啡       | on my god                                                          |

| XX奶糖       | on my god                                                          |

+————–+———————————————————————+

5 rows in set (0.00sec)

 

 

5.系统信息函数

系统信息函数用来查询mysql数据库的系统信息。例如查询数据库的版本,查询数据的当前用户等。

函数 作用
VERSION() 返回数据库的版本号
CONNECTION_ID() 返回服务器的连接数,也就是到现在为止mysql服务的连接次数
DATABASE(),SCHEMA() 返回当前数据库名
USER() 返回当前用户的名称
CHARSET(str) 返回字符串str的字符集
COLLATION(str) 返回字符串str的字符排列方式
LAST_INSERT_ID()                          返回最后生成的auto_increment值

 

6.加密解密函数

加密函数是mysql中用来对数据进行加密的函数。

函数 作用
PASSWORD(str) 对字符串str进行加密
MD5(str) 对字符串str进行加密
ENCODE(str,pswd_str) 使用字符串pswd_str来加密字符串str,加密结果是一个二进制数,必须使用BLOB类型来保持它
DECODE(crypt_str,pswd_str) 解密函数,使用字符串pswd_str来为crypt_str解密

 

7.其他函数

函数 作用
FORMAT(x,n) 格式化函数,可以讲数字x进行格式化,将x保留到小数点后n位,这个过程需要进行四舍五入。
ASCII(s) 返回字符串s的第一个字符的ASSCII码
BIN(x) 返回x的二进制编码
HEX(x) 返回x的十六进制编码
OCT(x) 返回x的八进制编码
CONV(x,f1,f2) 将x从f1进制数变成f2进制数
INET_ATON(IP) 将IP地址转换为数字表示,IP值需要加上引号
INET_NTOA(n) 可以将数字n转换成IP的形式
GET_LOCT(name,time) 加锁函数,定义一个名称为name、持续时间长度为time秒的锁,如果锁定成功,返回1,如果尝试超时,返回0,如果遇到错误,返回NULL.
RELEASE_LOCK(name) 解除名称为name的锁,如果解锁成功,返回1,如果尝试超时,返回0,如果解锁失败,返回NULL。
IS_FREE_LOCK(name) 判断是否使用名为name的锁,如果使用,返回0,否则返回1.
CONVERT(s USING cs) 将字符串s的字符集变成cs
CAST(x AS type),CONVERT(x,type) 这两个函数将x变成type类型,这两个函数只对BINARY,CHAR,DATE,DATETIME,TIME,SIGNED  INTEGER,UNSIGNED INTEGER这些类型起作用,但这两种方法只是改变了输出值得数据类型,并没有改变表中字段的类型。

 

每天进步一点点之Mysql基础教程

Mysql技术总结

1. 用户管理

a) 用户登录

格式: mysql -h主机地址 -u用户名 -p用户密码

例:mysql -h110.110.110.110 -uroot -p123

本地可以直接mysql –uroot -p

b) 用户退出

exit、quit

c) 添加用户

1)insert into

mysql.user表保存的是用户的登录信息。因此可以直接对其进行插入数据的操作,这样可以实现添加用户

insert into mysql.user (host,user,password) values(‘%’,’jifei’,PASSWORD(‘jifei’)); 最新版本这个会出现问题。

注:对于上面的命令首先是password字段已经改名为authentication_string。

其次改正之后还是会报

mysql> insert into mysql.user (host,user,authentication_string) values('%','jifei',PASSWORD('jifei')); 
  ERROR 1364 (HY000): Field 'ssl_cipher' doesn't have a default value

2)grant

grant select on 数据库.* to ‘用户名’@’登录主机’ identified by ‘密码’ WITH GRANT OPTION;

注意:上述的数据库名和数据表名不需要引号。

GRANT命令说明:
ALL PRIVILEGES 是表示所有权限,你也可以使用select、update等权限。

ON 用来指定权限针对哪些库和表。

*.* 中前面的*号用来指定数据库名,后面的*号用来指定表名。

TO 表示将权限赋予某个用户。

jack@’localhost’ 表示jack用户,@后面接限制的主机,可以是IP、IP段、域名以及%,%表示任何地方。注意:这里%有的版本不包括本地,以前碰到过给某个用户设置了%允许任何地方登录,但是在本地登录不了,这个和版本有关系,遇到这个问题再加一个localhost的用户就可以了。

IDENTIFIED BY 指定用户的登录密码。

WITH GRANT OPTION 这个选项表示该用户可以将自己拥有的权限授权给别人。注意:经常有人在创建操作用户的时候不指定WITH GRANT OPTION选项导致后来该用户不能使用GRANT命令创建用户或者给其它用户授权。

备注:可以使用GRANT重复给用户添加权限,权限叠加,比如你先给用户添加一个select权限,然后又给用户添加一个insert权限,那么该用户就同时拥有了select和insert权限。

mysql> grant select,insert on test_db.test_table to 'test_user'@'localhost' identified by '123456' with grant option;
  Query OK, 0 rows affected, 1 warning (0.00 sec)

d) 用户权限

1)添加权限

grant select on 数据库.* to ‘用户名’@’登录主机’ identified by ‘密码’ WITH GRANT OPTION;

这个命令跟创建用户的命令是一模一样的。

权限列表:

权限 权限级别 权限说明
CREATE 数据库、表或索引 创建数据库、表或索引权限
DROP 数据库或表 删除数据库或表权限
GRANT OPTION 数据库、表或保存的程序 赋予权限选项
REFERENCES 数据库或表
ALTER 更改表,比如添加字段、索引等
DELETE 删除数据权限
INDEX 索引权限
INSERT 插入权限
SELECT 查询权限
UPDATE 更新权限
CREATE VIEW 视图 创建视图权限
SHOW VIEW 视图 查看视图权限
ALTER ROUTINE 存储过程 更改存储过程权限
CREATE ROUTINE 存储过程 创建存储过程权限
EXECUTE 存储过程 执行存储过程权限
FILE 服务器主机上的文件访问 文件访问权限
CREATE TEMPORARY TABLES 服务器管理 创建临时表权限
LOCK TABLES 服务器管理 锁表权限
CREATE USER 服务器管理 创建用户权限
PROCESS 服务器管理 查看进程权限
RELOAD  

 

服务器管理

执行flush-hosts, flush-logs, flush-privileges, flush-status, flush-tables, flush-threads, refresh, reload等命令的权限
REPLICATION CLIENT 服务器管理 复制权限
REPLICATION SLAVE 服务器管理 复制权限
SHOW DATABASES 服务器管理 查看数据库权限
SHUTDOWN 服务器管理 关闭数据库权限
SUPER 服务器管理 执行kill线程权限

2)撤销权限

revoke 权限 on 数据库.表 from ‘用户名’@’登录主机’;   //将to改为from

例:revoke all on *.* from ‘jifei’@’%’;

mysql> revoke insert on test_db.test_table from 'test_user'@'localhost';
  Query OK, 0 rows affected (0.00 sec)

3)查看权限

show grants; //自己

mysql> show grants;
+---------------------------------------------------------------------+
| Grants for root@localhost                                           |
+---------------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION |
| GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION        |
+---------------------------------------------------------------------+
2 rows in set (0.00 sec)

show grants for dba@localhost; //指定用户指定host

mysql> show grants for 'test_user'@'localhost';
+-------------------------------------------------------------------------------------+
| Grants for test_user@localhost                                                      |
+-------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'test_user'@'localhost'                                       |
| GRANT SELECT ON `test_db`.`test_table` TO 'test_user'@'localhost' WITH GRANT OPTION |
+-------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

e) 删除用户

(方法一)drop user 用户名;

语法:drop user 用户名;

作用:删除已经存在的用户,例如要删除yan这个用户,(drop user yan;)默认删除的是yan@”%”这个用户,如果还有其他用户,例如yan@”localhost”,yan@”ip”,则不会一起被删除。如果只存在一个用户yan@”localhost”,使用语句(drop user yan;)会报错,应该用(drop user yan@”localhost”;)如果不能确定(用户名@机器名)中的机器名,可以在mysql中的user表中进行查找,user列对应的是用户名,host列对应的是机器名。

mysql> drop user test_user;
  ERROR 1396 (HY000): Operation DROP USER failed for 'test_user'@'%'
mysql> drop user test_user@'localhost';
  Query OK, 0 rows affected (0.00 sec)

(方法二)delete from user where user=”用户名” and host=”localhost”;(不鼓励用这个方法)

delete也是删除用户的方法,例如要删除yan@”localhost”用户,则可以(delete from user where user=”yan” and host=”localhost”;)

注:drop删除掉的用户不仅将user表中的数据删除,还会删除诸如db和其他权限表的内容。而(方法二)只是删除了user表的内容,其他表不会被删除,后期如果命名一个和已删除用户相同的名字,权限就会被继承。

f) 修改密码

1) mysqladmin命令

格式如下(其中,USER为用户名,PASSWORD为新密码):

mysqladmin -u USER -p password PASSWORD  (建议不要把密码写到命令中)

该命令之后会提示输入原密码,输入正确后即可修改。

mysqladmin -utest_user -p password 
  Enter password: 
  New password: 
  Confirm new password: 
  Warning: Since password will be sent to server in plain text, use ssl connection to ensure password safety.

2)UPDATE user 语句

这种方式必须是先用root帐户登入mysql,然后执行:

UPDATE user SET password=PASSWORD(‘新密码’) WHERE user=’账号名’;   (注意新版的password字段改名为authentication_string)
FLUSH PRIVILEGES;

mysql> update user set authentication_string=PASSWORD('123456') where user='test_user';
  ERROR 1046 (3D000): No database selected

mysql> use mysql   //首先要选择数据库,否则会报错
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

mysql> update user set authentication_string=PASSWORD('123456') where user='test_user';
Query OK, 1 row affected, 1 warning (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 1

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

3) SET PASSWORD语句

这种方式也需要先用root命令登入mysql,然后执行:

SET PASSWORD FOR 用户名=PASSWORD(‘新密码’);

mysql> set password for test_user=password('654321');
  ERROR 1133 (42000): Can't find any matching row in the user table

mysql> set password for test_user@'localhost'=password('654321');
  Query OK, 0 rows affected, 1 warning (0.00 sec)

上述产生的原因是:我并没有用户test_user@‘%’,所以这个时候改不了test_user的密码。因此要用下边的语句修改密码。

4)忘记root密码

i) 首先确认服务器出于安全的状态,也就是没有人能够任意地连接MySQL数据库。

因为在重新设置MySQL的root密码的期间,MySQL数据库完全出于没有密码保护的状态下,其他的用户也可以任意地登录和修改MySQL的信息。可以采用将MySQL对外的端口封闭,并且停止Apache以及所有的用户进程的方法实现服务器的准安全状态。最安全的状态是到服务器的Console上面操作,并且拔掉网线。

ii) 修改MySQL的登录设置:

# vi /etc/my.cnf

在[mysqld]的段中加上一句:skip-grant-tables

例如:

[mysqld]

datadir=/var/lib/mysql

socket=/var/lib/mysql/mysql.sock

skip-grant-tables

保存并且退出vi。

iii) 重新启动mysqld

# /etc/init.d/mysqld restart

Stopping MySQL: [ OK ]

Starting MySQL: [ OK ]

iv) 登录并修改MySQL的root密码
# /usr/bin/mysql 
  Welcome to the MySQL monitor. Commands end with ; or \g. 
  Your MySQL connection id is 3 to server version: 3.23.56 
  Type 'help;' or '\h' for help. Type '\c' to clear the buffer. 
  mysql> USE mysql ; 
    Reading table information for completion of table and column names 
    You can turn off this feature to get a quicker startup with -A 

    Database changed 

  mysql> UPDATE user SET Password = password ( 'new-password' ) WHERE User = 'root' ; 
    Query OK, 0 rows affected (0.00 sec) 
    Rows matched: 2 Changed: 0 Warnings: 0 
  mysql> flush privileges ; 
    Query OK, 0 rows affected (0.01 sec) 
  mysql> quit 
    Bye
v) 将MySQL的登录设置修改回来

# vi /etc/my.cnf

将刚才在[mysqld]的段中加上的skip-grant-tables删除

保存并且退出vi。

vi) 重新启动mysqld

# /etc/init.d/mysqld restart

Stopping MySQL: [ OK ]

Starting MySQL: [ OK ]

g) 其他一些权限操作

1)刷新权限

flush privileges;

2)对账户重命名

rename user ‘oldusername’@’oldhost’ to ‘newusername’@’newhost’

mysql> rename user 'test_user'@'localhost' to 'test_user1'@'localhost';
Query OK, 0 rows affected (0.00 sec)

3) 查看当前用户

select user();

mysql> select user();
+----------------------+
| user()               |
+----------------------+
| test_user1@localhost |
+----------------------+
1 row in set (0.00 sec)

2. 数据库管理

a) 创建数据库

方法原型:

CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] db_name    [create_specification]

create_specification:    [DEFAULT] CHARACTER SET [=] charset_name  | [DEFAULT] COLLATE [=] collation_name

注意:在mysql中DATABASE和SCHEMA是一个东西,但是在其他的关系型数据库中就不一定。

例:CREATE DATABASE IF NOT EXISTS yourdbname DEFAULT CHARSET utf8 COLLATE utf8_general_ci;

mysql> create database if not exists test_db1 charset utf8;
Query OK, 1 row affected (0.00 sec)


mysql> create database if not exists test_db1 character set utf8;
Query OK, 1 row affected (0.00 sec)

注意:CHARACTER跟charset 是一个东西

b) 删除数据库(慎用)

方法一:

方法原型:

DROP {DATABASE | SCHEMA} [IF EXISTS] db_name

例: drop database xhkdb;

mysql> drop database if exists test_db1;
Query OK, 0 rows affected (0.00 sec)

方法二:

使用普通用户登陆mysql服务器,你可能需要特定的权限来创建或者删除 MySQL 数据库。

所以我们这边使用root用户登录,root用户拥有最高权限,可以使用 mysql mysqladmin 命令来创建数据库。

在删除数据库过程中,务必要十分谨慎,因为在执行删除命令后,所有数据将会消失。

以下实例删除数据库TUTORIALS(该数据库在前一章节已创建):

[root@host]# mysqladmin -u root -p drop TUTORIALS
Enter password:******
执行以上删除数据库命令后,会出现一个提示框,来确认是否真的删除数据库:

1

2

3

4

5

Dropping the database is potentially a very bad thing to do.

Any data stored in the database will be destroyed.

 

Do you really want to drop the ‘TUTORIALS’ database [y/N] y

Database “TUTORIALS” dropped

c) 得到数据库

show databases;

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| test_db            |
+--------------------+
2 rows in set (0.00 sec)

d) 选择数据库

use databasename;

mysql> use test_db
  Reading table information for completion of table and column names
  You can turn off this feature to get a quicker startup with -A

  Database changed

3. 数据表管理

a) 创建表

 

b) 删除表

drop table命令用于删除数据表。

drop table命令格式:

DROP [TEMPORARY] TABLE [IF EXISTS]
    tbl_name [, tbl_name] ...
    [RESTRICT | CASCADE]

例如,删除表名为 MyClass 的表:
mysql> drop table MyClass;

mysql> drop table if exists test_table;
Query OK, 0 rows affected (0.14 sec)

DROP TABLE用于删除一个或多个表。您必须有每个表的DROP权限。所有的表数据和表定义会被取消,所以使用本语句要小心!

注意:对于一个带分区的表,DROP TABLE会永久性地取消表定义,取消各分区,并取消储存在这些分区中的所有数据。DROP TABLE还会取消与被取消的表有关联的分区定义(.par)文件。

对于不存在的表,使用IF EXISTS用于防止错误发生。当使用IF EXISTS时,对于每个不存在的表,会生成一个NOTE(警告)。

RESTRICT和CASCADE可以使分区更容易。目前,RESTRICT和CASCADE不起作用。

c) 清空表

方法一:truncate

TRUNCATE [TABLE] tbl_name

方法二:delete

delete from 表名;

注:

不带where参数的delete语句可以删除mysql表中所有内容,使用truncate table也可以清空mysql表中所有内容。效率上truncatedelete快,但truncate删除后不记录mysql日志,不可以恢复数据。

delete的效果有点像将mysql表中所有记录一条一条删除到删完,而truncate相当于保留mysql表的结构,重新创建了这个表,所有的状态都相当于新表。

d) 修改表结构

 

f) 得到表结构

mysql> desc MyClass;
mysql> show columns from MyClass;

mysql> desc test_table;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id    | int(11) | NO   |     | NULL    |       |
+-------+---------+------+-----+---------+-------+
1 row in set (0.01 sec)

mysql> show columns from test_table;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id    | int(11) | NO   |     | NULL    |       |
+-------+---------+------+-----+---------+-------+
1 row in set (0.00 sec)

4. 数据操作(增删改查)

a) 插入数据

 

b) 删除数据

 

c) 更新数据

 

d) 查询数据

 

5. 数据库备份