IT技術互動交流平台(tai)

九州体彩官网

來源︰IT165收集(ji)  發布(bu)日期︰2020-02-26 03:08:47

1.前(qian)言

HBase是雲計算環境下最重要的NOSQL數(shu)據庫,提供了基于Hadoop的數(shu)據存儲(chu)、索引、查詢,其(qi)最大的優點(dian)就(jiu)是qiang)ke)以通過硬bu)睦kuo)展從(cong)而幾乎(hu)無限的擴(kuo)展其(qi)存儲(chu)和檢索能力。但是HBase與(yu)傳統的基于SQL語(yu)言的關系數(shu)據庫無論(lun)從(cong)理念(nian)還是使(shi)用方式(shi)上都相(xiang)去(qu)甚遠,以至(zhi)于要將基于SQL的項(xiang)目(mu)移植到HBase時(shi)往往需要重寫(xie)整個項(xiang)目(mu)。
為了si)餼穌飧鑫wen)題,很多開源項(xiang)目(mu)提供了HBase的類SQL中間件,意(yi)即提供一種(zhong)在HBase上使(shi)用的類SQL語(yu)言,使(shi)得程序員能夠像使(shi)用關系數(shu)據庫一樣使(shi)用HBase,Apache Phoenix就(jiu)是其(qi)中的一個優秀項(xiang)目(mu)。
本文(wen)介紹了如(ru)何將基于傳統關系數(shu)據庫的mou)絛蟯 pache Phoenix移植到基于HBase的雲計算平台(tai)上的方法(fa),並詳細講述了該過程中踫到的種(zhong)種(zhong)困(kun)難。主要內容包括︰

HBase及雲計算環境的安裝配置(zhi);HBase的Java API編程;Phoenix的安裝配置(zhi)與(yu)使(shi)用;Squirrel的安裝配置(zhi)與(yu)使(shi)用;使(shi)用Phoenix移植SQL代(dai)碼至(zhi)HBase;Phoenix性能調優;

本文(wen)的讀(du)者應該是數(shu)據庫系統項(xiang)目(mu)的mu) ?嗽焙臀 ?嗽保 萍撲閬xiang)目(mu)開發人員,最好具有以下基本知識︰

linux系統使(shi)用常(chang)識;Hadoop、Hbase、Zookeeper等雲計算環境使(shi)用常(chang)識;Java編程開發基礎;SQL語(yu)言基礎;Oracle、SQLServer或Mysql等關系數(shu)據庫使(shi)用管理基礎

2. HBase及雲計算環境的安裝配置(zhi)

九州体彩官网

雲計算環境通常(chang)安裝在linux或者CentOS等類UNIX操(cao)作系統中,本文(wen)涉及的軟件至(zhi)少需要三個,即Hadoop、Hbase和Zookeeper,其(qi)版本號如(ru)下︰

hadoop-2.3.0-cdh5.1.0zookeeper-3.4.5-cdh5.1.0hbase-0.98.1-cdh5.1.0
注意(yi)︰本文(wen)使(shi)用了雲時(shi)代(dai)的版本5.1.0,由于此(ci)類軟件版本眾多,互相(xiang)之間的兼容性復雜,因此(ci)最好統一采用cdh的版本。系統配置(zhi)如(ru)下圖所(suo)示︰
這里寫(xie)圖片描述

系統一共六個節(jie)點(dian),即Node1~Node6,hadoop安裝在全部六個節(jie)點(dian)上,其(qi)中Node1和Node2是NameNode,其(qi)他是DataNode;ZooKeeper安裝在Node4、Node5和Node6上,其(qi)端口使(shi)用默認的2181;Hbase安裝在Node1、Node3~Node6上,其(qi)眾?http://www.it165.net/pro/pkqt/" target="_blank" class="keylink">QTm9kZTHKx0hNYXN0ZXKjrMbky/vKx0hSZWdpb25TZXJ2ZXKhoyA8YnI+Cr7fzOWyzsr9xeTWw7/J0tSyzr+8xuTL+87EtbWjrLTLtKayu9f2z+rPuMPoyvahoyA8YnI+CjxlbT7XotLio7q/zbuntsux2NDrzai5/Vpvb0tlZXBlctXStb1IYmFzZbXEyOu/2qGjttTT2r/Nu6fAtMu1o6zWu9Do0qrWqrXAWm9vS2VlcGVy1NrExLb5o7vQ6NKqt8POymhiYXNlyrGjrL/Nu6e2y8il1dJab29LZWVwZXKjrFpvb0tlZXBlctTZyKWy6dGvSEJhc2W1xEhNYXN0ZXK6zUhSZWdpb25TZXJ2ZXK1yNDFz6KjrL7fzOXH6b/2vPuhtkhCYXNlyrXVvaG3NjPSs6GjPC9lbT48L3A+Cgo8aDMgaWQ9"22-hbase-shell使(shi)用">2.2 HBase Shell使(shi)用

環境配置(zhi)成功後,即可(ke)使(shi)用HBase Shell對HBase數(shu)據庫進行(xing)操(cao)作,類似于Oracle提供的sqlplus。
登陸任意(yi)一個安裝了HBase的服務器,輸入︰

hbase shelllist

即可(ke)列出該hbase中xie)媧chu)的所(suo)有表(biao)格。
創建一個名(ming)為test的表(biao)格,它帶有一個名(ming)為cf的列族,並使(shi)用list來查看表(biao)格是否被(bei)創建,然後插(cha)入一些數(shu)據︰

hbase(main):003:0> create 'test', 'cf'0 row(s) in 1.2200 secondshbase(main):003:0> listtest1 row(s) in 0.0550 secondshbase(main):004:0> put 'test', 'row1', 'cf:a', 'value1'0 row(s) in 0.0560 secondshbase(main):005:0> put 'test', 'row2', 'cf:b', 'value2'0 row(s) in 0.0370 secondshbase(main):006:0> put 'test', 'row3', 'cf:c', 'value3'0 row(s) in 0.0450 seconds

使(shi)用scan來查看test表(biao)格中的內容︰

hbase(main):007:0> scan 'test'ROW COLUMN+CELLrow1 column=cf:a, timestamp=1288380727188, value=value1row2 column=cf:b, timestamp=1288380738440, value=value2row3 column=cf:c, timestamp=1288380747365, value=value33 row(s) in 0.0590 seconds

得到表(biao)中的一行(xing)數(shu)據︰

hbase(main):008:0> get 'test', 'row1'COLUMN CELLcf:a timestamp=1288380727188, value=value11 row(s) in 0.0400 secondsdisable和drop一個表(biao)格︰hbase(main):012:0> disable 'test'0 row(s) in 1.0930 secondshbase(main):013:0> drop 'test'0 row(s) in 0.0770 seconds 

退出shell︰

hbase(main):014:0> exit

其(qi)他更多具體的命(ming)令請參看HBase的手冊或者在線幫助。

3. HBase Java API 編程

使(shi)用HBase的Java API進行(xing)開發需要掌握(wo)HBase的基本理念(nian),推薦閱讀(du)《HBase實戰(zhan)》一書。
在進行(xing)開發的操(cao)作系統chang) ru)Windows、Linux或者CentOS)中解壓hbase-0.98.1-cdh5.1.0.tar.gz,得到開發所(suo)依(yi)賴的所(suo)有jar包,位于hbase-0.98.1-cdh5.1.0/lib目(mu)錄中xiao)
在開發環境chang) ru)Eclipse、NetBean或者Intellij)中建立工程,導入hbase-0.98.1-cdh5.1.0\lib中的所(suo)有jar包。

九州体彩官网

在給(gei)出源代(dai)碼之前(qian),先介紹一下遠程連接HBase的問(wen)題。從(cong)Oracle時(shi)代(dai)過來的mou)絛蛟保 xian)然期望得到數(shu)據庫服務器的ip、port和Service Name之類的信息。但是在連接HBase時(shi),你需要的卻(que)是一個或多個ZooKeeper服務器的ip(或者hostname)和port,因為只(zhi)有它才知曉整個HBase集(ji)群的元(yuan)數(shu)據。
顯(xian)然,使(shi)用hostname比使(shi)用ip要顯(xian)得習慣更好,因為它帶來了更大的mu)ke)移植性,因此(ci)費一點(dian)筆(bi)墨講講linux和windows的hostname設置(zhi)。
在linux下,hostname通過修(xiu)改/etc/hosts文(wen)件來完成,在集(ji)群的每(mei)台(tai)服務器上加入如(ru)下內容︰

192.168.1.101 Node1192.168.1.102 Node2192.168.1.103 Node3192.168.1.104 Node4192.168.1.105 Node5192.168.1.106 Node6

在各自的/etc/sysconfig/network文(wen)件中,將“HOSTNAME=”修(xiu)改為“HOSTNAME=Node?”(將Node?替換為本服務器的hostname)。
在Windows下(僅(jin)測(ce)試過Win7 64),修(xiu)改Windows/System32/drivers/etc/hosts文(wen)件,加入︰

192.168.1.101 Node1192.168.1.102 Node2192.168.1.103 Node3192.168.1.104 Node4192.168.1.105 Node5192.168.1.106 Node6

(不同(tong)的windows平台(tai)hosts文(wen)件的位置(zhi)可(ke)能不一樣,建議裝一個everything,桌面搜索速度極快)。
其(qi)實多種(zhong)方法(fa)都可(ke)以連接到ZooKeeper,例如(ru)ip加端口︰

public static String hbase_svr_ip = "192.168.1.104, 192.168.1.105, 192.168.1.106";public static String hbase_svr_port = "2181";

或者hostname加端口︰

public static String hbase_svr_hostname = "Node4,Node5,Node6";public static String hbase_svr_port = "2181";

或者將端口直接寫(xie)在ip後︰

public static String hbase_svr_ip = "192.168.1.104:2181, 192.168.1.105:2181, 192.168.1.106:2181";

或者將端口直接寫(xie)在hostname後︰

public static String hbase_svr_hostname = "Node4:2181,Node5:2181,Node6:2181";

或者僅(jin)使(shi)用一個ZooKeeper服務器︰

public static String hbase_svr_hostname = "Node4:2181";

具體使(shi)用哪種(zhong)方法(fa)就(jiu)看程序員自己的偏好,也存在某(mou)種(zhong)方法(fa)在某(mou)些版本中可(ke)能無法(fa)連接的問(wen)題,本文(wen)中沒(mei)有窮盡測(ce)試,但個人認為hostname加端口的方法(fa)可(ke)能比較穩妥。

九州体彩官网

本篇給(gei)出了使(shi)用Java API操(cao)作HBase的源代(dai)碼,注意(yi)要將這幾行(xing)替換為實際的ZooKeeper服務器地址、hostname和端口號︰

public static String hbase_svr_ip = "192.168.1.104, 192.168.1.105, 192.168.1.106";public static String hbase_svr_port = "2181";public static String hbase_svr_hostname = "Node4,Node5,Node6";

代(dai)碼功能包括︰

遠程連接Hbase數(shu)據庫;創建表(biao);掃描所(suo)有表(biao);插(cha)入數(shu)據;掃描數(shu)據;刪(shan)除數(shu)據;刪(shan)除表(biao)。
package com.wxb;import java.io.IOException;import java.util.ArrayList;import java.util.List;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.hbase.Cell;import org.apache.hadoop.hbase.CellUtil;import org.apache.hadoop.hbase.HBaseConfiguration;import org.apache.hadoop.hbase.HColumnDescriptor;import org.apache.hadoop.hbase.HTableDescriptor;import org.apache.hadoop.hbase.client.Delete;import org.apache.hadoop.hbase.client.Get;import org.apache.hadoop.hbase.client.HBaseAdmin;import org.apache.hadoop.hbase.client.HConnection;import org.apache.hadoop.hbase.client.HConnectionManager;import org.apache.hadoop.hbase.client.HTableInterface;import org.apache.hadoop.hbase.client.Put;import org.apache.hadoop.hbase.client.Result;import org.apache.hadoop.hbase.client.ResultScanner;import org.apache.hadoop.hbase.client.Scan;import org.apache.hadoop.hbase.util.Bytes;/** * @author wxb hbase的基本操(cao)作方法(fa) */public class HBaseSample {public static String hbase_svr_ip = "192.168.1.104, 192.168.1.105, 192.168.1.106"; public static String hbase_svr_port = "2181"; public static String hbase_svr_hostname = "Node4,Node5,Node6"; private HConnection connection = null; Configuration config = null; /** * 構造函數(shu),構造一個HBaseSample對象,必(bi)須在最後調用close方法(fa)來關閉(bi)所(suo)有的連接,釋放所(suo)有的資源 */ public HBaseSample() { config = HBaseConfiguration.create(); config.set("hbase.zookeeper.quorum", hbase_svr_hostname); config.set("hbase.zookeeper.property.clientPort", hbase_svr_port); // System.out.println(config.get("hbase.zookeeper.quorum")); // System.out.println(config.get("hbase.zookeeper.property.clientPort")); try {  connection = HConnectionManager.createConnection(config); } catch (IOException e) {  e.printStackTrace(); } } /** * 釋放資源 */ public void close() { try {  if (null != connection) {  connection.close();  } } catch (IOException e) {  e.printStackTrace(); } } /** * 創建表(biao)格 * * @param tableName * @param columnFarily */ public void createTable(final String tableName, String columnFarily) { if (null != config) {  System.out.println("begin create table...");  HBaseAdmin admin = null;  try {  admin = new HBaseAdmin(config);  if (admin.tableExists(tableName)) {   System.out.println(tableName + " is already exist!");  } else {   HTableDescriptor tableDesc = new HTableDescriptor(tableName);   tableDesc.addFamily(new HColumnDescriptor(columnFarily));   admin.createTable(tableDesc);   System.out.println(tableDesc.toString()    + " has been created.");  }  admin.close();  } catch (IOException e) {  e.printStackTrace();  } } else {  System.out.println("hbase could not connected!"); } } /** * 向指定(ding)表(biao)格中添(tian)加一行(xing)數(shu)據 * * @param table * @param key * @param family * @param col * @param dataIn * @return */ public boolean addOneRecord(String table, String key, String family,  String col, byte[] dataIn) { if (null != connection) {  try {  HTableInterface tb = connection.getTable(table);  Put put = new Put(key.getBytes());  put.add(family.getBytes(), col.getBytes(), dataIn);  tb.put(put);  System.out.println("put data key = " + key);  return true;  } catch (IOException e) {  System.out.println("put data failed.");  return false;  } } else {  System.out.println("hbase could not connected!");  return false; } } /** * 得到hbase中所(suo)有的表(biao) * * @return */ public List<String> getAllTables() { List<String> tables = null; if (connection != null) {  try {  HTableDescriptor[] allTable = connection.listTables();  if (allTable.length > 0)   tables = new ArrayList<String>();  for (HTableDescriptor hTableDescriptor : allTable) {   tables.add(hTableDescriptor.getNameAsString());   System.out.println(hTableDescriptor.getNameAsString());  }  } catch (IOException e) {  e.printStackTrace();  } } else {  System.out.println("hbase could not connected!"); } return tables; } public byte[] getValueWithKey(String tableName, String rowKey,  String family, String qualifier) { byte[] rel = null; if (null != connection) {  try {  HTableInterface table = connection.getTable(tableName);  Get get = new Get(rowKey.getBytes());  get.addColumn(Bytes.toBytes(family), Bytes.toBytes(qualifier));  Result result = table.get(get);  if (!result.isEmpty()) {   rel = result.getValue(Bytes.toBytes(family),    Bytes.toBytes(qualifier));  }  } catch (IOException e) {  e.printStackTrace();  } } else {  System.out.println("hbase could not connected!"); } return rel; } /** * 從(cong)表(biao)中刪(shan)除一行(xing) * * @param tableName * @param rowKey */ public void deleteWithKey(String tableName, String rowKey) { if (null != connection) {  try {  HTableInterface table = connection.getTable(tableName);  Delete delete = new Delete(rowKey.getBytes());  table.delete(delete);  } catch (IOException e) {  e.printStackTrace();  } } else {  System.out.println("hbase could not connected!"); } } /** * 得到一個表(biao)中的所(suo)有元(yuan)素 * * @param tableName */ public void getAllData(String tableName) { if (null != connection) {  try {  HTableInterface table = connection.getTable(tableName);  Scan scan = new Scan();  ResultScanner rs = table.getScanner(scan);  for (Result r : rs) {   Cell[] cells = r.rawCells();   System.out.println("This row have " + cells.length    + " cells:");   for (Cell cell : cells) {   String row = Bytes.toString(CellUtil.cloneRow(cell));   String family = Bytes.toString(CellUtil    .cloneFamily(cell));   String qualifier = Bytes.toString(CellUtil    .cloneQualifier(cell));   String value = Bytes    .toString(CellUtil.cloneValue(cell));   System.out.println(String.format("%s:%s:%s:%s", row,    family, qualifier, value));   }  }  } catch (IOException e) {  e.printStackTrace();  } } else {  System.out.println("hbase could not connected!"); } } public void deleteTable(String tableName) { if (null != config) {  System.out.println("begin delete table...");  HBaseAdmin admin = null;  try {  admin = new HBaseAdmin(config);  if (!admin.tableExists(tableName)) {   System.out.println(tableName + " is not exist!");  } else {   admin.disableTable(tableName);   admin.deleteTable(tableName);   System.out.println(tableName + " has been deleted.");  }  admin.close();  } catch (IOException e) {  e.printStackTrace();  } } else {  System.out.println("hbase could not connected!"); } } /** * @param args */ public static void main(String[] args) { HBaseSample sample = new HBaseSample(); // 1.create table and insert data sample.createTable("student", "fam1"); sample.addOneRecord("student", "id1", "fam1", "name", "Jack".getBytes()); sample.addOneRecord("student", "id1", "fam1", "address",  "HZ".getBytes()); // 2.list table sample.getAllTables(); // 3.getValue byte[] value = sample.getValueWithKey("student", "id1", "fam1",  "address"); System.out.println("value = " + Bytes.toString(value)); // 4.addOneRecord and delete// sample.addOneRecord("student", "id2", "fam1", "name", "wxb".getBytes());// sample.addOneRecord("student", "id2", "fam1", "address",//  "here".getBytes());// sample.deleteWithKey("student", "id2"); // 5.scan table sample.getAllData("student"); // 6.delete table // sample.deleteTable("student"); sample.close(); }}

4. Phoenix的安裝配置(zhi)與(yu)使(shi)用

從(cong)上一章可(ke)以看出,HBase的基本理念(nian)和傳統的關系數(shu)據庫是截然不同(tong)的,為了使(shi)得熟悉SQL的mou)絛蛟蹦芄豢燜偈shi)用HBase,使(shi)用Apache Phoenix是比較好的辦法(fa)。它提供了一組(zu)類似于SQL的語(yu)法(fa),以及序列xiao)?饕  shu)等工具,使(shi)得將SQL代(dai)碼移植至(zhi)HBase成為可(ke)能。

九州体彩官网

同(tong)其(qi)他分布(bu)式(shi)軟件一樣,Phoenix的安裝也是較為復雜的,且要密切關注其(qi)版本兼容性,否則很可(ke)能無法(fa)正(zheng)常(chang)運(yun)行(xing)xiao)@ru)Phoenix4.x版本jing)加屑嬡Base0.98的版本,但是經(jing)過兩天(tian)的測(ce)試才發現不同(tong)的Phoenix版本jing)Base0.98的小(xiao)版本號的要求不同(tong)。
由于本文(wen)使(shi)用的是HBase0.98.1,因此(ci)只(zhi)能使(shi)用Phoenix4.1.0版本。如(ru)果(guo)使(shi)用的Phoenix版本和HBase版本不兼容,會出現zhi)諞淮文(wen)芄渙 Base,但以後都連接失敗的現象。
Phoenix的具體安裝步驟如(ru)下︰
第一步︰將phoenix-4.1.0-bin.tar.gz拷貝到Node1(HBase的HMaster)的某(mou)路(lu)徑(jing)下,解壓縮,拷貝hadoop2/phoenix-4.1.0-server-hadoop2.jar到HBase的lib目(mu)錄下。
第二步︰然後用scp(關于scp和ssh的設置(zhi)請參考網上的其(qi)他文(wen)章,假設用mei) ming)為hadoop)拷貝到各個regionserver的HBase的lib目(mu)錄下︰

scp phoenix-4.1.0-server-hadoop2.jar hadoop@Node3:/home/hadoop/hbase-0.98.1-cdh5.1.0/lib/phoenix-core-4.6.0-HBase-0.98.jar             scp phoenix-4.1.0-server-hadoop2.jar hadoop@Node4:/home/hadoop/hbase-0.98.1-cdh5.1.0/lib/phoenix-core-4.6.0-HBase-0.98.jar             scp phoenix-4.1.0-server-hadoop2.jar hadoop@Node5:/home/hadoop/hbase-0.98.1-cdh5.1.0/lib/phoenix-core-4.6.0-HBase-0.98.jarscp phoenix-4.1.0-server-hadoop2.jar hadoop@Node6:/home/hadoop/hbase-0.98.1-cdh5.1.0/lib/phoenix-core-4.6.0-HBase-0.98.jar  

第三步︰在HMaster上重啟hbase(即Node1);
第四步︰將phoenix-4.1.0-client-hadoop2.jar加入客戶端的CLASSPATH變量(liang)路(lu)徑(jing)中,修(xiu)改用mei)?bash_profile文(wen)件,同(tong)時(shi)將此(ci)文(wen)件拷貝到hbase的lib目(mu)錄下。
第五步︰測(ce)試使(shi)用phoenix,輸入命(ming)令︰

sqlline.py Node4:2181

注意(yi)︰後面的參wen)ooKeeper的服務器和端口。
出現以下顯(xian)示則說明連接成功。

[hadoop@iips25 hadoop2]$bin/sqlline.py Node1:2181Setting property: [isolation, TRANSACTION_READ_COMMITTED]issuing: !connect jdbc:phoenix:Node4 none none org.apache.phoenix.jdbc.PhoenixDriverConnecting to jdbc:phoenix:Node416/06/21 08:04:24 WARN impl.MetricsConfig: Cannot locate configuration: tried hadoop-metrics2-phoenix.properties,hadoop-metrics2.propertiesConnected to: Phoenix (version 4.1)Driver: org.apache.phoenix.jdbc.PhoenixDriver (version 4.1)Autocommit status: trueTransaction isolation: TRANSACTION_READ_COMMITTEDBuilding list of tables and columns for tab-completion (set fastconnect to true to skip)...59/59 (100%) DoneDonesqlline version 1.1.20: jdbc:phoenix:Node4>

查看數(shu)據庫表(biao)︰(注意(yi),phoenix只(zhi)能看到自己創建的表(biao),不能看到HBase創建的表(biao))

0: jdbc:phoenix:Node4> !tables+------------+-------------+------------+------------+------------+------------+---------------------------+----------------+-------------+----------------+--------+ TABLE_CAT TABLE_SCHEM TABLE_NAME TABLE_TYPE REMARKS TYPE_NAME SELF_REFERENCING_COL_NAME REF_GENERATION INDEX_STATE IMMUTABLE_ROWS SALT_B +------------+-------------+------------+------------+------------+------------+---------------------------+----------------+-------------+----------------+--------+ null SYSTEM CATALOG SYSTEM TABLE null null null   null  null  false  null null SYSTEM SEQUENCE SYSTEM TABLE null null null   null  null  false  null +------------+-------------+------------+------------+------------+------------+---------------------------+----------------+-------------+----------------+--------+0: jdbc:phoenix:Node4>

創建表(biao),並插(cha)入數(shu)據︰

0: jdbc:phoenix:Node4> create table abc(a integer primary key, b integer) ;No rows affected (1.133 seconds)0: jdbc:phoenix:Node4> UPSERT INTO abc VALUES (1, 1); 1 row affected (0.064 seconds)0: jdbc:phoenix:Node4> UPSERT INTO abc VALUES (2, 2); 1 row affected (0.009 seconds)0: jdbc:phoenix:Node4> UPSERT INTO abc VALUES (3, 12); 1 row affected (0.009 seconds)0: jdbc:phoenix:Node4> select * from abc;+------------+------------+ A  B +------------+------------+ 1  1  2  2  3  12  +------------+------------+3 rows selected (0.082 seconds)0: jdbc:phoenix:Node4>

創建包含中文(wen)的表(biao)(注意(yi)中文(wen)要使(shi)用VARCHAR)︰

create table user ( id integer primary key, name VARCHAR);upsert into user values ( 2, '測(ce)試員2');upsert into user values ( 1, '測(ce)試員1');select * from user;+------------+------------+ ID  NAME +------------+------------+ 1  測(ce)試員1  2  測(ce)試員2  

九州体彩官网

在hbase集(ji)群每(mei)個服務器的hbase-site.xml配置(zhi)文(wen)件中,加入︰

<property> <name>hbase.regionserver.wal.codec</name> <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value></property>

這是在phoenix中建立索引的先決條(tiao)件。如(ru)果(guo)不添(tian)加此(ci)設置(zhi),Phoenix依(yi)然可(ke)以正(zheng)常(chang)使(shi)用,但不能建立索引。

九州体彩官网

phoenix的語(yu)法(fa)可(ke)參考其(qi)官方網站,也可(ke)下載其(qi)“Grammar _ Apache Phoenix.html”網頁。
訪問(wen)Phoenix時(shi),可(ke)以使(shi)用其(qi)提供的sqlline.py命(ming)令,也可(ke)以使(shi)用下一章介紹的數(shu)據庫圖形界面工具Squirrel,當然也可(ke)以通過Phoenix提供的Java API。

4.3.1. 創建表(biao)

注意(yi)︰Phoenix中的表(biao)必(bi)須有主鍵,這一點(dian)和許(xu)多關系數(shu)據庫不同(tong)。因為主鍵是後續很多表(biao)操(cao)作的必(bi)備(bei)因素。

CREATE TABLE IF NOT EXISTS MYTABLE (ID INTEGER PRIMARY KEY, NAME VARCHAR, SEX VARCHAR, ADDRESS VARCHAR);

4.3.2. 刪(shan)除表(biao)

DROP TABLE IF EXISTS MYTABLE;

4.3.3. 插(cha)入數(shu)據

UPSERT INTO MYTABLE VALUES (1, 'WXB', 'MALE', '010-22222222');

注意(yi)phoenix使(shi)用UPSERT而不是INSERT。

4.3.4. 刪(shan)除數(shu)據

DELETE FROM MYTABLE WHERE ID = 1;

4.3.5. 查詢數(shu)據

SELECT * FROM MYTABLE WHERE ID=1;

4.3.6. 修(xiu)改數(shu)據

UPSERT INTO MYTABLE VALUES (1, 'WXB', 'MALE', '010-22222222');

可(ke)以看到,修(xiu)改數(shu)據與(yu)插(cha)入數(shu)據一樣,都是使(shi)用UPSERT語(yu)句,若此(ci)主鍵對應的行(xing)不存在,就(jiu)插(cha)入,否則就(jiu)修(xiu)改。這也是為什(shi)麼Phoenix的表(biao)必(bi)須有主鍵的原因之一。

4.3.7. 創建序列

Phoenix的序列與(yu)Oracle很像,也是先que)唇  緩蟺饔ext得到下一個值(zhi)。也可(ke)以繼續調用current value得到當前(qian)序列值(zhi),沒(mei)有調用next時(shi),不能使(shi)用current value。
創建一個序列︰

CREATE SEQUENCE IF NOT EXISTS WXB_SEQ START WITH 1000 INCREMENT BY 1 MINVALUE 1000 MAXVALUE 999999999 CYCLE CACHE 30;

其(qi)含義基本上與(yu)Oracle類似。

4.3.8. 使(shi)用序列

序列只(zhi)能在Select或者Upsert語(yu)句中使(shi)用,例如(ru)在Upsert中使(shi)用︰

UPSERT INTO MYTABLE VALUES (NEXT VALUE FOR WXB_SEQ, 'WXB', 'MALE', '010-22222222');

讀(du)取序列的當前(qian)值(zhi)時(shi),采用這個語(yu)句︰

SELECT CURRENT VALUE FOR WXB_SEQ DUALID FROM WXB_DUAL;

然後讀(du)取DUALID就(jiu)可(ke)得到序列的當前(qian)值(zhi)。
這里的WXB_DUAL是我自己創建的一個特殊表(biao),用來模擬(ni)Oracle中的Dual表(biao)。

CREATE TABLE IF NOT EXISTS WXB_DUAL (DUALID INTEGER PRIMARY KEY );UPSERT INTO WXB_DUAL VALUES (1);

4.3.9. 刪(shan)除序列

DROP SEQUENCE IF EXISTS WXB_SEQ;

本章至(zhi)此(ci)為止,詳細的操(cao)作留待後續再(zai)講。

5. 安裝SQuirrel

Squirrel是一個圖形化的數(shu)據庫工具,它可(ke)以將Phoenix以圖形化的方式(shi)展示出來,它可(ke)以安裝在windows或linux系統中xiao)/p>

九州体彩官网

第一步︰
設置(zhi)好JDK,JAVA_HOME,CLASSPATH等一系列的環境變量(liang),注意(yi)無論(lun)是在windows還是在linux下,都需要上面安裝的hbase和phoenix的存放jar包的目(mu)錄,並將其(qi)設置(zhi)到CLASSPATH中xiao)indows下的CLASSPATH如(ru)下︰

%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;D:\hbase-0.98.1-cdh5.1.0\lib;D:\phoenix-4.1.0-bin\hadoop2

linux的CLASSPATH如(ru)下︰

export PHOENIX_HOME=/home/hadoop/phoenix-4.1.0-binexport CLASSPATH=$PHOENIX_HOME/hadoop2/phoenix-4.1.0-client-hadoop2.jar:$HBASE_HOME/lib/:$CLASSPATHexport PATH=$PHOENIX_HOME/bin:$PATH

第二步︰
下載解壓squirrel-sql-snapshot-20160613_2107-standard.jar(最新版本的squirrel安裝包),在命(ming)令行(xing)中運(yun)行(xing)java -jar squirrel-sql-snapshot-20160613_2107-standard.jar開始安裝。
第三步︰執(zhi)行(xing)如(ru)下安裝
1. Remove prior phoenix-[oldversion]-client.jar from the lib directory of SQuirrel, copy phoenix-[newversion]-client.jar to the lib directory (newversion should be compatible with the version of the phoenix server jar used with your HBase installation)
2. Start SQuirrel and add new driver to SQuirrel (Drivers -> New Driver)
3. In Add Driver dialog box, set Name to Phoenix, and set the Example URL to jdbc:phoenix:localhost.
4. Type “org.apache.phoenix.jdbc.PhoenixDriver” into the Class Name textbox and click OK to close this dialog.
5. Switch to Alias tab and create the new Alias (Aliases -> New Aliases)
6. In the dialog box, Name:Any name, Driver: Phoenix, User Name:Anything, Password:Anything
7. Construct URL as follows: jdbc:phoenix:zookeeper quorum server. For example, to connect to a local HBase use: jdbc:phoenix:localhost
8. Press Test (which should succeed if everything is setup correctly) and press OK to close.
9. Now double click on your newly created Phoenix alias and click Connect. Now you are ready to run SQL queries against Phoenix.
注意(yi),我們連接的URL是jdbc:phoenix:Node4,用mei) ming)和密碼隨意(yi)即可(ke)。連接成功後,如(ru)下︰
這里寫(xie)圖片描述

九州体彩官网

安裝完畢後,就(jiu)可(ke)以在Squirrel中執(zhi)行(xing)各種(zhong)phoenix支持zhi)睦QL語(yu)句和觀察數(shu)據了,例如(ru)在SQL欄中輸入如(ru)下語(yu)句︰

CREATE TABLE IF NOT EXISTS MYTABLE (ID INTEGER PRIMARY KEY, NAME VARCHAR, SEX VARCHAR, ADDRESS VARCHAR);UPSERT INTO MYTABLE VALUES (1, 'WXB', 'MALE', '010-22222222');UPSERT INTO MYTABLE VALUES (2, ‘LL’, 'MALE', '010-11111111');SELECT * FROM MYTABLE;

結果(guo)如(ru)下︰
這里寫(xie)圖片描述
使(shi)用Squirrel的好處在于可(ke)以方便的查看數(shu)據庫中的各種(zhong)對象,以及編輯和執(zhi)行(xing)復雜的phoenix類sql腳本。

6. 使(shi)用Phoenix移植SQL代(dai)碼至(zhi)HBase

Phoenix提供了完全適配JDBC的API,程序員可(ke)以像操(cao)作關系數(shu)據庫(例如(ru)Oracle)一樣來使(shi)用JDBC來操(cao)作Phoenix,這也是Phoenix的最大的優勢所(suo)在。唯一需要注意(yi)的是,提交的SQL語(yu)句必(bi)須符合Phoenix語(yu)法(fa),雖然此(ci)語(yu)法(fa)很類似于SQL,但還是有xing)xu)多不同(tong)之處。

九州体彩官网

本章給(gei)出了一個最基本的Phoenix JDBC源代(dai)碼實dao)  yi)其(qi)中所(suo)引用的所(suo)有類幾乎(hu)都來自于java.sql.*包,與(yu)Oracle唯一的不同(tong)是其(qi)driver的字(zi)符串(chuan),該字(zi)符串(chuan)等于前(qian)面連接Squirrel的連接字(zi)符串(chuan),你可(ke)以在Squirrel上測(ce)試driver字(zi)符串(chuan)是否能夠正(zheng)確連接。driver字(zi)符串(chuan)一般為jdbc:phoenix:ZooKeeper_hostname:port,例如(ru)jdbc:phoenix:Node4,Node5,Node6:2181。但是在端口為默認2181端口時(shi),也可(ke)以省略端口號。
編碼之前(qian)將phoenix-4.1.0-client-hadoop2.jar加入java項(xiang)目(mu)的依(yi)賴Libraries,例子代(dai)碼如(ru)下︰

package com.wxb;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;/** * @author wxb Phoenix的基本操(cao)作方法(fa) * */public class PhoenixSample { public static String hbase_svr_ip = "192.168.1.104, 192.168.1.105, 192.168.1.106"; public static String hbase_svr_port = "2181"; public static String hbase_svr_hostname = "Node4,Node5,Node6"; /* * 所(suo)有幾種(zhong)方式(shi)的driver都能夠通過測(ce)試︰ 1.Node4 2.Node4,Node5,Node6 3.Node4:2181 * 4.Node4,Node5,Node6:2181 5.Node4:2181,Node5:2181,Node6:2181 * 6.101.60.27.114 */ public static String driver = "jdbc:phoenix:" + hbase_svr_hostname; public static void createTable(String tableName) { System.out.println("create table " + tableName); Statement stmt = null; try {  Connection con = DriverManager.getConnection(driver);  stmt = con.createStatement();  stmt.executeUpdate("create table if not exists " + tableName   + " (mykey integer not null primary key, mycolumn varchar)");  con.commit();  con.close(); } catch (SQLException e) {  e.printStackTrace(); } } public static void addRecord(String tableName, String values) { Statement stmt = null; try {  Connection con = DriverManager.getConnection(driver);  stmt = con.createStatement();  stmt.executeUpdate("upsert into " + tableName + " values ("   + values + ")");  con.commit();  con.close(); } catch (SQLException e) {  e.printStackTrace(); } } public static void deleteRecord(String tableName, String whereClause) { Statement stmt = null; try {  Connection con = DriverManager.getConnection(driver);  stmt = con.createStatement();  stmt.executeUpdate("delete from " + tableName + " where "   + whereClause);  con.commit();  con.close(); } catch (SQLException e) {  e.printStackTrace(); } } public static void createSequence(String seqName) { System.out.println("Create Sequence :" + seqName); Statement stmt = null; try {  Connection con = DriverManager.getConnection(driver);  stmt = con.createStatement();  stmt.executeUpdate("CREATE SEQUENCE IF NOT EXISTS "   + seqName   + " START WITH 1000 INCREMENT BY 1 MINVALUE 1000 MAXVALUE 999999999 CYCLE CACHE 30");  con.commit();  con.close(); } catch (SQLException e) {  e.printStackTrace(); } } public static void dropSequence(String seqName) { System.out.println("drop Sequence :" + seqName); Statement stmt = null; try {  Connection con = DriverManager.getConnection(driver);  stmt = con.createStatement();  stmt.executeUpdate("DROP SEQUENCE IF EXISTS " + seqName);  con.commit();  con.close(); } catch (SQLException e) {  e.printStackTrace(); } } public static void getAllData(String tableName) { System.out.println("Get all data from :" + tableName); ResultSet rset = null; try {  Connection con = DriverManager.getConnection(driver);  PreparedStatement statement = con.prepareStatement("select * from "   + tableName);  rset = statement.executeQuery();  while (rset.next()) {  System.out.print(rset.getInt("mykey"));  System.out.println(" " + rset.getString("mycolumn"));  }  statement.close();  con.close(); } catch (SQLException e) {  e.printStackTrace(); } } public static void dropTable(String tableName) { Statement stmt = null; try {  Connection con = DriverManager.getConnection(driver);  stmt = con.createStatement();  stmt.executeUpdate("drop table if exists " + tableName);  con.commit();  con.close();  System.out.println("drop table " + tableName); } catch (SQLException e) {  e.printStackTrace(); } } public static void main(String[] args) { createTable("wxb_test"); createSequence("WXB_SEQ_ID"); // 使(shi)用了Sequence addRecord("wxb_test", "NEXT VALUE FOR WXB_SEQ_ID,'wxb'"); addRecord("wxb_test", "NEXT VALUE FOR WXB_SEQ_ID,'wjw'"); addRecord("wxb_test", "NEXT VALUE FOR WXB_SEQ_ID,'wjl'"); // deleteRecord("wxb_test", " mykey = 1 "); getAllData("wxb_test"); // dropTable("wxb_test");// dropSequence("WXB_SEQ_ID"); }}

九州体彩官网

在使(shi)用Phoenix時(shi),建立的每(mei)個表(biao)都必(bi)須包含一個主鍵,這與(yu)關系數(shu)據庫不同(tong)。而且每(mei)個表(biao)的主鍵會自動被(bei)索引,這意(yi)味著在select語(yu)句的where子句中使(shi)用主鍵作為條(tiao)件,會得到最快的查詢速度。關于索引,在後續章節(jie)中再(zai)詳細介紹。
我ye)慕ㄒ槭牽  mei)個表(biao)創建一個序列,並在插(cha)入數(shu)據時(shi)以序列的值(zhi)作為主鍵的值(zhi)。

九州体彩官网

Phoenix支持用mei)?約捍唇DBC連接池(chi),可(ke)以將基于JDBC連接池(chi)的代(dai)碼復制(zhi)過來,把Driver部分修(xiu)改一番即可(ke)。

九州体彩官网

涉及中文(wen)的字(zi)段可(ke)設置(zhi)為VARCHAR類型,經(jing)測(ce)試沒(mei)有問(wen)題。

九州体彩官网

CLOB和BLOB字(zi)段我都設置(zhi)為VARCHAR類型,經(jing)測(ce)試存儲(chu)400k字(zi)節(jie)的數(shu)據沒(mei)有問(wen)題,更多的沒(mei)有測(ce)試。

九州体彩官网

因為本文(wen)使(shi)用的Phoenix版本不是最新版,因此(ci)官網上給(gei)出的SQL語(yu)法(fa)不是完全都能夠支持,例如(ru)下面的語(yu)句就(jiu)不能支持︰

delete from wxb_senword where swid in (select swid from wxb_rela_sw_group where groupid=1)

因此(ci)對于一些復雜的SQL語(yu)句,需要先到官網上查詢語(yu)法(fa),然後在phoenix中進行(xing)測(ce)試,測(ce)試通過後才能夠在程序中使(shi)用。
兩個表(biao)的關聯查詢是qiang)ke)行(xing)的,語(yu)句如(ru)下︰

SELECT d.swid,d.swname, d.userid, e.groupid FROM wxb_senword d JOIN wxb_rela_sw_group e ON e.swid = d.swid where e.groupid=1;

7. Phoenix性能調優

九州体彩官网

將基于SQL的java代(dai)碼移植到Phoenix其(qi)實不難,以Oracle為例,基本流程如(ru)下︰

將Oracle中的所(suo)有表(biao)在Phoenix中重新建立一次,沒(mei)有主鍵的自己加一個主鍵(並建立對應的序列);將Oracle中所(suo)有的序列xiao)?油級莢hoenix中重新建立一次;將程序中的每(mei)條(tiao)SQL語(yu)句都翻譯為Phoenix的SQL語(yu)句,並測(ce)試該語(yu)句是否能夠正(zheng)確運(yun)行(xing),若不能,總能找ye)郊柑tiao)簡單的語(yu)句進行(xing)替代(dai)。

九州体彩官网

移植完成後,經(jing)過一系列debug,程序總算能夠正(zheng)常(chang)運(yun)行(xing)了。但是性能問(wen)題會變得非常(chang)嚴重,這是關系數(shu)據庫和HBase之間的設計思(si)路(lu)和應用問(wen)題域(yu)之間的差(cha)異造成的。
Oracle的設計思(si)路(lu)是盡可(ke)能的mu)燜俁允shu)據進行(xing)操(cao)作,但是隨著表(biao)中記錄數(shu)的不斷xian)黽櫻 檠 閱?中陸怠R racle進行(xing)硬bu)kuo)充會比較困(kun)難,而且會在單表(biao)一億條(tiao)左(zuo)右時(shi)(沒(mei)有經(jing)過本人驗(yan)證)踫到性能瓶頸。Oracle的優勢是在表(biao)中記錄數(shu)不多(幾百萬以內,具體看服務器性能)時(shi)擁有極高的查詢速度。
而HBase的優勢是讓單表(biao)可(ke)以存儲(chu)幾乎(hu)無限的記錄,並且可(ke)以方便的擴(kuo)充硬bu) shi)得查詢速度可(ke)以達到一個穩定(ding)的標準(zhun)。但是其(qi)缺(que)點(dian)在于表(biao)中數(shu)據不多時(shi),查詢速度相(xiang)對較慢(man)。經(jing)測(ce)試,Phoenix的表(biao)在記錄數(shu)很少時(shi)(數(shu)十條(tiao)),查詢單條(tiao)數(shu)據也需要0.2秒左(zuo)右(服務器集(ji)群配置(zhi)見前(qian)面的章節(jie)),而同(tong)時(shi)單服務器的Oracle查詢這樣的數(shu)據僅(jin)需30ms左(zuo)右,相(xiang)差(cha)接近十倍。

九州体彩官网

與(yu)Oracle相(xiang)比,Phoenix在性能上還有一個特點(dian)就(jiu)是在沒(mei)有索引的情況(kuang)下,查詢性能下降很快。
例如(ru)下表(biao)︰

CREATE TABLE IF NOT EXISTS WXB_WORD (ID INTEGER PRIMARY KEY, NAME VARCHAR, VALUE DOUBLE, HEAT INTEGER, FOCUSLEVEL INTEGER, USERID INTEGER);

不建立索引的情況(kuang)下,在前(qian)面介紹的集(ji)群上進行(xing)查詢性能測(ce)試,查詢語(yu)句如(ru)下(確保單條(tiao)命(ming)中)︰

SELECT * FROM WXB_WORD WHERE NAME=’XXX’;

50萬條(tiao)記錄,平均單條(tiao)查詢時(shi)間為0.38秒;
100萬條(tiao)記錄,平均單條(tiao)查詢時(shi)間為0.79秒;
500萬條(tiao)記錄,平均單條(tiao)查詢時(shi)間為4.31秒;
然而在NAME字(zi)段上建立索引後,將表(biao)中數(shu)據增加到1億條(tiao),平均單條(tiao)查詢時(shi)間為0.164秒,可(ke)見索引對Phoenix性能的提升(sheng)作用是無可(ke)替代(dai)的。

九州体彩官网

Phoenix中的索引被(bei)稱(chen)之為Secondary Indexing(二級索引),這是為了和HBase主鍵上的索引區分開。在HBase中,每(mei)個表(biao)有且僅(jin)有一個主鍵的索引,該索引按照字(zi)zhi)湫蚪xing)排序;所(suo)有不基于主鍵的查詢都會導致(zhi)全表(biao)掃描,效率非常(chang)低下。在Phoenix中,可(ke)以對表(biao)中的任何一個字(zi)段或者幾個字(zi)段建立二級索引,該索引實際上是一個獨立的表(biao),表(biao)中包含了被(bei)索引的列以及建立索引時(shi)包含的列(在索引的include語(yu)句中包含的列)。當用mei)?員biao)進行(xing)查詢時(shi),會首先對yun)饕xing)查詢,若能夠得到全部的結果(guo),則會直接返回,否則就(jiu)到原表(biao)中進行(xing)查詢。
注意(yi),Phoenix的每(mei)個表(biao)都可(ke)以建立多個索引,索引和原表(biao)之間的同(tong)步由Phoenix保證。但是,索引越多,寫(xie)入效率越低。
Phoenix支持zhi)街zhong)類型的索引︰可(ke)變索引(mutable indexing)和不可(ke)變索引(immutable indexing)。在表(biao)中數(shu)據需要變化時(shi),使(shi)用mei)ke)變索引;當應用場景(jing)為“一次寫(xie)入,只(zhi)會追加,永不改變”bi)shi)使(shi)用不可(ke)變索引。本文(wen)中只(zhi)使(shi)用了可(ke)變索引。

九州体彩官网

在建立索引之前(qian),再(zai)次檢查Phoenix的配置(zhi),在HBase集(ji)群的每(mei)個服務器的hbase-site.xml配置(zhi)文(wen)件中,加入︰

<property> <name>hbase.regionserver.wal.codec</name> <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value></property>

例如(ru)︰在WXB_WORD表(biao)上對NAME字(zi)段建立DESC索引,該索引還包含了VALUE字(zi)段的值(zhi)(注意(yi),Phoenix是大小(xiao)寫(xie)不敏(min)感的)。

create index if not exists idx_wxb_word on wxb_word (name desc) include (value) ;

那麼這種(zhong)語(yu)句就(jiu)查詢得特別快︰

select name,value from wxb_word where name='AHNHLYPKGYAR_59999';

但是如(ru)果(guo)查詢語(yu)句中還需要知道(dao)其(qi)他字(zi)段的值(zhi),例如(ru)︰

select name,value,userid from wxb_word where name='AHNHLYPKGYAR_59999';

那麼,就(jiu)和沒(mei)有索引差(cha)不多,因為該索引中沒(mei)有包含userid這個字(zi)段。
另外需要注意(yi)的是︰主鍵不需要索引,查詢也非常(chang)快,這是由HBASE的特性保證的。
刪(shan)除索引語(yu)句︰

drop index if exists idx_wxb_word on wxb_word;

8. 總結

使(shi)用Phoenix將SQL代(dai)碼移植到HBase應注意(yi)以下幾個問(wen)題。
第一,應用場景(jing)是否合適?是否需要在單表(biao)中xie)媧chu)幾乎(hu)無限的數(shu)據,並保證一定(ding)的查詢性能?在數(shu)據量(liang)較少的情景(jing)下,Phoenix反而比Oracle的性能差(cha)。若要追求最高的性能,可(ke)以考慮(lv)同(tong)時(shi)使(shi)用關系數(shu)據庫和HBase,並自己保證這部分數(shu)據的同(tong)步。
第二,Phoenix、HBase、Hadoop、ZooKeeper的版本兼容問(wen)題。在大部分情況(kuang)下,開發人員並不能決定(ding)HBase、Hadoop和ZooKeeper的版本,因此(ci)只(zhi)能尋找合適的Phoenix版本來適配它們,這將導致(zhi)你不能使(shi)用最新的Phoenix版本。如(ru)同(tong)本文(wen)中xing)xie)的一樣,這種(zhong)情況(kuang)會導致(zhi)一些Phoenix SQL語(yu)句的特性得不到支持。
第三,注意(yi)Phoenix的每(mei)個表(biao)必(bi)須包含一個主鍵(其(qi)實就(jiu)是HBase的Primary rowkey),且該主鍵自帶索引,合理設計這個主鍵能夠帶來性能上的提升(sheng)和查詢的便利。作為從(cong)SQL時(shi)代(dai)過來的mou)絛蛟保 灼qi)節(jie)約空間的想(xiang)法(fa);在大數(shu)據時(shi)代(dai),就(jiu)是盡可(ke)能的用mei)佔浠皇shi)間。舉個例子,你甚至(zhi)可(ke)以將所(suo)有字(zi)段以一定(ding)的順序和分隔符全部堆到主鍵上。
第四,移植代(dai)碼時(shi),將所(suo)有SQL語(yu)句一一翻譯為對應的Phoenix語(yu)句即可(ke)。注意(yi)參考Phoenix主頁上的語(yu)法(fa)介紹,並一一進行(xing)測(ce)試。Phoenix對JDBC的支持很好,諸(zhu)如(ru)連接池(chi)一類的特性可(ke)以原封不動的照搬。但若原來的mou)絛蚴shi)用了針對SQL語(yu)句的中間件之類的技術,請恕我也不知如(ru)何處理。
第五,一定(ding)要對Phoenix的表(biao)建立二級索引,索引中盡可(ke)能包含所(suo)有xing)枰 檠 淖zi)段。索引會導致(zhi)數(shu)據插(cha)入速度que)瀆man),但會帶來巨大的性能提升(sheng)。

Tag標簽︰代(dai)碼  
  • 九州体彩官网

  • Windows7系統入門 優化 技巧技術專(zhuan)題
  • Windows7系統專(zhuan)題 無論(lun)是升(sheng)級操(cao)作系統、資料(liao)備(bei)份、加強(qiang)資料(liao)的安全及管...... 詳細
About IT165 - 廣(guang)告服務 - 隱私(si)聲明 - 版權申明 - 免責條(tiao)款 - 網站地圖 - 網友投稿 - 聯系方式(shi)
本站內容來自于互聯網,僅(jin)供用于網絡技術學習,學習中請遵循相(xiang)關法(fa)律(lv)法(fa)規
九州体彩官网 | 下一页