使用HBase API刪除數據的時候需要注意的地方有很多,需要分成幾種情況進行分別的討論,進行刪除操作之前,首先需要構建刪除對象,即org.apache.hadoop.hbase.client包下的Delete,然后根據實際情況進行具體的操作,下面一一介紹:
1、只傳rowKey
rowKey這個參數可以在構建Delete對象的時候,作為構造方法的參數傳進去。這種情況相當於HBase Shell操作下的deleteall命令,會將指定rowKey下的所有列族以及所有列的所有版本數據都刪除,最終做的標記類型也是DeleteFamily。示例如下,我們客戶端,只傳rowKey值1004,調用API執行刪除操作。
刪除之前:
刪除之后:
會發現,rowKey=1004的所有列族的數據都被刪除了,其中刪除所做的標記類型為DeleteFamily
API的代碼如下:
public static void deleteData(String tableName,String rowKey,String cf,String cn) throws IOException { //1.獲取表對象 Table table = connection.getTable(TableName.valueOf(tableName)); //2.構建刪除對象 Delete delete = new Delete(Bytes.toBytes(rowKey)); //3.執行刪除操作 table.delete(delete); //4.關閉連接 table.close(); }
2、傳入rowKey和Column Family
傳入rowKey和列族,則會將指定的rowKey和列族下的所有版本的數據都給刪除掉,案例實操:
刪除之前,rowKey=1005的對應的數據有兩個列族info1和info2,同時info1下含有兩個列sex和addr,sex列下含有兩個版本的數據:
調用API進行刪除,刪除操作執行之后:
可以判斷,傳入rowKey和列族之后,會刪除指定的rowKey和列族下對應的所有列,以及列的所有版本的數據,同時刪除標記是DeleteFamily。對應的代碼如下:
public static void deleteData(String tableName,String rowKey,String cf,String cn) throws IOException { //1.獲取表對象 Table table = connection.getTable(TableName.valueOf(tableName)); //2.構建刪除對象 Delete delete = new Delete(Bytes.toBytes(rowKey)); //2.2刪除指定的列族 delete.addFamily(Bytes.toBytes(cf)); //3.執行刪除操作 table.delete(delete); //4.關閉連接 table.close(); }
3、傳入rowKey、列族以及列名
在此種情況下,向Delete對象添加列名的方法有兩種:addColumn()和addColumns()。
(1)addColumns()
/** * Delete all versions of the specified column. * @param family family name * @param qualifier column qualifier * @return this for invocation chaining */ public Delete addColumns(final byte [] family, final byte [] qualifier) { addColumns(family, qualifier, this.ts); return this; }
觀察源碼的注釋可以知道,該方法的作用是刪除指定列的所有版本的數據,同時它還有一個重載的方法,多了一個參數,這個參數是時間戳參數,作用是刪除所有小於等於指定列的指定時間戳的所有版本的數據
/** * Delete all versions of the specified column with a timestamp less than * or equal to the specified timestamp. * @param family family name * @param qualifier column qualifier * @param timestamp maximum version timestamp * @return this for invocation chaining */ public Delete addColumns(final byte [] family, final byte [] qualifier, final long timestamp) { if (timestamp < 0) { throw new IllegalArgumentException("Timestamp cannot be negative. ts=" + timestamp); } List<Cell> list = familyMap.get(family); if (list == null) { list = new ArrayList<Cell>(); } list.add(new KeyValue(this.row, family, qualifier, timestamp, KeyValue.Type.DeleteColumn)); familyMap.put(family, list); return this; }
實操一下:
刪除之前,對於rowKey=1010這一行數據,其info2列族下,phone對應的列,含有兩個版本的數據:
執行刪除操作:
可以看到,所有版本的數據都被刪除了。這是沒有指定時間戳的情況,下面來看一下指定時間戳的情況,我們重新向1010這一行put兩個不同版本的數據:
其中,時間戳最大的那個版本的數據的時間戳的值為1596987728940,較小的值為1596987723619。我們插入列的時候,傳入一個介於以上兩個版本數據的時間戳值之間的時間戳1596987728940。然后執行刪除操作,結果如下:
發現時間戳較大的那個數據還存在,但是小於傳入的時間戳值的那個數據被刪除了。
該方法刪除數據的時候,標記的刪除類型也是DeleteFamily。代碼如下:
public static void deleteData(String tableName,String rowKey,String cf,String cn) throws IOException { //1.獲取表對象 Table table = connection.getTable(TableName.valueOf(tableName)); //2.構建刪除對象 Delete delete = new Delete(Bytes.toBytes(rowKey)); delete.addColumns(Bytes.toBytes(cf),Bytes.toBytes(cn),1596987728939l); //3.執行刪除操作 table.delete(delete); //4.關閉連接 table.close(); }
(2)addColumn()
該方法添加指定的列,然后執行刪除操作,是存在較大的爭議的,它的作用是刪除指定列的最新版本的那一條數據而非全部,同時也可以傳入要刪除的指定的時間戳。先來看一下它的源碼:
/** * Delete the latest version of the specified column. * This is an expensive call in that on the server-side, it first does a * get to find the latest versions timestamp. Then it adds a delete using * the fetched cells timestamp. * @param family family name * @param qualifier column qualifier * @return this for invocation chaining */ public Delete addColumn(final byte [] family, final byte [] qualifier) { this.deleteColumn(family, qualifier, this.ts); return this; }
帶有時間戳參數的源碼:
/** * Delete the specified version of the specified column. * @param family family name * @param qualifier column qualifier * @param timestamp version timestamp * @return this for invocation chaining */ public Delete addColumn(byte [] family, byte [] qualifier, long timestamp) { if (timestamp < 0) { throw new IllegalArgumentException("Timestamp cannot be negative. ts=" + timestamp); } List<Cell> list = familyMap.get(family); if(list == null) { list = new ArrayList<Cell>(); } KeyValue kv = new KeyValue(this.row, family, qualifier, timestamp, KeyValue.Type.Delete); list.add(kv); familyMap.put(family, list); return this; }
先來看一下注釋說的,第一個方法的功能就是刪除指定列的最新版本的數據,其中還說這個方法在服務器端是一個比較昂貴的調用,它會先執行get方法獲取最新版本的時間戳,然后將delete標記添加到這個最新的時間戳上。這就相當於標記了指定列最新版本的數據是被刪除的了。第二個方法會傳入一個時間戳參數,然后作用是刪除指定列的指定版本的數據。那我們來案例實操一下:
我們先put兩條數據到表中,其中rowKey=1011,列族為info1,列名為name,列的版本數為1:
執行完成之后,結果如下:
會發現,時間戳最大的那個數據被刪除了,但是執行scan操作,原先時間戳較小的那個數據顯示出來了,其中刪除標記是Delete,刪除標記對應的時間戳正是原先最大的那個時間戳。這是在兩個put的數據都在內存中的時候所出現的情況,那我們再進一步進行一種實驗,調用API執行刪除操作放在put數據並進行刷寫到磁盤之后,我們再次put兩條數據,然后執行flush操作:
調用API執行刪除操作,然后使用scan命令查看數據:
刪除操作的api代碼:
public static void deleteData(String tableName,String rowKey,String cf,String cn) throws IOException { //1.獲取表對象 Table table = connection.getTable(TableName.valueOf(tableName)); //2.構建刪除對象 Delete delete = new Delete(Bytes.toBytes(rowKey)); //2.1設置刪除的列 delete.addColumn(Bytes.toBytes(cf),Bytes.toBytes(cn)); //3.執行刪除操作 table.delete(delete); //4.關閉連接 table.close(); }
查詢結果:
發現1012對應的數據沒了,也就是說在設計表的時候,某個列的最大版本數,被設置為了1,然后當我們多次對這個列put數據的時候,如果這些數據都還在內存中,那我們使用addColumn方法指定刪除的列族和列名的時候,會刪除時間戳最大的那一條數據,然后第二大的時間戳對應的那一條數據對顯示出來。如果在put多條數據和調用API刪除數據的過程之間發生了刷寫數據到磁盤的過程(自動或者手動),那么就不會產生上面的現象。
當然,以上現象都是針對設計表的時候,最大版本數為1的情況,如果設計表的時候,設置了列的最大版本數大於1,那么put多條數據之后,然后刷寫到磁盤,使用addColumn方法指定列和列族刪除數據,最后進行scan掃描的時候還是會有這個列的其他版本的數據顯示出來。
第二種重載的方法,傳入指定的時間戳,這個方法也只是將指定的時間戳標記為delete,如果指定列沒有對應時間戳的數據,則不會刪除任何數據,除非插入的數據的時間戳和指定的時間戳相同,否則是不會刪除數據。
對於addColumn方法應該謹慎使用為好,因為會造成數據不一致的情況發生,比如我們對某個列put了多條數據,在刷寫之前,調用API進行了刪除操作,然后執行了刷寫,這是磁盤中保存的這一列的數據是時間戳較小的那個版本的數據。如果,put了多條數據,刷寫操作在刪除操作之前,那么最終磁盤中該列是沒有數據的(針對列的最大版本數為1的情況),這就造成了數據不一致的情況。
一定要注意,這種方法,標記的時間戳不是專門為這次刪除操作生成的時間戳,而是先去獲取指定列的所有版本中最新版本的時間戳,然后將這個時間戳標記為delete,這是本質。